summaryrefslogtreecommitdiff
path: root/pjmedia/src
diff options
context:
space:
mode:
Diffstat (limited to 'pjmedia/src')
-rw-r--r--pjmedia/src/pjmedia-audiodev/alsa_dev.c980
-rw-r--r--pjmedia/src/pjmedia-audiodev/audiodev.c822
-rw-r--r--pjmedia/src/pjmedia-audiodev/audiotest.c269
-rw-r--r--pjmedia/src/pjmedia-audiodev/bb10_dev.c965
-rw-r--r--pjmedia/src/pjmedia-audiodev/coreaudio_dev.c2107
-rw-r--r--pjmedia/src/pjmedia-audiodev/errno.c206
-rw-r--r--pjmedia/src/pjmedia-audiodev/legacy_dev.c468
-rw-r--r--pjmedia/src/pjmedia-audiodev/null_dev.c388
-rw-r--r--pjmedia/src/pjmedia-audiodev/pa_dev.c1284
-rw-r--r--pjmedia/src/pjmedia-audiodev/s60_g729_bitstream.h171
-rw-r--r--pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp1929
-rw-r--r--pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp1196
-rw-r--r--pjmedia/src/pjmedia-audiodev/symb_vas_dev.cpp2006
-rw-r--r--pjmedia/src/pjmedia-audiodev/wmme_dev.c1524
-rw-r--r--pjmedia/src/pjmedia-codec/amr_sdp_match.c176
-rw-r--r--pjmedia/src/pjmedia-codec/audio_codecs.c119
-rw-r--r--pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c1818
-rw-r--r--pjmedia/src/pjmedia-codec/g722.c714
-rw-r--r--pjmedia/src/pjmedia-codec/g722/g722_dec.c549
-rw-r--r--pjmedia/src/pjmedia-codec/g722/g722_dec.h79
-rw-r--r--pjmedia/src/pjmedia-codec/g722/g722_enc.c576
-rw-r--r--pjmedia/src/pjmedia-codec/g722/g722_enc.h78
-rw-r--r--pjmedia/src/pjmedia-codec/g7221.c950
-rw-r--r--pjmedia/src/pjmedia-codec/g7221_sdp_match.c91
-rw-r--r--pjmedia/src/pjmedia-codec/gsm.c645
-rw-r--r--pjmedia/src/pjmedia-codec/h263_packetizer.c294
-rw-r--r--pjmedia/src/pjmedia-codec/h264_packetizer.c535
-rw-r--r--pjmedia/src/pjmedia-codec/ilbc.c883
-rw-r--r--pjmedia/src/pjmedia-codec/ipp_codecs.c1680
-rw-r--r--pjmedia/src/pjmedia-codec/l16.c729
-rw-r--r--pjmedia/src/pjmedia-codec/opencore_amrnb.c831
-rw-r--r--pjmedia/src/pjmedia-codec/passthrough.c1054
-rw-r--r--pjmedia/src/pjmedia-codec/speex_codec.c997
-rw-r--r--pjmedia/src/pjmedia-videodev/avi_dev.c678
-rw-r--r--pjmedia/src/pjmedia-videodev/colorbar_dev.c631
-rw-r--r--pjmedia/src/pjmedia-videodev/dshow_dev.c1062
-rw-r--r--pjmedia/src/pjmedia-videodev/dshowclasses.cpp242
-rw-r--r--pjmedia/src/pjmedia-videodev/errno.c119
-rw-r--r--pjmedia/src/pjmedia-videodev/ffmpeg_dev.c516
-rw-r--r--pjmedia/src/pjmedia-videodev/ios_dev.m703
-rw-r--r--pjmedia/src/pjmedia-videodev/qt_dev.m697
-rw-r--r--pjmedia/src/pjmedia-videodev/sdl_dev.c1432
-rw-r--r--pjmedia/src/pjmedia-videodev/sdl_dev_m.m20
-rw-r--r--pjmedia/src/pjmedia-videodev/v4l2_dev.c819
-rw-r--r--pjmedia/src/pjmedia-videodev/videodev.c877
-rw-r--r--pjmedia/src/pjmedia/alaw_ulaw.c300
-rw-r--r--pjmedia/src/pjmedia/alaw_ulaw_table.c4207
-rw-r--r--pjmedia/src/pjmedia/avi_player.c794
-rw-r--r--pjmedia/src/pjmedia/bidirectional.c78
-rw-r--r--pjmedia/src/pjmedia/clock_thread.c426
-rw-r--r--pjmedia/src/pjmedia/codec.c639
-rw-r--r--pjmedia/src/pjmedia/conf_switch.c1580
-rw-r--r--pjmedia/src/pjmedia/conference.c2098
-rw-r--r--pjmedia/src/pjmedia/converter.c178
-rw-r--r--pjmedia/src/pjmedia/converter_libswscale.c208
-rw-r--r--pjmedia/src/pjmedia/delaybuf.c405
-rw-r--r--pjmedia/src/pjmedia/dummy.c24
-rw-r--r--pjmedia/src/pjmedia/echo_common.c375
-rw-r--r--pjmedia/src/pjmedia/echo_internal.h79
-rw-r--r--pjmedia/src/pjmedia/echo_port.c146
-rw-r--r--pjmedia/src/pjmedia/echo_speex.c192
-rw-r--r--pjmedia/src/pjmedia/echo_suppress.c805
-rw-r--r--pjmedia/src/pjmedia/endpoint.c942
-rw-r--r--pjmedia/src/pjmedia/errno.c272
-rw-r--r--pjmedia/src/pjmedia/event.c377
-rw-r--r--pjmedia/src/pjmedia/ffmpeg_util.c204
-rw-r--r--pjmedia/src/pjmedia/ffmpeg_util.h55
-rw-r--r--pjmedia/src/pjmedia/format.c416
-rw-r--r--pjmedia/src/pjmedia/g711.c621
-rw-r--r--pjmedia/src/pjmedia/jbuf.c1190
-rw-r--r--pjmedia/src/pjmedia/master_port.c321
-rw-r--r--pjmedia/src/pjmedia/mem_capture.c235
-rw-r--r--pjmedia/src/pjmedia/mem_player.c224
-rw-r--r--pjmedia/src/pjmedia/null_port.c101
-rw-r--r--pjmedia/src/pjmedia/plc_common.c164
-rw-r--r--pjmedia/src/pjmedia/port.c138
-rw-r--r--pjmedia/src/pjmedia/resample_libsamplerate.c207
-rw-r--r--pjmedia/src/pjmedia/resample_port.c232
-rw-r--r--pjmedia/src/pjmedia/resample_resample.c346
-rw-r--r--pjmedia/src/pjmedia/resample_speex.c127
-rw-r--r--pjmedia/src/pjmedia/rtcp.c1104
-rw-r--r--pjmedia/src/pjmedia/rtcp_xr.c858
-rw-r--r--pjmedia/src/pjmedia/rtp.c366
-rw-r--r--pjmedia/src/pjmedia/sdp.c1576
-rw-r--r--pjmedia/src/pjmedia/sdp_cmp.c304
-rw-r--r--pjmedia/src/pjmedia/sdp_neg.c1549
-rw-r--r--pjmedia/src/pjmedia/sdp_wrap.cpp24
-rw-r--r--pjmedia/src/pjmedia/session.c444
-rw-r--r--pjmedia/src/pjmedia/silencedet.c333
-rw-r--r--pjmedia/src/pjmedia/sound_legacy.c284
-rw-r--r--pjmedia/src/pjmedia/sound_port.c742
-rw-r--r--pjmedia/src/pjmedia/splitcomb.c807
-rw-r--r--pjmedia/src/pjmedia/stereo_port.c227
-rw-r--r--pjmedia/src/pjmedia/stream.c2803
-rw-r--r--pjmedia/src/pjmedia/stream_common.c111
-rw-r--r--pjmedia/src/pjmedia/stream_info.c548
-rw-r--r--pjmedia/src/pjmedia/tonegen.c898
-rw-r--r--pjmedia/src/pjmedia/transport_adapter_sample.c440
-rw-r--r--pjmedia/src/pjmedia/transport_ice.c1853
-rw-r--r--pjmedia/src/pjmedia/transport_loop.c404
-rw-r--r--pjmedia/src/pjmedia/transport_srtp.c1680
-rw-r--r--pjmedia/src/pjmedia/transport_udp.c915
-rw-r--r--pjmedia/src/pjmedia/types.c47
-rw-r--r--pjmedia/src/pjmedia/vid_codec.c759
-rw-r--r--pjmedia/src/pjmedia/vid_codec_util.c646
-rw-r--r--pjmedia/src/pjmedia/vid_port.c971
-rw-r--r--pjmedia/src/pjmedia/vid_stream.c1981
-rw-r--r--pjmedia/src/pjmedia/vid_stream_info.c385
-rw-r--r--pjmedia/src/pjmedia/vid_tee.c396
-rw-r--r--pjmedia/src/pjmedia/wav_player.c691
-rw-r--r--pjmedia/src/pjmedia/wav_playlist.c642
-rw-r--r--pjmedia/src/pjmedia/wav_writer.c457
-rw-r--r--pjmedia/src/pjmedia/wave.c58
-rw-r--r--pjmedia/src/pjmedia/wsola.c1139
-rw-r--r--pjmedia/src/test/audio_tool.c410
-rw-r--r--pjmedia/src/test/codec_vectors.c626
-rw-r--r--pjmedia/src/test/jbuf_test.c349
-rw-r--r--pjmedia/src/test/main.c53
-rw-r--r--pjmedia/src/test/mips_test.c2511
-rw-r--r--pjmedia/src/test/rtp_test.c41
-rw-r--r--pjmedia/src/test/sdp_neg_test.c1596
-rw-r--r--pjmedia/src/test/sdptest.c122
-rw-r--r--pjmedia/src/test/session_test.c131
-rw-r--r--pjmedia/src/test/test.c127
-rw-r--r--pjmedia/src/test/test.h50
-rw-r--r--pjmedia/src/test/vectors/.keep0
-rw-r--r--pjmedia/src/test/vid_codec_test.c486
-rw-r--r--pjmedia/src/test/vid_dev_test.c303
-rw-r--r--pjmedia/src/test/vid_port_test.c250
-rw-r--r--pjmedia/src/test/wince_main.c72
-rw-r--r--pjmedia/src/test/wsola_test.c373
131 files changed, 87557 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia-audiodev/alsa_dev.c b/pjmedia/src/pjmedia-audiodev/alsa_dev.c
new file mode 100644
index 0000000..72b995e
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/alsa_dev.c
@@ -0,0 +1,980 @@
+/* $Id: alsa_dev.c 4130 2012-05-17 08:35:51Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2007-2009 Keystream AB and Konftel AB, All rights reserved.
+ * Author: <dan.aberg@keystream.se>
+ *
+ * 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 <pjmedia_audiodev.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pjmedia/errno.h>
+
+#if defined(PJMEDIA_AUDIO_DEV_HAS_ALSA) && PJMEDIA_AUDIO_DEV_HAS_ALSA
+
+#include <sys/syscall.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/select.h>
+#include <pthread.h>
+#include <errno.h>
+#include <alsa/asoundlib.h>
+
+
+#define THIS_FILE "alsa_dev.c"
+#define ALSA_DEVICE_NAME "plughw:%d,%d"
+#define ALSASOUND_PLAYBACK 1
+#define ALSASOUND_CAPTURE 2
+#define MAX_SOUND_CARDS 5
+#define MAX_SOUND_DEVICES_PER_CARD 5
+#define MAX_DEVICES 16
+
+/* Set to 1 to enable tracing */
+#if 0
+# define TRACE_(expr) PJ_LOG(5,expr)
+#else
+# define TRACE_(expr)
+#endif
+
+/*
+ * Factory prototypes
+ */
+static pj_status_t alsa_factory_init(pjmedia_aud_dev_factory *f);
+static pj_status_t alsa_factory_destroy(pjmedia_aud_dev_factory *f);
+static pj_status_t alsa_factory_refresh(pjmedia_aud_dev_factory *f);
+static unsigned alsa_factory_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t alsa_factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info);
+static pj_status_t alsa_factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param);
+static pj_status_t alsa_factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_strm);
+
+/*
+ * Stream prototypes
+ */
+static pj_status_t alsa_stream_get_param(pjmedia_aud_stream *strm,
+ pjmedia_aud_param *param);
+static pj_status_t alsa_stream_get_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ void *value);
+static pj_status_t alsa_stream_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value);
+static pj_status_t alsa_stream_start(pjmedia_aud_stream *strm);
+static pj_status_t alsa_stream_stop(pjmedia_aud_stream *strm);
+static pj_status_t alsa_stream_destroy(pjmedia_aud_stream *strm);
+
+
+struct alsa_factory
+{
+ pjmedia_aud_dev_factory base;
+ pj_pool_factory *pf;
+ pj_pool_t *pool;
+ pj_pool_t *base_pool;
+
+ unsigned dev_cnt;
+ pjmedia_aud_dev_info devs[MAX_DEVICES];
+};
+
+struct alsa_stream
+{
+ pjmedia_aud_stream base;
+
+ /* Common */
+ pj_pool_t *pool;
+ struct alsa_factory *af;
+ void *user_data;
+ pjmedia_aud_param param; /* Running parameter */
+ int rec_id; /* Capture device id */
+ int quit;
+
+ /* Playback */
+ snd_pcm_t *pb_pcm;
+ snd_pcm_uframes_t pb_frames; /* samples_per_frame */
+ pjmedia_aud_play_cb pb_cb;
+ unsigned pb_buf_size;
+ char *pb_buf;
+ pj_thread_t *pb_thread;
+
+ /* Capture */
+ snd_pcm_t *ca_pcm;
+ snd_pcm_uframes_t ca_frames; /* samples_per_frame */
+ pjmedia_aud_rec_cb ca_cb;
+ unsigned ca_buf_size;
+ char *ca_buf;
+ pj_thread_t *ca_thread;
+};
+
+static pjmedia_aud_dev_factory_op alsa_factory_op =
+{
+ &alsa_factory_init,
+ &alsa_factory_destroy,
+ &alsa_factory_get_dev_count,
+ &alsa_factory_get_dev_info,
+ &alsa_factory_default_param,
+ &alsa_factory_create_stream,
+ &alsa_factory_refresh
+};
+
+static pjmedia_aud_stream_op alsa_stream_op =
+{
+ &alsa_stream_get_param,
+ &alsa_stream_get_cap,
+ &alsa_stream_set_cap,
+ &alsa_stream_start,
+ &alsa_stream_stop,
+ &alsa_stream_destroy
+};
+
+static void null_alsa_error_handler (const char *file,
+ int line,
+ const char *function,
+ int err,
+ const char *fmt,
+ ...)
+{
+ PJ_UNUSED_ARG(file);
+ PJ_UNUSED_ARG(line);
+ PJ_UNUSED_ARG(function);
+ PJ_UNUSED_ARG(err);
+ PJ_UNUSED_ARG(fmt);
+}
+
+static void alsa_error_handler (const char *file,
+ int line,
+ const char *function,
+ int err,
+ const char *fmt,
+ ...)
+{
+ char err_msg[128];
+ int index;
+ va_list arg;
+
+#ifndef NDEBUG
+ index = snprintf (err_msg, sizeof(err_msg), "ALSA lib %s:%i:(%s) ",
+ file, line, function);
+#else
+ index = snprintf (err_msg, sizeof(err_msg), "ALSA lib: ");
+#endif
+ va_start (arg, fmt);
+ if (index < sizeof(err_msg)-1)
+ index += vsnprintf (err_msg+index, sizeof(err_msg)-index, fmt, arg);
+ va_end(arg);
+ if (err && index < sizeof(err_msg)-1)
+ index += snprintf (err_msg+index, sizeof(err_msg)-index, ": %s",
+ snd_strerror(err));
+ PJ_LOG (4,(THIS_FILE, "%s", err_msg));
+}
+
+
+static pj_status_t add_dev (struct alsa_factory *af, const char *dev_name)
+{
+ pjmedia_aud_dev_info *adi;
+ snd_pcm_t* pcm;
+ int pb_result, ca_result;
+
+ if (af->dev_cnt >= PJ_ARRAY_SIZE(af->devs))
+ return PJ_ETOOMANY;
+
+ adi = &af->devs[af->dev_cnt];
+
+ TRACE_((THIS_FILE, "add_dev (%s): Enter", dev_name));
+
+ /* Try to open the device in playback mode */
+ pb_result = snd_pcm_open (&pcm, dev_name, SND_PCM_STREAM_PLAYBACK, 0);
+ if (pb_result >= 0) {
+ TRACE_((THIS_FILE, "Try to open the device for playback - success"));
+ snd_pcm_close (pcm);
+ } else {
+ TRACE_((THIS_FILE, "Try to open the device for playback - failure"));
+ }
+
+ /* Try to open the device in capture mode */
+ ca_result = snd_pcm_open (&pcm, dev_name, SND_PCM_STREAM_CAPTURE, 0);
+ if (ca_result >= 0) {
+ TRACE_((THIS_FILE, "Try to open the device for capture - success"));
+ snd_pcm_close (pcm);
+ } else {
+ TRACE_((THIS_FILE, "Try to open the device for capture - failure"));
+ }
+
+ /* Check if the device could be opened in playback or capture mode */
+ if (pb_result<0 && ca_result<0) {
+ TRACE_((THIS_FILE, "Unable to open sound device %s", dev_name));
+ return PJMEDIA_EAUD_NODEV;
+ }
+
+ /* Reset device info */
+ pj_bzero(adi, sizeof(*adi));
+
+ /* Set device name */
+ strcpy(adi->name, dev_name);
+
+ /* Check the number of playback channels */
+ adi->output_count = (pb_result>=0) ? 1 : 0;
+
+ /* Check the number of capture channels */
+ adi->input_count = (ca_result>=0) ? 1 : 0;
+
+ /* Set the default sample rate */
+ adi->default_samples_per_sec = 8000;
+
+ /* Driver name */
+ strcpy(adi->driver, "ALSA");
+
+ ++af->dev_cnt;
+
+ PJ_LOG (5,(THIS_FILE, "Added sound device %s", adi->name));
+
+ return PJ_SUCCESS;
+}
+
+
+/* Create ALSA audio driver. */
+pjmedia_aud_dev_factory* pjmedia_alsa_factory(pj_pool_factory *pf)
+{
+ struct alsa_factory *af;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "alsa_aud_base", 256, 256, NULL);
+ af = PJ_POOL_ZALLOC_T(pool, struct alsa_factory);
+ af->pf = pf;
+ af->base_pool = pool;
+ af->base.op = &alsa_factory_op;
+
+ return &af->base;
+}
+
+
+/* API: init factory */
+static pj_status_t alsa_factory_init(pjmedia_aud_dev_factory *f)
+{
+ pj_status_t status = alsa_factory_refresh(f);
+ if (PJ_SUCCESS != status)
+ return status;
+
+ PJ_LOG(4,(THIS_FILE, "ALSA initialized"));
+ return PJ_SUCCESS;
+}
+
+
+/* API: destroy factory */
+static pj_status_t alsa_factory_destroy(pjmedia_aud_dev_factory *f)
+{
+ struct alsa_factory *af = (struct alsa_factory*)f;
+
+ if (af->pool)
+ pj_pool_release(af->pool);
+
+ if (af->base_pool) {
+ pj_pool_t *pool = af->base_pool;
+ af->base_pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ /* Restore handler */
+ snd_lib_error_set_handler(NULL);
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: refresh the device list */
+static pj_status_t alsa_factory_refresh(pjmedia_aud_dev_factory *f)
+{
+ struct alsa_factory *af = (struct alsa_factory*)f;
+ char **hints, **n;
+ int err;
+
+ TRACE_((THIS_FILE, "pjmedia_snd_init: Enumerate sound devices"));
+
+ if (af->pool != NULL) {
+ pj_pool_release(af->pool);
+ af->pool = NULL;
+ }
+
+ af->pool = pj_pool_create(af->pf, "alsa_aud", 256, 256, NULL);
+ af->dev_cnt = 0;
+
+ /* Enumerate sound devices */
+ err = snd_device_name_hint(-1, "pcm", (void***)&hints);
+ if (err != 0)
+ return PJMEDIA_EAUD_SYSERR;
+
+ /* Set a null error handler prior to enumeration to suppress errors */
+ snd_lib_error_set_handler(null_alsa_error_handler);
+
+ n = hints;
+ while (*n != NULL) {
+ char *name = snd_device_name_get_hint(*n, "NAME");
+ if (name != NULL && 0 != strcmp("null", name)) {
+ add_dev(af, name);
+ free(name);
+ }
+ n++;
+ }
+
+ /* Install error handler after enumeration, otherwise we'll get many
+ * error messages about invalid card/device ID.
+ */
+ snd_lib_error_set_handler(alsa_error_handler);
+
+ err = snd_device_name_free_hint((void**)hints);
+
+ PJ_LOG(4,(THIS_FILE, "ALSA driver found %d devices", af->dev_cnt));
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: get device count */
+static unsigned alsa_factory_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+ struct alsa_factory *af = (struct alsa_factory*)f;
+ return af->dev_cnt;
+}
+
+
+/* API: get device info */
+static pj_status_t alsa_factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info)
+{
+ struct alsa_factory *af = (struct alsa_factory*)f;
+
+ PJ_ASSERT_RETURN(index>=0 && index<af->dev_cnt, PJ_EINVAL);
+
+ pj_memcpy(info, &af->devs[index], sizeof(*info));
+ info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+ return PJ_SUCCESS;
+}
+
+/* API: create default parameter */
+static pj_status_t alsa_factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param)
+{
+ struct alsa_factory *af = (struct alsa_factory*)f;
+ pjmedia_aud_dev_info *adi;
+
+ PJ_ASSERT_RETURN(index>=0 && index<af->dev_cnt, PJ_EINVAL);
+
+ adi = &af->devs[index];
+
+ pj_bzero(param, sizeof(*param));
+ if (adi->input_count && adi->output_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param->rec_id = index;
+ param->play_id = index;
+ } else if (adi->input_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE;
+ param->rec_id = index;
+ param->play_id = PJMEDIA_AUD_INVALID_DEV;
+ } else if (adi->output_count) {
+ param->dir = PJMEDIA_DIR_PLAYBACK;
+ param->play_id = index;
+ param->rec_id = PJMEDIA_AUD_INVALID_DEV;
+ } else {
+ return PJMEDIA_EAUD_INVDEV;
+ }
+
+ param->clock_rate = adi->default_samples_per_sec;
+ param->channel_count = 1;
+ param->samples_per_frame = adi->default_samples_per_sec * 20 / 1000;
+ param->bits_per_sample = 16;
+ param->flags = adi->caps;
+ param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+ param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+
+ return PJ_SUCCESS;
+}
+
+
+static int pb_thread_func (void *arg)
+{
+ struct alsa_stream* stream = (struct alsa_stream*) arg;
+ snd_pcm_t* pcm = stream->pb_pcm;
+ int size = stream->pb_buf_size;
+ snd_pcm_uframes_t nframes = stream->pb_frames;
+ void* user_data = stream->user_data;
+ char* buf = stream->pb_buf;
+ pj_timestamp tstamp;
+ int result;
+
+ pj_bzero (buf, size);
+ tstamp.u64 = 0;
+
+ TRACE_((THIS_FILE, "pb_thread_func(%u): Started",
+ (unsigned)syscall(SYS_gettid)));
+
+ snd_pcm_prepare (pcm);
+
+ while (!stream->quit) {
+ pjmedia_frame frame;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = buf;
+ frame.size = size;
+ frame.timestamp.u64 = tstamp.u64;
+ frame.bit_info = 0;
+
+ result = stream->pb_cb (user_data, &frame);
+ if (result != PJ_SUCCESS || stream->quit)
+ break;
+
+ if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
+ pj_bzero (buf, size);
+
+ result = snd_pcm_writei (pcm, buf, nframes);
+ if (result == -EPIPE) {
+ PJ_LOG (4,(THIS_FILE, "pb_thread_func: underrun!"));
+ snd_pcm_prepare (pcm);
+ } else if (result < 0) {
+ PJ_LOG (4,(THIS_FILE, "pb_thread_func: error writing data!"));
+ }
+
+ tstamp.u64 += nframes;
+ }
+
+ snd_pcm_drain (pcm);
+ TRACE_((THIS_FILE, "pb_thread_func: Stopped"));
+ return PJ_SUCCESS;
+}
+
+
+
+static int ca_thread_func (void *arg)
+{
+ struct alsa_stream* stream = (struct alsa_stream*) arg;
+ snd_pcm_t* pcm = stream->ca_pcm;
+ int size = stream->ca_buf_size;
+ snd_pcm_uframes_t nframes = stream->ca_frames;
+ void* user_data = stream->user_data;
+ char* buf = stream->ca_buf;
+ pj_timestamp tstamp;
+ int result;
+ struct sched_param param;
+ pthread_t* thid;
+
+ thid = (pthread_t*) pj_thread_get_os_handle (pj_thread_this());
+ param.sched_priority = sched_get_priority_max (SCHED_RR);
+ PJ_LOG (5,(THIS_FILE, "ca_thread_func(%u): Set thread priority "
+ "for audio capture thread.",
+ (unsigned)syscall(SYS_gettid)));
+ result = pthread_setschedparam (*thid, SCHED_RR, &param);
+ if (result) {
+ if (result == EPERM)
+ PJ_LOG (5,(THIS_FILE, "Unable to increase thread priority, "
+ "root access needed."));
+ else
+ PJ_LOG (5,(THIS_FILE, "Unable to increase thread priority, "
+ "error: %d",
+ result));
+ }
+
+ pj_bzero (buf, size);
+ tstamp.u64 = 0;
+
+ TRACE_((THIS_FILE, "ca_thread_func(%u): Started",
+ (unsigned)syscall(SYS_gettid)));
+
+ snd_pcm_prepare (pcm);
+
+ while (!stream->quit) {
+ pjmedia_frame frame;
+
+ pj_bzero (buf, size);
+ result = snd_pcm_readi (pcm, buf, nframes);
+ if (result == -EPIPE) {
+ PJ_LOG (4,(THIS_FILE, "ca_thread_func: overrun!"));
+ snd_pcm_prepare (pcm);
+ continue;
+ } else if (result < 0) {
+ PJ_LOG (4,(THIS_FILE, "ca_thread_func: error reading data!"));
+ }
+ if (stream->quit)
+ break;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = (void*) buf;
+ frame.size = size;
+ frame.timestamp.u64 = tstamp.u64;
+ frame.bit_info = 0;
+
+ result = stream->ca_cb (user_data, &frame);
+ if (result != PJ_SUCCESS || stream->quit)
+ break;
+
+ tstamp.u64 += nframes;
+ }
+ snd_pcm_drain (pcm);
+ TRACE_((THIS_FILE, "ca_thread_func: Stopped"));
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t open_playback (struct alsa_stream* stream,
+ const pjmedia_aud_param *param)
+{
+ snd_pcm_hw_params_t* params;
+ snd_pcm_format_t format;
+ int result;
+ unsigned int rate;
+ snd_pcm_uframes_t tmp_buf_size;
+ snd_pcm_uframes_t tmp_period_size;
+
+ if (param->play_id < 0 || param->play_id >= stream->af->dev_cnt)
+ return PJMEDIA_EAUD_INVDEV;
+
+ /* Open PCM for playback */
+ PJ_LOG (5,(THIS_FILE, "open_playback: Open playback device '%s'",
+ stream->af->devs[param->play_id].name));
+ result = snd_pcm_open (&stream->pb_pcm,
+ stream->af->devs[param->play_id].name,
+ SND_PCM_STREAM_PLAYBACK,
+ 0);
+ if (result < 0)
+ return PJMEDIA_EAUD_SYSERR;
+
+ /* Allocate a hardware parameters object. */
+ snd_pcm_hw_params_alloca (&params);
+
+ /* Fill it in with default values. */
+ snd_pcm_hw_params_any (stream->pb_pcm, params);
+
+ /* Set interleaved mode */
+ snd_pcm_hw_params_set_access (stream->pb_pcm, params,
+ SND_PCM_ACCESS_RW_INTERLEAVED);
+
+ /* Set format */
+ switch (param->bits_per_sample) {
+ case 8:
+ TRACE_((THIS_FILE, "open_playback: set format SND_PCM_FORMAT_S8"));
+ format = SND_PCM_FORMAT_S8;
+ break;
+ case 16:
+ TRACE_((THIS_FILE, "open_playback: set format SND_PCM_FORMAT_S16_LE"));
+ format = SND_PCM_FORMAT_S16_LE;
+ break;
+ case 24:
+ TRACE_((THIS_FILE, "open_playback: set format SND_PCM_FORMAT_S24_LE"));
+ format = SND_PCM_FORMAT_S24_LE;
+ break;
+ case 32:
+ TRACE_((THIS_FILE, "open_playback: set format SND_PCM_FORMAT_S32_LE"));
+ format = SND_PCM_FORMAT_S32_LE;
+ break;
+ default:
+ TRACE_((THIS_FILE, "open_playback: set format SND_PCM_FORMAT_S16_LE"));
+ format = SND_PCM_FORMAT_S16_LE;
+ break;
+ }
+ snd_pcm_hw_params_set_format (stream->pb_pcm, params, format);
+
+ /* Set number of channels */
+ TRACE_((THIS_FILE, "open_playback: set channels: %d",
+ param->channel_count));
+ snd_pcm_hw_params_set_channels (stream->pb_pcm, params,
+ param->channel_count);
+
+ /* Set clock rate */
+ rate = param->clock_rate;
+ TRACE_((THIS_FILE, "open_playback: set clock rate: %d", rate));
+ snd_pcm_hw_params_set_rate_near (stream->pb_pcm, params, &rate, NULL);
+ TRACE_((THIS_FILE, "open_playback: clock rate set to: %d", rate));
+
+ /* Set period size to samples_per_frame frames. */
+ stream->pb_frames = (snd_pcm_uframes_t) param->samples_per_frame /
+ param->channel_count;
+ TRACE_((THIS_FILE, "open_playback: set period size: %d",
+ stream->pb_frames));
+ tmp_period_size = stream->pb_frames;
+ snd_pcm_hw_params_set_period_size_near (stream->pb_pcm, params,
+ &tmp_period_size, NULL);
+ TRACE_((THIS_FILE, "open_playback: period size set to: %d",
+ tmp_period_size));
+
+ /* Set the sound device buffer size and latency */
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)
+ tmp_buf_size = (rate / 1000) * param->output_latency_ms;
+ else
+ tmp_buf_size = (rate / 1000) * PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+ snd_pcm_hw_params_set_buffer_size_near (stream->pb_pcm, params,
+ &tmp_buf_size);
+ stream->param.output_latency_ms = tmp_buf_size / (rate / 1000);
+
+ /* Set our buffer */
+ stream->pb_buf_size = stream->pb_frames * param->channel_count *
+ (param->bits_per_sample/8);
+ stream->pb_buf = (char*) pj_pool_alloc(stream->pool, stream->pb_buf_size);
+
+ TRACE_((THIS_FILE, "open_playback: buffer size set to: %d",
+ (int)tmp_buf_size));
+ TRACE_((THIS_FILE, "open_playback: playback_latency set to: %d ms",
+ (int)stream->param.output_latency_ms));
+
+ /* Activate the parameters */
+ result = snd_pcm_hw_params (stream->pb_pcm, params);
+ if (result < 0) {
+ snd_pcm_close (stream->pb_pcm);
+ return PJMEDIA_EAUD_SYSERR;
+ }
+
+ PJ_LOG (5,(THIS_FILE, "Opened device alsa(%s) for playing, sample rate=%d"
+ ", ch=%d, bits=%d, period size=%d frames, latency=%d ms",
+ stream->af->devs[param->play_id].name,
+ rate, param->channel_count,
+ param->bits_per_sample, stream->pb_frames,
+ (int)stream->param.output_latency_ms));
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t open_capture (struct alsa_stream* stream,
+ const pjmedia_aud_param *param)
+{
+ snd_pcm_hw_params_t* params;
+ snd_pcm_format_t format;
+ int result;
+ unsigned int rate;
+ snd_pcm_uframes_t tmp_buf_size;
+ snd_pcm_uframes_t tmp_period_size;
+
+ if (param->rec_id < 0 || param->rec_id >= stream->af->dev_cnt)
+ return PJMEDIA_EAUD_INVDEV;
+
+ /* Open PCM for capture */
+ PJ_LOG (5,(THIS_FILE, "open_capture: Open capture device '%s'",
+ stream->af->devs[param->rec_id].name));
+ result = snd_pcm_open (&stream->ca_pcm,
+ stream->af->devs[param->rec_id].name,
+ SND_PCM_STREAM_CAPTURE,
+ 0);
+ if (result < 0)
+ return PJMEDIA_EAUD_SYSERR;
+
+ /* Allocate a hardware parameters object. */
+ snd_pcm_hw_params_alloca (&params);
+
+ /* Fill it in with default values. */
+ snd_pcm_hw_params_any (stream->ca_pcm, params);
+
+ /* Set interleaved mode */
+ snd_pcm_hw_params_set_access (stream->ca_pcm, params,
+ SND_PCM_ACCESS_RW_INTERLEAVED);
+
+ /* Set format */
+ switch (param->bits_per_sample) {
+ case 8:
+ TRACE_((THIS_FILE, "open_capture: set format SND_PCM_FORMAT_S8"));
+ format = SND_PCM_FORMAT_S8;
+ break;
+ case 16:
+ TRACE_((THIS_FILE, "open_capture: set format SND_PCM_FORMAT_S16_LE"));
+ format = SND_PCM_FORMAT_S16_LE;
+ break;
+ case 24:
+ TRACE_((THIS_FILE, "open_capture: set format SND_PCM_FORMAT_S24_LE"));
+ format = SND_PCM_FORMAT_S24_LE;
+ break;
+ case 32:
+ TRACE_((THIS_FILE, "open_capture: set format SND_PCM_FORMAT_S32_LE"));
+ format = SND_PCM_FORMAT_S32_LE;
+ break;
+ default:
+ TRACE_((THIS_FILE, "open_capture: set format SND_PCM_FORMAT_S16_LE"));
+ format = SND_PCM_FORMAT_S16_LE;
+ break;
+ }
+ snd_pcm_hw_params_set_format (stream->ca_pcm, params, format);
+
+ /* Set number of channels */
+ TRACE_((THIS_FILE, "open_capture: set channels: %d",
+ param->channel_count));
+ snd_pcm_hw_params_set_channels (stream->ca_pcm, params,
+ param->channel_count);
+
+ /* Set clock rate */
+ rate = param->clock_rate;
+ TRACE_((THIS_FILE, "open_capture: set clock rate: %d", rate));
+ snd_pcm_hw_params_set_rate_near (stream->ca_pcm, params, &rate, NULL);
+ TRACE_((THIS_FILE, "open_capture: clock rate set to: %d", rate));
+
+ /* Set period size to samples_per_frame frames. */
+ stream->ca_frames = (snd_pcm_uframes_t) param->samples_per_frame /
+ param->channel_count;
+ TRACE_((THIS_FILE, "open_capture: set period size: %d",
+ stream->ca_frames));
+ tmp_period_size = stream->ca_frames;
+ snd_pcm_hw_params_set_period_size_near (stream->ca_pcm, params,
+ &tmp_period_size, NULL);
+ TRACE_((THIS_FILE, "open_capture: period size set to: %d",
+ tmp_period_size));
+
+ /* Set the sound device buffer size and latency */
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)
+ tmp_buf_size = (rate / 1000) * param->input_latency_ms;
+ else
+ tmp_buf_size = (rate / 1000) * PJMEDIA_SND_DEFAULT_REC_LATENCY;
+ snd_pcm_hw_params_set_buffer_size_near (stream->ca_pcm, params,
+ &tmp_buf_size);
+ stream->param.input_latency_ms = tmp_buf_size / (rate / 1000);
+
+ /* Set our buffer */
+ stream->ca_buf_size = stream->ca_frames * param->channel_count *
+ (param->bits_per_sample/8);
+ stream->ca_buf = (char*) pj_pool_alloc (stream->pool, stream->ca_buf_size);
+
+ TRACE_((THIS_FILE, "open_capture: buffer size set to: %d",
+ (int)tmp_buf_size));
+ TRACE_((THIS_FILE, "open_capture: capture_latency set to: %d ms",
+ (int)stream->param.input_latency_ms));
+
+ /* Activate the parameters */
+ result = snd_pcm_hw_params (stream->ca_pcm, params);
+ if (result < 0) {
+ snd_pcm_close (stream->ca_pcm);
+ return PJMEDIA_EAUD_SYSERR;
+ }
+
+ PJ_LOG (5,(THIS_FILE, "Opened device alsa(%s) for capture, sample rate=%d"
+ ", ch=%d, bits=%d, period size=%d frames, latency=%d ms",
+ stream->af->devs[param->rec_id].name,
+ rate, param->channel_count,
+ param->bits_per_sample, stream->ca_frames,
+ (int)stream->param.input_latency_ms));
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: create stream */
+static pj_status_t alsa_factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_strm)
+{
+ struct alsa_factory *af = (struct alsa_factory*)f;
+ pj_status_t status;
+ pj_pool_t* pool;
+ struct alsa_stream* stream;
+
+ pool = pj_pool_create (af->pf, "alsa%p", 1024, 1024, NULL);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ /* Allocate and initialize comon stream data */
+ stream = PJ_POOL_ZALLOC_T (pool, struct alsa_stream);
+ stream->base.op = &alsa_stream_op;
+ stream->pool = pool;
+ stream->af = af;
+ stream->user_data = user_data;
+ stream->pb_cb = play_cb;
+ stream->ca_cb = rec_cb;
+ stream->quit = 0;
+ pj_memcpy(&stream->param, param, sizeof(*param));
+
+ /* Init playback */
+ if (param->dir & PJMEDIA_DIR_PLAYBACK) {
+ status = open_playback (stream, param);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release (pool);
+ return status;
+ }
+ }
+
+ /* Init capture */
+ if (param->dir & PJMEDIA_DIR_CAPTURE) {
+ status = open_capture (stream, param);
+ if (status != PJ_SUCCESS) {
+ if (param->dir & PJMEDIA_DIR_PLAYBACK)
+ snd_pcm_close (stream->pb_pcm);
+ pj_pool_release (pool);
+ return status;
+ }
+ }
+
+ *p_strm = &stream->base;
+ return PJ_SUCCESS;
+}
+
+
+/* API: get running parameter */
+static pj_status_t alsa_stream_get_param(pjmedia_aud_stream *s,
+ pjmedia_aud_param *pi)
+{
+ struct alsa_stream *stream = (struct alsa_stream*)s;
+
+ PJ_ASSERT_RETURN(s && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &stream->param, sizeof(*pi));
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: get capability */
+static pj_status_t alsa_stream_get_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ void *pval)
+{
+ struct alsa_stream *stream = (struct alsa_stream*)s;
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY &&
+ (stream->param.dir & PJMEDIA_DIR_CAPTURE))
+ {
+ /* Recording latency */
+ *(unsigned*)pval = stream->param.input_latency_ms;
+ return PJ_SUCCESS;
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY &&
+ (stream->param.dir & PJMEDIA_DIR_PLAYBACK))
+ {
+ /* Playback latency */
+ *(unsigned*)pval = stream->param.output_latency_ms;
+ return PJ_SUCCESS;
+ } else {
+ return PJMEDIA_EAUD_INVCAP;
+ }
+}
+
+
+/* API: set capability */
+static pj_status_t alsa_stream_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value)
+{
+ PJ_UNUSED_ARG(strm);
+ PJ_UNUSED_ARG(cap);
+ PJ_UNUSED_ARG(value);
+
+ return PJMEDIA_EAUD_INVCAP;
+}
+
+
+/* API: start stream */
+static pj_status_t alsa_stream_start (pjmedia_aud_stream *s)
+{
+ struct alsa_stream *stream = (struct alsa_stream*)s;
+ pj_status_t status = PJ_SUCCESS;
+
+ stream->quit = 0;
+ if (stream->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ status = pj_thread_create (stream->pool,
+ "alsasound_playback",
+ pb_thread_func,
+ stream,
+ 0, //ZERO,
+ 0,
+ &stream->pb_thread);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ if (stream->param.dir & PJMEDIA_DIR_CAPTURE) {
+ status = pj_thread_create (stream->pool,
+ "alsasound_playback",
+ ca_thread_func,
+ stream,
+ 0, //ZERO,
+ 0,
+ &stream->ca_thread);
+ if (status != PJ_SUCCESS) {
+ stream->quit = PJ_TRUE;
+ pj_thread_join(stream->pb_thread);
+ pj_thread_destroy(stream->pb_thread);
+ stream->pb_thread = NULL;
+ }
+ }
+
+ return status;
+}
+
+
+/* API: stop stream */
+static pj_status_t alsa_stream_stop (pjmedia_aud_stream *s)
+{
+ struct alsa_stream *stream = (struct alsa_stream*)s;
+
+ stream->quit = 1;
+
+ if (stream->pb_thread) {
+ TRACE_((THIS_FILE,
+ "alsa_stream_stop(%u): Waiting for playback to stop.",
+ (unsigned)syscall(SYS_gettid)));
+ pj_thread_join (stream->pb_thread);
+ TRACE_((THIS_FILE,
+ "alsa_stream_stop(%u): playback stopped.",
+ (unsigned)syscall(SYS_gettid)));
+ pj_thread_destroy(stream->pb_thread);
+ stream->pb_thread = NULL;
+ }
+
+ if (stream->ca_thread) {
+ TRACE_((THIS_FILE,
+ "alsa_stream_stop(%u): Waiting for capture to stop.",
+ (unsigned)syscall(SYS_gettid)));
+ pj_thread_join (stream->ca_thread);
+ TRACE_((THIS_FILE,
+ "alsa_stream_stop(%u): capture stopped.",
+ (unsigned)syscall(SYS_gettid)));
+ pj_thread_destroy(stream->ca_thread);
+ stream->ca_thread = NULL;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+
+static pj_status_t alsa_stream_destroy (pjmedia_aud_stream *s)
+{
+ struct alsa_stream *stream = (struct alsa_stream*)s;
+
+ alsa_stream_stop (s);
+
+ if (stream->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ snd_pcm_close (stream->pb_pcm);
+ stream->pb_pcm = NULL;
+ }
+ if (stream->param.dir & PJMEDIA_DIR_CAPTURE) {
+ snd_pcm_close (stream->ca_pcm);
+ stream->ca_pcm = NULL;
+ }
+
+ pj_pool_release (stream->pool);
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_AUDIO_DEV_HAS_ALSA */
diff --git a/pjmedia/src/pjmedia-audiodev/audiodev.c b/pjmedia/src/pjmedia-audiodev/audiodev.c
new file mode 100644
index 0000000..9a70ab0
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/audiodev.c
@@ -0,0 +1,822 @@
+/* $Id: audiodev.c 4150 2012-06-01 04:29:56Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia-audiodev/audiodev_imp.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#define THIS_FILE "audiodev.c"
+
+#define DEFINE_CAP(name, info) {name, info}
+
+/* Capability names */
+static struct cap_info
+{
+ const char *name;
+ const char *info;
+} cap_infos[] =
+{
+ DEFINE_CAP("ext-fmt", "Extended/non-PCM format"),
+ DEFINE_CAP("latency-in", "Input latency/buffer size setting"),
+ DEFINE_CAP("latency-out", "Output latency/buffer size setting"),
+ DEFINE_CAP("vol-in", "Input volume setting"),
+ DEFINE_CAP("vol-out", "Output volume setting"),
+ DEFINE_CAP("meter-in", "Input meter"),
+ DEFINE_CAP("meter-out", "Output meter"),
+ DEFINE_CAP("route-in", "Input routing"),
+ DEFINE_CAP("route-out", "Output routing"),
+ DEFINE_CAP("aec", "Accoustic echo cancellation"),
+ DEFINE_CAP("aec-tail", "Tail length setting for AEC"),
+ DEFINE_CAP("vad", "Voice activity detection"),
+ DEFINE_CAP("cng", "Comfort noise generation"),
+ DEFINE_CAP("plg", "Packet loss concealment")
+};
+
+
+/*
+ * The device index seen by application and driver is different.
+ *
+ * At application level, device index is index to global list of device.
+ * At driver level, device index is index to device list on that particular
+ * factory only.
+ */
+#define MAKE_DEV_ID(f_id, index) (((f_id & 0xFFFF) << 16) | (index & 0xFFFF))
+#define GET_INDEX(dev_id) ((dev_id) & 0xFFFF)
+#define GET_FID(dev_id) ((dev_id) >> 16)
+#define DEFAULT_DEV_ID 0
+
+
+/* extern functions to create factories */
+#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+pjmedia_aud_dev_factory* pjmedia_pa_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_AUDIO_DEV_HAS_COREAUDIO
+pjmedia_aud_dev_factory* pjmedia_coreaudio_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_AUDIO_DEV_HAS_ALSA
+pjmedia_aud_dev_factory* pjmedia_alsa_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_AUDIO_DEV_HAS_BB10
+pjmedia_aud_dev_factory* pjmedia_bb10_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_AUDIO_DEV_HAS_WMME
+pjmedia_aud_dev_factory* pjmedia_wmme_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS
+pjmedia_aud_dev_factory* pjmedia_symb_vas_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
+pjmedia_aud_dev_factory* pjmedia_aps_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA
+pjmedia_aud_dev_factory* pjmedia_symb_mda_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_AUDIO_DEV_HAS_NULL_AUDIO
+pjmedia_aud_dev_factory* pjmedia_null_audio_factory(pj_pool_factory *pf);
+#endif
+
+#define MAX_DRIVERS 16
+#define MAX_DEVS 64
+
+
+/* driver structure */
+struct driver
+{
+ /* Creation function */
+ pjmedia_aud_dev_factory_create_func_ptr create;
+ /* Factory instance */
+ pjmedia_aud_dev_factory *f;
+ char name[32]; /* Driver name */
+ unsigned dev_cnt; /* Number of devices */
+ unsigned start_idx; /* Start index in global list */
+ int rec_dev_idx;/* Default capture device. */
+ int play_dev_idx;/* Default playback device */
+ int dev_idx; /* Default device. */
+};
+
+/* The audio subsystem */
+static struct aud_subsys
+{
+ unsigned init_count; /* How many times init() is called */
+ pj_pool_factory *pf; /* The pool factory. */
+
+ unsigned drv_cnt; /* Number of drivers. */
+ struct driver drv[MAX_DRIVERS]; /* Array of drivers. */
+
+ unsigned dev_cnt; /* Total number of devices. */
+ pj_uint32_t dev_list[MAX_DEVS];/* Array of device IDs. */
+
+} aud_subsys;
+
+/* API: get capability name/info */
+PJ_DEF(const char*) pjmedia_aud_dev_cap_name(pjmedia_aud_dev_cap cap,
+ const char **p_desc)
+{
+ const char *desc;
+ unsigned i;
+
+ if (p_desc==NULL) p_desc = &desc;
+
+ for (i=0; i<PJ_ARRAY_SIZE(cap_infos); ++i) {
+ if ((1 << i)==cap)
+ break;
+ }
+
+ if (i==PJ_ARRAY_SIZE(cap_infos)) {
+ *p_desc = "??";
+ return "??";
+ }
+
+ *p_desc = cap_infos[i].info;
+ return cap_infos[i].name;
+}
+
+static pj_status_t get_cap_pointer(const pjmedia_aud_param *param,
+ pjmedia_aud_dev_cap cap,
+ void **ptr,
+ unsigned *size)
+{
+#define FIELD_INFO(name) *ptr = (void*)&param->name; \
+ *size = sizeof(param->name)
+
+ switch (cap) {
+ case PJMEDIA_AUD_DEV_CAP_EXT_FORMAT:
+ FIELD_INFO(ext_fmt);
+ break;
+ case PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY:
+ FIELD_INFO(input_latency_ms);
+ break;
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY:
+ FIELD_INFO(output_latency_ms);
+ break;
+ case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
+ FIELD_INFO(input_vol);
+ break;
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
+ FIELD_INFO(output_vol);
+ break;
+ case PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE:
+ FIELD_INFO(input_route);
+ break;
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE:
+ FIELD_INFO(output_route);
+ break;
+ case PJMEDIA_AUD_DEV_CAP_EC:
+ FIELD_INFO(ec_enabled);
+ break;
+ case PJMEDIA_AUD_DEV_CAP_EC_TAIL:
+ FIELD_INFO(ec_tail_ms);
+ break;
+ /* vad is no longer in "fmt" in 2.0.
+ case PJMEDIA_AUD_DEV_CAP_VAD:
+ FIELD_INFO(ext_fmt.vad);
+ break;
+ */
+ case PJMEDIA_AUD_DEV_CAP_CNG:
+ FIELD_INFO(cng_enabled);
+ break;
+ case PJMEDIA_AUD_DEV_CAP_PLC:
+ FIELD_INFO(plc_enabled);
+ break;
+ default:
+ return PJMEDIA_EAUD_INVCAP;
+ }
+
+#undef FIELD_INFO
+
+ return PJ_SUCCESS;
+}
+
+/* API: set cap value to param */
+PJ_DEF(pj_status_t) pjmedia_aud_param_set_cap( pjmedia_aud_param *param,
+ pjmedia_aud_dev_cap cap,
+ const void *pval)
+{
+ void *cap_ptr;
+ unsigned cap_size;
+ pj_status_t status;
+
+ status = get_cap_pointer(param, cap, &cap_ptr, &cap_size);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_memcpy(cap_ptr, pval, cap_size);
+ param->flags |= cap;
+
+ return PJ_SUCCESS;
+}
+
+/* API: get cap value from param */
+PJ_DEF(pj_status_t) pjmedia_aud_param_get_cap( const pjmedia_aud_param *param,
+ pjmedia_aud_dev_cap cap,
+ void *pval)
+{
+ void *cap_ptr;
+ unsigned cap_size;
+ pj_status_t status;
+
+ status = get_cap_pointer(param, cap, &cap_ptr, &cap_size);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if ((param->flags & cap) == 0) {
+ pj_bzero(cap_ptr, cap_size);
+ return PJMEDIA_EAUD_INVCAP;
+ }
+
+ pj_memcpy(pval, cap_ptr, cap_size);
+ return PJ_SUCCESS;
+}
+
+/* Internal: init driver */
+static pj_status_t init_driver(unsigned drv_idx, pj_bool_t refresh)
+{
+ struct driver *drv = &aud_subsys.drv[drv_idx];
+ pjmedia_aud_dev_factory *f;
+ unsigned i, dev_cnt;
+ pj_status_t status;
+
+ if (!refresh) {
+ /* Create the factory */
+ f = (*drv->create)(aud_subsys.pf);
+ if (!f)
+ return PJ_EUNKNOWN;
+
+ /* Call factory->init() */
+ status = f->op->init(f);
+ if (status != PJ_SUCCESS) {
+ f->op->destroy(f);
+ return status;
+ }
+ } else {
+ f = drv->f;
+ }
+
+ /* Get number of devices */
+ dev_cnt = f->op->get_dev_count(f);
+ if (dev_cnt + aud_subsys.dev_cnt > MAX_DEVS) {
+ PJ_LOG(4,(THIS_FILE, "%d device(s) cannot be registered because"
+ " there are too many devices",
+ aud_subsys.dev_cnt + dev_cnt - MAX_DEVS));
+ dev_cnt = MAX_DEVS - aud_subsys.dev_cnt;
+ }
+
+ /* enabling this will cause pjsua-lib initialization to fail when there
+ * is no sound device installed in the system, even when pjsua has been
+ * run with --null-audio
+ *
+ if (dev_cnt == 0) {
+ f->op->destroy(f);
+ return PJMEDIA_EAUD_NODEV;
+ }
+ */
+
+ /* Fill in default devices */
+ drv->play_dev_idx = drv->rec_dev_idx = drv->dev_idx = -1;
+ for (i=0; i<dev_cnt; ++i) {
+ pjmedia_aud_dev_info info;
+
+ status = f->op->get_dev_info(f, i, &info);
+ if (status != PJ_SUCCESS) {
+ f->op->destroy(f);
+ return status;
+ }
+
+ if (drv->name[0]=='\0') {
+ /* Set driver name */
+ pj_ansi_strncpy(drv->name, info.driver, sizeof(drv->name));
+ drv->name[sizeof(drv->name)-1] = '\0';
+ }
+
+ if (drv->play_dev_idx < 0 && info.output_count) {
+ /* Set default playback device */
+ drv->play_dev_idx = i;
+ }
+ if (drv->rec_dev_idx < 0 && info.input_count) {
+ /* Set default capture device */
+ drv->rec_dev_idx = i;
+ }
+ if (drv->dev_idx < 0 && info.input_count &&
+ info.output_count)
+ {
+ /* Set default capture and playback device */
+ drv->dev_idx = i;
+ }
+
+ if (drv->play_dev_idx >= 0 && drv->rec_dev_idx >= 0 &&
+ drv->dev_idx >= 0)
+ {
+ /* Done. */
+ break;
+ }
+ }
+
+ /* Register the factory */
+ drv->f = f;
+ drv->f->sys.drv_idx = drv_idx;
+ drv->start_idx = aud_subsys.dev_cnt;
+ drv->dev_cnt = dev_cnt;
+
+ /* Register devices to global list */
+ for (i=0; i<dev_cnt; ++i) {
+ aud_subsys.dev_list[aud_subsys.dev_cnt++] = MAKE_DEV_ID(drv_idx, i);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* Internal: deinit driver */
+static void deinit_driver(unsigned drv_idx)
+{
+ struct driver *drv = &aud_subsys.drv[drv_idx];
+
+ if (drv->f) {
+ drv->f->op->destroy(drv->f);
+ drv->f = NULL;
+ }
+
+ drv->dev_cnt = 0;
+ drv->play_dev_idx = drv->rec_dev_idx = drv->dev_idx = -1;
+}
+
+/* API: Initialize the audio subsystem. */
+PJ_DEF(pj_status_t) pjmedia_aud_subsys_init(pj_pool_factory *pf)
+{
+ unsigned i;
+ pj_status_t status;
+
+ /* Allow init() to be called multiple times as long as there is matching
+ * number of shutdown().
+ */
+ if (aud_subsys.init_count++ != 0) {
+ return PJ_SUCCESS;
+ }
+
+ /* Register error subsystem */
+ status = pj_register_strerror(PJMEDIA_AUDIODEV_ERRNO_START,
+ PJ_ERRNO_SPACE_SIZE,
+ &pjmedia_audiodev_strerror);
+ pj_assert(status == PJ_SUCCESS);
+
+ /* Init */
+ aud_subsys.pf = pf;
+ aud_subsys.drv_cnt = 0;
+ aud_subsys.dev_cnt = 0;
+
+ /* Register creation functions */
+#if PJMEDIA_AUDIO_DEV_HAS_BB10
+ aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_bb10_factory;
+#endif
+#if PJMEDIA_AUDIO_DEV_HAS_ALSA
+ aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_alsa_factory;
+#endif
+#if PJMEDIA_AUDIO_DEV_HAS_COREAUDIO
+ aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_coreaudio_factory;
+#endif
+#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+ aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_pa_factory;
+#endif
+#if PJMEDIA_AUDIO_DEV_HAS_WMME
+ aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_wmme_factory;
+#endif
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS
+ aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_symb_vas_factory;
+#endif
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
+ aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_aps_factory;
+#endif
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA
+ aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_symb_mda_factory;
+#endif
+#if PJMEDIA_AUDIO_DEV_HAS_NULL_AUDIO
+ aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_null_audio_factory;
+#endif
+
+ /* Initialize each factory and build the device ID list */
+ for (i=0; i<aud_subsys.drv_cnt; ++i) {
+ status = init_driver(i, PJ_FALSE);
+ if (status != PJ_SUCCESS) {
+ deinit_driver(i);
+ continue;
+ }
+ }
+
+ return aud_subsys.dev_cnt ? PJ_SUCCESS : status;
+}
+
+/* API: register an audio device factory to the audio subsystem. */
+PJ_DEF(pj_status_t)
+pjmedia_aud_register_factory(pjmedia_aud_dev_factory_create_func_ptr adf)
+{
+ pj_status_t status;
+
+ if (aud_subsys.init_count == 0)
+ return PJMEDIA_EAUD_INIT;
+
+ aud_subsys.drv[aud_subsys.drv_cnt].create = adf;
+ status = init_driver(aud_subsys.drv_cnt, PJ_FALSE);
+ if (status == PJ_SUCCESS) {
+ aud_subsys.drv_cnt++;
+ } else {
+ deinit_driver(aud_subsys.drv_cnt);
+ }
+
+ return status;
+}
+
+/* API: unregister an audio device factory from the audio subsystem. */
+PJ_DEF(pj_status_t)
+pjmedia_aud_unregister_factory(pjmedia_aud_dev_factory_create_func_ptr adf)
+{
+ unsigned i, j;
+
+ if (aud_subsys.init_count == 0)
+ return PJMEDIA_EAUD_INIT;
+
+ for (i=0; i<aud_subsys.drv_cnt; ++i) {
+ struct driver *drv = &aud_subsys.drv[i];
+
+ if (drv->create == adf) {
+ for (j = drv->start_idx; j < drv->start_idx + drv->dev_cnt; j++)
+ {
+ aud_subsys.dev_list[j] = (pj_uint32_t)PJMEDIA_AUD_INVALID_DEV;
+ }
+
+ deinit_driver(i);
+ pj_bzero(drv, sizeof(*drv));
+ return PJ_SUCCESS;
+ }
+ }
+
+ return PJMEDIA_EAUD_ERR;
+}
+
+/* API: get the pool factory registered to the audio subsystem. */
+PJ_DEF(pj_pool_factory*) pjmedia_aud_subsys_get_pool_factory(void)
+{
+ return aud_subsys.pf;
+}
+
+/* API: Shutdown the audio subsystem. */
+PJ_DEF(pj_status_t) pjmedia_aud_subsys_shutdown(void)
+{
+ unsigned i;
+
+ /* Allow shutdown() to be called multiple times as long as there is matching
+ * number of init().
+ */
+ if (aud_subsys.init_count == 0) {
+ return PJ_SUCCESS;
+ }
+ --aud_subsys.init_count;
+
+ if (aud_subsys.init_count == 0) {
+ for (i=0; i<aud_subsys.drv_cnt; ++i) {
+ deinit_driver(i);
+ }
+
+ aud_subsys.pf = NULL;
+ }
+ return PJ_SUCCESS;
+}
+
+/* API: Refresh the list of sound devices installed in the system. */
+PJ_DEF(pj_status_t) pjmedia_aud_dev_refresh(void)
+{
+ unsigned i;
+
+ aud_subsys.dev_cnt = 0;
+ for (i=0; i<aud_subsys.drv_cnt; ++i) {
+ struct driver *drv = &aud_subsys.drv[i];
+
+ if (drv->f && drv->f->op->refresh) {
+ pj_status_t status = drv->f->op->refresh(drv->f);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4, (THIS_FILE, status, "Unable to refresh device "
+ "list for %s", drv->name));
+ }
+ }
+ init_driver(i, PJ_TRUE);
+ }
+ return PJ_SUCCESS;
+}
+
+/* API: Get the number of sound devices installed in the system. */
+PJ_DEF(unsigned) pjmedia_aud_dev_count(void)
+{
+ return aud_subsys.dev_cnt;
+}
+
+/* Internal: convert local index to global device index */
+static pj_status_t make_global_index(unsigned drv_idx,
+ pjmedia_aud_dev_index *id)
+{
+ if (*id < 0) {
+ return PJ_SUCCESS;
+ }
+
+ /* Check that factory still exists */
+ PJ_ASSERT_RETURN(aud_subsys.drv[drv_idx].f, PJ_EBUG);
+
+ /* Check that device index is valid */
+ PJ_ASSERT_RETURN(*id>=0 && *id<(int)aud_subsys.drv[drv_idx].dev_cnt,
+ PJ_EBUG);
+
+ *id += aud_subsys.drv[drv_idx].start_idx;
+ return PJ_SUCCESS;
+}
+
+/* Internal: lookup device id */
+static pj_status_t lookup_dev(pjmedia_aud_dev_index id,
+ pjmedia_aud_dev_factory **p_f,
+ unsigned *p_local_index)
+{
+ int f_id, index;
+
+ if (id < 0) {
+ unsigned i;
+
+ if (id == PJMEDIA_AUD_INVALID_DEV)
+ return PJMEDIA_EAUD_INVDEV;
+
+ for (i=0; i<aud_subsys.drv_cnt; ++i) {
+ struct driver *drv = &aud_subsys.drv[i];
+ if (drv->dev_idx >= 0) {
+ id = drv->dev_idx;
+ make_global_index(i, &id);
+ break;
+ } else if (id==PJMEDIA_AUD_DEFAULT_CAPTURE_DEV &&
+ drv->rec_dev_idx >= 0)
+ {
+ id = drv->rec_dev_idx;
+ make_global_index(i, &id);
+ break;
+ } else if (id==PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV &&
+ drv->play_dev_idx >= 0)
+ {
+ id = drv->play_dev_idx;
+ make_global_index(i, &id);
+ break;
+ }
+ }
+
+ if (id < 0) {
+ return PJMEDIA_EAUD_NODEFDEV;
+ }
+ }
+
+ f_id = GET_FID(aud_subsys.dev_list[id]);
+ index = GET_INDEX(aud_subsys.dev_list[id]);
+
+ if (f_id < 0 || f_id >= (int)aud_subsys.drv_cnt)
+ return PJMEDIA_EAUD_INVDEV;
+
+ if (index < 0 || index >= (int)aud_subsys.drv[f_id].dev_cnt)
+ return PJMEDIA_EAUD_INVDEV;
+
+ *p_f = aud_subsys.drv[f_id].f;
+ *p_local_index = (unsigned)index;
+
+ return PJ_SUCCESS;
+
+}
+
+/* API: Get device information. */
+PJ_DEF(pj_status_t) pjmedia_aud_dev_get_info(pjmedia_aud_dev_index id,
+ pjmedia_aud_dev_info *info)
+{
+ pjmedia_aud_dev_factory *f;
+ unsigned index;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(info && id!=PJMEDIA_AUD_INVALID_DEV, PJ_EINVAL);
+ PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT);
+
+ status = lookup_dev(id, &f, &index);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ return f->op->get_dev_info(f, index, info);
+}
+
+/* API: find device */
+PJ_DEF(pj_status_t) pjmedia_aud_dev_lookup( const char *drv_name,
+ const char *dev_name,
+ pjmedia_aud_dev_index *id)
+{
+ pjmedia_aud_dev_factory *f = NULL;
+ unsigned drv_idx, dev_idx;
+
+ PJ_ASSERT_RETURN(drv_name && dev_name && id, PJ_EINVAL);
+ PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT);
+
+ for (drv_idx=0; drv_idx<aud_subsys.drv_cnt; ++drv_idx) {
+ if (!pj_ansi_stricmp(drv_name, aud_subsys.drv[drv_idx].name)) {
+ f = aud_subsys.drv[drv_idx].f;
+ break;
+ }
+ }
+
+ if (!f)
+ return PJ_ENOTFOUND;
+
+ for (dev_idx=0; dev_idx<aud_subsys.drv[drv_idx].dev_cnt; ++dev_idx) {
+ pjmedia_aud_dev_info info;
+ pj_status_t status;
+
+ status = f->op->get_dev_info(f, dev_idx, &info);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (!pj_ansi_stricmp(dev_name, info.name))
+ break;
+ }
+
+ if (dev_idx==aud_subsys.drv[drv_idx].dev_cnt)
+ return PJ_ENOTFOUND;
+
+ *id = dev_idx;
+ make_global_index(drv_idx, id);
+
+ return PJ_SUCCESS;
+}
+
+/* API: Initialize the audio device parameters with default values for the
+ * specified device.
+ */
+PJ_DEF(pj_status_t) pjmedia_aud_dev_default_param(pjmedia_aud_dev_index id,
+ pjmedia_aud_param *param)
+{
+ pjmedia_aud_dev_factory *f;
+ unsigned index;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(param && id!=PJMEDIA_AUD_INVALID_DEV, PJ_EINVAL);
+ PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT);
+
+ status = lookup_dev(id, &f, &index);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = f->op->default_param(f, index, param);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Normalize device IDs */
+ make_global_index(f->sys.drv_idx, &param->rec_id);
+ make_global_index(f->sys.drv_idx, &param->play_id);
+
+ return PJ_SUCCESS;
+}
+
+/* API: Open audio stream object using the specified parameters. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_create(const pjmedia_aud_param *prm,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm)
+{
+ pjmedia_aud_dev_factory *rec_f=NULL, *play_f=NULL, *f=NULL;
+ pjmedia_aud_param param;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(prm && prm->dir && p_aud_strm, PJ_EINVAL);
+ PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT);
+ PJ_ASSERT_RETURN(prm->dir==PJMEDIA_DIR_CAPTURE ||
+ prm->dir==PJMEDIA_DIR_PLAYBACK ||
+ prm->dir==PJMEDIA_DIR_CAPTURE_PLAYBACK,
+ PJ_EINVAL);
+
+ /* Must make copy of param because we're changing device ID */
+ pj_memcpy(&param, prm, sizeof(param));
+
+ /* Normalize rec_id */
+ if (param.dir & PJMEDIA_DIR_CAPTURE) {
+ unsigned index;
+
+ if (param.rec_id < 0)
+ param.rec_id = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV;
+
+ status = lookup_dev(param.rec_id, &rec_f, &index);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ param.rec_id = index;
+ f = rec_f;
+ }
+
+ /* Normalize play_id */
+ if (param.dir & PJMEDIA_DIR_PLAYBACK) {
+ unsigned index;
+
+ if (param.play_id < 0)
+ param.play_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
+
+ status = lookup_dev(param.play_id, &play_f, &index);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ param.play_id = index;
+ f = play_f;
+ }
+
+ PJ_ASSERT_RETURN(f != NULL, PJ_EBUG);
+
+ /* For now, rec_id and play_id must belong to the same factory */
+ PJ_ASSERT_RETURN((param.dir != PJMEDIA_DIR_CAPTURE_PLAYBACK) ||
+ (rec_f == play_f),
+ PJMEDIA_EAUD_INVDEV);
+
+ /* Create the stream */
+ status = f->op->create_stream(f, &param, rec_cb, play_cb,
+ user_data, p_aud_strm);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Assign factory id to the stream */
+ (*p_aud_strm)->sys.drv_idx = f->sys.drv_idx;
+ return PJ_SUCCESS;
+}
+
+/* API: Get the running parameters for the specified audio stream. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_get_param(pjmedia_aud_stream *strm,
+ pjmedia_aud_param *param)
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(strm && param, PJ_EINVAL);
+ PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT);
+
+ status = strm->op->get_param(strm, param);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Normalize device id's */
+ make_global_index(strm->sys.drv_idx, &param->rec_id);
+ make_global_index(strm->sys.drv_idx, &param->play_id);
+
+ return PJ_SUCCESS;
+}
+
+/* API: Get the value of a specific capability of the audio stream. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_get_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ void *value)
+{
+ return strm->op->get_cap(strm, cap, value);
+}
+
+/* API: Set the value of a specific capability of the audio stream. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value)
+{
+ return strm->op->set_cap(strm, cap, value);
+}
+
+/* API: Start the stream. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_start(pjmedia_aud_stream *strm)
+{
+ return strm->op->start(strm);
+}
+
+/* API: Stop the stream. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_stop(pjmedia_aud_stream *strm)
+{
+ return strm->op->stop(strm);
+}
+
+/* API: Destroy the stream. */
+PJ_DEF(pj_status_t) pjmedia_aud_stream_destroy(pjmedia_aud_stream *strm)
+{
+ return strm->op->destroy(strm);
+}
+
+
diff --git a/pjmedia/src/pjmedia-audiodev/audiotest.c b/pjmedia/src/pjmedia-audiodev/audiotest.c
new file mode 100644
index 0000000..82893f5
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/audiotest.c
@@ -0,0 +1,269 @@
+/* $Id: audiotest.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia-audiodev/audiotest.h>
+#include <pjmedia-audiodev/audiodev.h>
+#include <pjlib.h>
+#include <pjlib-util.h>
+
+#define THIS_FILE "audiotest.c"
+
+/* Test duration in msec */
+#define DURATION 10000
+
+/* Skip the first msec from the calculation */
+#define SKIP_DURATION 1000
+
+/* Division helper */
+#define DIV_ROUND_UP(a,b) (((a) + ((b) - 1)) / (b))
+#define DIV_ROUND(a,b) (((a) + ((b)/2 - 1)) / (b))
+
+struct stream_data
+{
+ pj_uint32_t first_timestamp;
+ pj_uint32_t last_timestamp;
+ pj_timestamp last_called;
+ pj_math_stat delay;
+};
+
+struct test_data
+{
+ pj_pool_t *pool;
+ const pjmedia_aud_param *param;
+ pjmedia_aud_test_results *result;
+ pj_bool_t running;
+ pj_bool_t has_error;
+ pj_mutex_t *mutex;
+
+ struct stream_data capture_data;
+ struct stream_data playback_data;
+};
+
+static pj_status_t play_cb(void *user_data, pjmedia_frame *frame)
+{
+ struct test_data *test_data = (struct test_data *)user_data;
+ struct stream_data *strm_data = &test_data->playback_data;
+
+ pj_mutex_lock(test_data->mutex);
+
+ /* Skip frames when test is not started or test has finished */
+ if (!test_data->running) {
+ pj_bzero(frame->buf, frame->size);
+ pj_mutex_unlock(test_data->mutex);
+ return PJ_SUCCESS;
+ }
+
+ /* Save last timestamp seen (to calculate drift) */
+ strm_data->last_timestamp = frame->timestamp.u32.lo;
+
+ if (strm_data->last_called.u64 == 0) {
+ /* Init vars. */
+ pj_get_timestamp(&strm_data->last_called);
+ pj_math_stat_init(&strm_data->delay);
+ strm_data->first_timestamp = frame->timestamp.u32.lo;
+ } else {
+ pj_timestamp now;
+ unsigned delay;
+
+ /* Calculate frame interval */
+ pj_get_timestamp(&now);
+ delay = pj_elapsed_usec(&strm_data->last_called, &now);
+ strm_data->last_called = now;
+
+ /* Update frame interval statistic */
+ pj_math_stat_update(&strm_data->delay, delay);
+ }
+
+ pj_bzero(frame->buf, frame->size);
+
+ pj_mutex_unlock(test_data->mutex);
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t rec_cb(void *user_data, pjmedia_frame *frame)
+{
+ struct test_data *test_data = (struct test_data*)user_data;
+ struct stream_data *strm_data = &test_data->capture_data;
+
+ pj_mutex_lock(test_data->mutex);
+
+ /* Skip frames when test is not started or test has finished */
+ if (!test_data->running) {
+ pj_mutex_unlock(test_data->mutex);
+ return PJ_SUCCESS;
+ }
+
+ /* Save last timestamp seen (to calculate drift) */
+ strm_data->last_timestamp = frame->timestamp.u32.lo;
+
+ if (strm_data->last_called.u64 == 0) {
+ /* Init vars. */
+ pj_get_timestamp(&strm_data->last_called);
+ pj_math_stat_init(&strm_data->delay);
+ strm_data->first_timestamp = frame->timestamp.u32.lo;
+ } else {
+ pj_timestamp now;
+ unsigned delay;
+
+ /* Calculate frame interval */
+ pj_get_timestamp(&now);
+ delay = pj_elapsed_usec(&strm_data->last_called, &now);
+ strm_data->last_called = now;
+
+ /* Update frame interval statistic */
+ pj_math_stat_update(&strm_data->delay, delay);
+ }
+
+ pj_mutex_unlock(test_data->mutex);
+ return PJ_SUCCESS;
+}
+
+static void app_perror(const char *title, pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ printf( "%s: %s (err=%d)\n",
+ title, errmsg, status);
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_aud_test( const pjmedia_aud_param *param,
+ pjmedia_aud_test_results *result)
+{
+ pj_status_t status = PJ_SUCCESS;
+ pjmedia_aud_stream *strm;
+ struct test_data test_data;
+ unsigned ptime, tmp;
+
+ /*
+ * Init test parameters
+ */
+ pj_bzero(&test_data, sizeof(test_data));
+ test_data.param = param;
+ test_data.result = result;
+
+ test_data.pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(),
+ "audtest", 1000, 1000, NULL);
+ pj_mutex_create_simple(test_data.pool, "sndtest", &test_data.mutex);
+
+ /*
+ * Open device.
+ */
+ status = pjmedia_aud_stream_create(test_data.param, &rec_cb, &play_cb,
+ &test_data, &strm);
+ if (status != PJ_SUCCESS) {
+ app_perror("Unable to open device", status);
+ pj_pool_release(test_data.pool);
+ return status;
+ }
+
+
+ /* Sleep for a while to let sound device "settles" */
+ pj_thread_sleep(200);
+
+ /*
+ * Start the stream.
+ */
+ status = pjmedia_aud_stream_start(strm);
+ if (status != PJ_SUCCESS) {
+ app_perror("Unable to start capture stream", status);
+ pjmedia_aud_stream_destroy(strm);
+ pj_pool_release(test_data.pool);
+ return status;
+ }
+
+ PJ_LOG(3,(THIS_FILE,
+ " Please wait while test is in progress (~%d secs)..",
+ (DURATION+SKIP_DURATION)/1000));
+
+ /* Let the stream runs for few msec/sec to get stable result.
+ * (capture normally begins with frames available simultaneously).
+ */
+ pj_thread_sleep(SKIP_DURATION);
+
+
+ /* Begin gather data */
+ test_data.running = 1;
+
+ /*
+ * Let the test runs for a while.
+ */
+ pj_thread_sleep(DURATION);
+
+
+ /*
+ * Close stream.
+ */
+ test_data.running = 0;
+ pjmedia_aud_stream_destroy(strm);
+ pj_pool_release(test_data.pool);
+
+
+ /*
+ * Gather results
+ */
+ ptime = param->samples_per_frame * 1000 / param->clock_rate;
+
+ tmp = pj_math_stat_get_stddev(&test_data.capture_data.delay);
+ result->rec.frame_cnt = test_data.capture_data.delay.n;
+ result->rec.min_interval = DIV_ROUND(test_data.capture_data.delay.min, 1000);
+ result->rec.max_interval = DIV_ROUND(test_data.capture_data.delay.max, 1000);
+ result->rec.avg_interval = DIV_ROUND(test_data.capture_data.delay.mean, 1000);
+ result->rec.dev_interval = DIV_ROUND(tmp, 1000);
+ result->rec.max_burst = DIV_ROUND_UP(result->rec.max_interval, ptime);
+
+ tmp = pj_math_stat_get_stddev(&test_data.playback_data.delay);
+ result->play.frame_cnt = test_data.playback_data.delay.n;
+ result->play.min_interval = DIV_ROUND(test_data.playback_data.delay.min, 1000);
+ result->play.max_interval = DIV_ROUND(test_data.playback_data.delay.max, 1000);
+ result->play.avg_interval = DIV_ROUND(test_data.playback_data.delay.mean, 1000);
+ result->play.dev_interval = DIV_ROUND(tmp, 1000);
+ result->play.max_burst = DIV_ROUND_UP(result->play.max_interval, ptime);
+
+ /* Check drifting */
+ if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
+ int play_diff, cap_diff, drift;
+
+ play_diff = test_data.playback_data.last_timestamp -
+ test_data.playback_data.first_timestamp;
+ cap_diff = test_data.capture_data.last_timestamp -
+ test_data.capture_data.first_timestamp;
+ drift = play_diff > cap_diff? play_diff - cap_diff :
+ cap_diff - play_diff;
+
+ /* Allow one frame tolerance for clock drift detection */
+ if (drift < (int)param->samples_per_frame) {
+ result->rec_drift_per_sec = 0;
+ } else {
+ unsigned msec_dur;
+
+ msec_dur = (test_data.capture_data.last_timestamp -
+ test_data.capture_data.first_timestamp) * 1000 /
+ test_data.param->clock_rate;
+
+ result->rec_drift_per_sec = drift * 1000 / msec_dur;
+
+ }
+ }
+
+ return test_data.has_error? PJ_EUNKNOWN : PJ_SUCCESS;
+}
+
diff --git a/pjmedia/src/pjmedia-audiodev/bb10_dev.c b/pjmedia/src/pjmedia-audiodev/bb10_dev.c
new file mode 100644
index 0000000..418256d
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/bb10_dev.c
@@ -0,0 +1,965 @@
+/* $Id: bb10_dev.c 4151 2012-06-01 04:49:57Z ming $ */
+/*
+ * Copyright (C) 2008-2012 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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
+ */
+
+/*
+ * This is the implementation of BlackBerry 10 (BB10) audio device.
+ * Original code was kindly donated by Truphone Ltd. (http://www.truphone.com)
+ * The key methods here are bb10_capture_open, bb10_play_open together
+ * with the capture and play threads ca_thread_func and pb_thread_func
+ */
+
+#include <pjmedia_audiodev.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pjmedia/errno.h>
+
+#if defined(PJMEDIA_AUDIO_DEV_HAS_BB10) && PJMEDIA_AUDIO_DEV_HAS_BB10 != 0
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/select.h>
+#include <pthread.h>
+#include <errno.h>
+#include <sys/asoundlib.h>
+
+
+#define THIS_FILE "bb10_dev.c"
+#define BB10_DEVICE_NAME "plughw:%d,%d"
+/* Double these for 16khz sampling */
+#define PREFERRED_FRAME_SIZE 320
+#define VOIP_SAMPLE_RATE 8000
+
+/* Set to 1 to enable tracing */
+#if 1
+# define TRACE_(expr) PJ_LOG(4,expr)
+#else
+# define TRACE_(expr)
+#endif
+
+/*
+ * Factory prototypes
+ */
+static pj_status_t bb10_factory_init(pjmedia_aud_dev_factory *f);
+static pj_status_t bb10_factory_destroy(pjmedia_aud_dev_factory *f);
+static pj_status_t bb10_factory_refresh(pjmedia_aud_dev_factory *f);
+static unsigned bb10_factory_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t bb10_factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info);
+static pj_status_t bb10_factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param);
+static pj_status_t bb10_factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_strm);
+
+/*
+ * Stream prototypes
+ */
+static pj_status_t bb10_stream_get_param(pjmedia_aud_stream *strm,
+ pjmedia_aud_param *param);
+static pj_status_t bb10_stream_get_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ void *value);
+static pj_status_t bb10_stream_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value);
+static pj_status_t bb10_stream_start(pjmedia_aud_stream *strm);
+static pj_status_t bb10_stream_stop(pjmedia_aud_stream *strm);
+static pj_status_t bb10_stream_destroy(pjmedia_aud_stream *strm);
+
+
+struct bb10_factory
+{
+ pjmedia_aud_dev_factory base;
+ pj_pool_factory *pf;
+ pj_pool_t *pool;
+ pj_pool_t *base_pool;
+ unsigned dev_cnt;
+ pjmedia_aud_dev_info devs[1];
+};
+
+struct bb10_stream
+{
+ pjmedia_aud_stream base;
+
+ /* Common */
+ pj_pool_t *pool;
+ struct bb10_factory *af;
+ void *user_data;
+ pjmedia_aud_param param; /* Running parameter */
+ int rec_id; /* Capture device id */
+ int quit;
+
+ /* Playback */
+ snd_pcm_t *pb_pcm;
+ snd_mixer_t *pb_mixer;
+ unsigned long pb_frames; /* samples_per_frame */
+ pjmedia_aud_play_cb pb_cb;
+ unsigned pb_buf_size;
+ char *pb_buf;
+ pj_thread_t *pb_thread;
+
+ /* Capture */
+ snd_pcm_t *ca_pcm;
+ snd_mixer_t *ca_mixer;
+ unsigned long ca_frames; /* samples_per_frame */
+ pjmedia_aud_rec_cb ca_cb;
+ unsigned ca_buf_size;
+ char *ca_buf;
+ pj_thread_t *ca_thread;
+};
+
+static pjmedia_aud_dev_factory_op bb10_factory_op =
+{
+ &bb10_factory_init,
+ &bb10_factory_destroy,
+ &bb10_factory_get_dev_count,
+ &bb10_factory_get_dev_info,
+ &bb10_factory_default_param,
+ &bb10_factory_create_stream,
+ &bb10_factory_refresh
+};
+
+static pjmedia_aud_stream_op bb10_stream_op =
+{
+ &bb10_stream_get_param,
+ &bb10_stream_get_cap,
+ &bb10_stream_set_cap,
+ &bb10_stream_start,
+ &bb10_stream_stop,
+ &bb10_stream_destroy
+};
+
+/*
+ * BB10 - tests loads the audio units and sets up the driver structure
+ */
+static pj_status_t bb10_add_dev (struct bb10_factory *af)
+{
+ pjmedia_aud_dev_info *adi;
+ int pb_result, ca_result;
+ int card = -1;
+ int dev = 0;
+ snd_pcm_t *pcm_handle;
+
+ if (af->dev_cnt >= PJ_ARRAY_SIZE(af->devs))
+ return PJ_ETOOMANY;
+
+ adi = &af->devs[af->dev_cnt];
+
+ TRACE_((THIS_FILE, "bb10_add_dev Enter"));
+
+ if ((pb_result = snd_pcm_open_preferred (&pcm_handle, &card, &dev,
+ SND_PCM_OPEN_PLAYBACK)) >= 0)
+ {
+ TRACE_((THIS_FILE, "Try to open the device for playback - success"));
+ snd_pcm_close (pcm_handle);
+ } else {
+ TRACE_((THIS_FILE, "Try to open the device for playback - failure"));
+ }
+
+ if ((ca_result = snd_pcm_open_preferred (&pcm_handle, &card, &dev,
+ SND_PCM_OPEN_CAPTURE)) >=0)
+ {
+ TRACE_((THIS_FILE, "Try to open the device for capture - success"));
+ snd_pcm_close (pcm_handle);
+ } else {
+ TRACE_((THIS_FILE, "Try to open the device for capture - failure"));
+ }
+
+ if (pb_result < 0 && ca_result < 0) {
+ TRACE_((THIS_FILE, "Unable to open sound device", "preferred"));
+ return PJMEDIA_EAUD_NODEV;
+ }
+
+ /* Reset device info */
+ pj_bzero(adi, sizeof(*adi));
+
+ /* Set device name */
+ strcpy(adi->name, "preferred");
+
+ /* Check the number of playback channels */
+ adi->output_count = (pb_result >= 0) ? 1 : 0;
+
+ /* Check the number of capture channels */
+ adi->input_count = (ca_result >= 0) ? 1 : 0;
+
+ /* Set the default sample rate */
+ adi->default_samples_per_sec = 8000;
+
+ /* Driver name */
+ strcpy(adi->driver, "BB10");
+
+ ++af->dev_cnt;
+
+ PJ_LOG (4,(THIS_FILE, "Added sound device %s", adi->name));
+
+ return PJ_SUCCESS;
+}
+
+/* Create BB10 audio driver. */
+pjmedia_aud_dev_factory* pjmedia_bb10_factory(pj_pool_factory *pf)
+{
+ struct bb10_factory *af;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "bb10_aud_base", 256, 256, NULL);
+ af = PJ_POOL_ZALLOC_T(pool, struct bb10_factory);
+ af->pf = pf;
+ af->base_pool = pool;
+ af->base.op = &bb10_factory_op;
+
+ return &af->base;
+}
+
+
+/* API: init factory */
+static pj_status_t bb10_factory_init(pjmedia_aud_dev_factory *f)
+{
+ pj_status_t status;
+
+ status = bb10_factory_refresh(f);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ PJ_LOG(4,(THIS_FILE, "BB10 initialized"));
+ return PJ_SUCCESS;
+}
+
+
+/* API: destroy factory */
+static pj_status_t bb10_factory_destroy(pjmedia_aud_dev_factory *f)
+{
+ struct bb10_factory *af = (struct bb10_factory*)f;
+
+ if (af->pool) {
+ TRACE_((THIS_FILE, "bb10_factory_destroy() - 1"));
+ pj_pool_release(af->pool);
+ }
+
+ if (af->base_pool) {
+ pj_pool_t *pool = af->base_pool;
+ af->base_pool = NULL;
+ TRACE_((THIS_FILE, "bb10_factory_destroy() - 2"));
+ pj_pool_release(pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: refresh the device list */
+static pj_status_t bb10_factory_refresh(pjmedia_aud_dev_factory *f)
+{
+ struct bb10_factory *af = (struct bb10_factory*)f;
+ int err;
+
+ TRACE_((THIS_FILE, "bb10_factory_refresh()"));
+
+ if (af->pool != NULL) {
+ pj_pool_release(af->pool);
+ af->pool = NULL;
+ }
+
+ af->pool = pj_pool_create(af->pf, "bb10_aud", 256, 256, NULL);
+ af->dev_cnt = 0;
+
+ err = bb10_add_dev(af);
+
+ PJ_LOG(4,(THIS_FILE, "BB10 driver found %d devices", af->dev_cnt));
+
+ return err;
+}
+
+
+/* API: get device count */
+static unsigned bb10_factory_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+ struct bb10_factory *af = (struct bb10_factory*)f;
+ return af->dev_cnt;
+}
+
+
+/* API: get device info */
+static pj_status_t bb10_factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info)
+{
+ struct bb10_factory *af = (struct bb10_factory*)f;
+
+ PJ_ASSERT_RETURN(index>=0 && index<af->dev_cnt, PJ_EINVAL);
+
+ pj_memcpy(info, &af->devs[index], sizeof(*info));
+ info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default parameter */
+static pj_status_t bb10_factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param)
+{
+ struct bb10_factory *af = (struct bb10_factory*)f;
+ pjmedia_aud_dev_info *adi;
+
+ PJ_ASSERT_RETURN(index>=0 && index<af->dev_cnt, PJ_EINVAL);
+
+ adi = &af->devs[index];
+
+ pj_bzero(param, sizeof(*param));
+ if (adi->input_count && adi->output_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param->rec_id = index;
+ param->play_id = index;
+ } else if (adi->input_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE;
+ param->rec_id = index;
+ param->play_id = PJMEDIA_AUD_INVALID_DEV;
+ } else if (adi->output_count) {
+ param->dir = PJMEDIA_DIR_PLAYBACK;
+ param->play_id = index;
+ param->rec_id = PJMEDIA_AUD_INVALID_DEV;
+ } else {
+ return PJMEDIA_EAUD_INVDEV;
+ }
+
+ param->clock_rate = adi->default_samples_per_sec;
+ param->channel_count = 1;
+ param->samples_per_frame = adi->default_samples_per_sec * 20 / 1000;
+ param->bits_per_sample = 16;
+ param->flags = adi->caps;
+ param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+ param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+
+ TRACE_((THIS_FILE, "bb10_factory_default_param clock = %d flags = %d"
+ " spf = %d", param->clock_rate, param->flags,
+ param->samples_per_frame));
+
+ return PJ_SUCCESS;
+}
+
+
+static void close_play_pcm(struct bb10_stream *stream)
+{
+ if (stream != NULL && stream->pb_pcm != NULL) {
+ snd_pcm_close(stream->pb_pcm);
+ stream->pb_pcm = NULL;
+ }
+}
+
+static void close_play_mixer(struct bb10_stream *stream)
+{
+ if (stream != NULL && stream->pb_mixer != NULL) {
+ snd_mixer_close(stream->pb_mixer);
+ stream->pb_mixer = NULL;
+ }
+}
+
+static void flush_play(struct bb10_stream *stream)
+{
+ if (stream != NULL && stream->pb_pcm != NULL) {
+ snd_pcm_plugin_flush (stream->pb_pcm, SND_PCM_CHANNEL_PLAYBACK);
+ }
+}
+
+static void close_capture_pcm(struct bb10_stream *stream)
+{
+ if (stream != NULL && stream->ca_pcm != NULL) {
+ snd_pcm_close(stream->ca_pcm);
+ stream->ca_pcm = NULL;
+ }
+}
+
+static void close_capture_mixer(struct bb10_stream *stream)
+{
+ if (stream != NULL && stream->ca_mixer != NULL) {
+ snd_mixer_close(stream->ca_mixer);
+ stream->ca_mixer = NULL;
+ }
+}
+
+static void flush_capture(struct bb10_stream *stream)
+{
+ if (stream != NULL && stream->ca_pcm != NULL) {
+ snd_pcm_plugin_flush (stream->ca_pcm, SND_PCM_CHANNEL_CAPTURE);
+ }
+}
+
+
+/**
+ * Play audio received from PJMEDIA
+ */
+static int pb_thread_func (void *arg)
+{
+ struct bb10_stream* stream = (struct bb10_stream *) arg;
+ /* Handle from bb10_open_playback */
+ /* Will be 640 */
+ int size = stream->pb_buf_size;
+ /* 160 frames for 20ms */
+ unsigned long nframes = stream->pb_frames;
+ void *user_data = stream->user_data;
+ char *buf = stream->pb_buf;
+ pj_timestamp tstamp;
+ int result = 0;
+
+ pj_bzero (buf, size);
+ tstamp.u64 = 0;
+
+ TRACE_((THIS_FILE, "pb_thread_func: size = %d ", size));
+
+ /* Do the final initialization now the thread has started. */
+ if ((result = snd_pcm_plugin_prepare(stream->pb_pcm,
+ SND_PCM_CHANNEL_PLAYBACK)) < 0)
+ {
+ close_play_mixer(stream);
+ close_play_pcm(stream);
+ TRACE_((THIS_FILE, "pb_thread_func failed prepare = %d", result));
+ return PJ_SUCCESS;
+ }
+
+ while (!stream->quit) {
+ pjmedia_frame frame;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ /* pointer to buffer filled by PJMEDIA */
+ frame.buf = buf;
+ frame.size = size;
+ frame.timestamp.u64 = tstamp.u64;
+ frame.bit_info = 0;
+
+ result = stream->pb_cb (user_data, &frame);
+ if (result != PJ_SUCCESS || stream->quit)
+ break;
+
+ if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
+ pj_bzero (buf, size);
+
+ /* Write 640 to play unit */
+ result = snd_pcm_plugin_write(stream->pb_pcm,buf,size);
+ if (result != size) {
+ TRACE_((THIS_FILE, "pb_thread_func failed write = %d", result));
+ }
+
+ tstamp.u64 += nframes;
+ }
+
+ flush_play(stream);
+ close_play_mixer(stream);
+ close_play_pcm(stream);
+ TRACE_((THIS_FILE, "pb_thread_func: Stopped"));
+
+ return PJ_SUCCESS;
+}
+
+
+
+static int ca_thread_func (void *arg)
+{
+ struct bb10_stream* stream = (struct bb10_stream *) arg;
+ int size = stream->ca_buf_size;
+ unsigned long nframes = stream->ca_frames;
+ void *user_data = stream->user_data;
+ /* Buffer to fill for PJMEDIA */
+ char *buf = stream->ca_buf;
+ pj_timestamp tstamp;
+ int result;
+ struct sched_param param;
+ pthread_t *thid;
+
+ TRACE_((THIS_FILE, "ca_thread_func: size = %d ", size));
+
+ thid = (pthread_t*) pj_thread_get_os_handle (pj_thread_this());
+ param.sched_priority = sched_get_priority_max (SCHED_RR);
+
+ result = pthread_setschedparam (*thid, SCHED_RR, &param);
+ if (result) {
+ if (result == EPERM) {
+ PJ_LOG (4,(THIS_FILE, "Unable to increase thread priority, "
+ "root access needed."));
+ } else {
+ PJ_LOG (4,(THIS_FILE, "Unable to increase thread priority, "
+ "error: %d", result));
+ }
+ }
+
+ pj_bzero (buf, size);
+ tstamp.u64 = 0;
+
+ /* Final init now the thread has started */
+ if ((result = snd_pcm_plugin_prepare (stream->ca_pcm,
+ SND_PCM_CHANNEL_CAPTURE)) < 0)
+ {
+ close_capture_mixer(stream);
+ close_capture_pcm(stream);
+ TRACE_((THIS_FILE, "ca_thread_func failed prepare = %d", result));
+ return PJ_SUCCESS;
+ }
+
+ while (!stream->quit) {
+ pjmedia_frame frame;
+
+ pj_bzero (buf, size);
+
+ result = snd_pcm_plugin_read(stream->ca_pcm, buf,size);
+ if (result == -EPIPE) {
+ PJ_LOG (4,(THIS_FILE, "ca_thread_func: overrun!"));
+ snd_pcm_plugin_prepare (stream->ca_pcm, SND_PCM_CHANNEL_CAPTURE);
+ continue;
+ } else if (result < 0) {
+ PJ_LOG (4,(THIS_FILE, "ca_thread_func: error reading data!"));
+ }
+
+ if (stream->quit)
+ break;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = (void *) buf;
+ frame.size = size;
+ frame.timestamp.u64 = tstamp.u64;
+ frame.bit_info = 0;
+
+ result = stream->ca_cb (user_data, &frame);
+ if (result != PJ_SUCCESS || stream->quit)
+ break;
+
+ tstamp.u64 += nframes;
+ }
+
+ flush_capture(stream);
+ close_capture_mixer(stream);
+ close_capture_pcm(stream);
+ TRACE_((THIS_FILE, "ca_thread_func: Stopped"));
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t bb10_open_playback (struct bb10_stream *stream,
+ const pjmedia_aud_param *param)
+{
+ int card = -1;
+ int dev = 0;
+ int ret = 0;
+ snd_pcm_channel_info_t pi;
+ snd_pcm_channel_setup_t setup;
+ snd_mixer_group_t group;
+ snd_pcm_channel_params_t pp;
+ unsigned int rate;
+ unsigned long tmp_buf_size;
+
+ if (param->play_id < 0 || param->play_id >= stream->af->dev_cnt) {
+ return PJMEDIA_EAUD_INVDEV;
+ }
+
+ if ((ret = snd_pcm_open_preferred (&stream->pb_pcm, &card, &dev,
+ SND_PCM_OPEN_PLAYBACK)) < 0)
+ {
+ TRACE_((THIS_FILE, "snd_pcm_open_preferred ret = %d", ret));
+ return PJMEDIA_EAUD_SYSERR;
+ }
+
+ /* TODO PJ_ZERO */
+ memset (&pi, 0, sizeof (pi));
+ pi.channel = SND_PCM_CHANNEL_PLAYBACK;
+ if ((ret = snd_pcm_plugin_info (stream->pb_pcm, &pi)) < 0) {
+ TRACE_((THIS_FILE, "snd_pcm_plugin_info ret = %d", ret));
+ return PJMEDIA_EAUD_SYSERR;
+ }
+
+ memset (&pp, 0, sizeof (pp));
+
+ /* Request VoIP compatible capabilities
+ * On simulator frag_size is always negotiated to 170
+ */
+ pp.mode = SND_PCM_MODE_BLOCK;
+ pp.channel = SND_PCM_CHANNEL_PLAYBACK;
+ pp.start_mode = SND_PCM_START_DATA;
+ pp.stop_mode = SND_PCM_STOP_ROLLOVER;
+ /* HARD CODE for the time being PJMEDIA expects 640 for 16khz */
+ pp.buf.block.frag_size = PREFERRED_FRAME_SIZE*2;
+ /* Increasing this internal buffer count delays write failure in the loop */
+ pp.buf.block.frags_max = 4;
+ pp.buf.block.frags_min = 1;
+ pp.format.interleave = 1;
+ /* HARD CODE for the time being PJMEDIA expects 16khz */
+ PJ_TODO(REMOVE_SAMPLE_RATE_HARD_CODE);
+ pj_assert(param->clock_rate == VOIP_SAMPLE_RATE * 2);
+ pp.format.rate = VOIP_SAMPLE_RATE*2;
+ pp.format.voices = 1;
+ pp.format.format = SND_PCM_SFMT_S16_LE;
+
+ /* Make the calls as per the wave sample */
+ if ((ret = snd_pcm_plugin_params (stream->pb_pcm, &pp)) < 0) {
+ TRACE_((THIS_FILE, "snd_pcm_plugin_params ret = %d", ret));
+ return PJMEDIA_EAUD_SYSERR;
+ }
+
+ memset (&setup, 0, sizeof (setup));
+ memset (&group, 0, sizeof (group));
+ setup.channel = SND_PCM_CHANNEL_PLAYBACK;
+ setup.mixer_gid = &group.gid;
+
+ if ((ret = snd_pcm_plugin_setup (stream->pb_pcm, &setup)) < 0) {
+ TRACE_((THIS_FILE, "snd_pcm_plugin_setup ret = %d", ret));
+ return PJMEDIA_EAUD_SYSERR;
+ }
+
+ if (group.gid.name[0] == 0) {
+ return PJMEDIA_EAUD_SYSERR;
+ }
+
+ if ((ret = snd_mixer_open (&stream->pb_mixer, card,
+ setup.mixer_device)) < 0)
+ {
+ TRACE_((THIS_FILE, "snd_mixer_open ret = %d", ret));
+ return PJMEDIA_EAUD_SYSERR;
+ }
+
+
+ rate = param->clock_rate;
+ /* Set the sound device buffer size and latency */
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) {
+ tmp_buf_size = (rate / 1000) * param->output_latency_ms;
+ } else {
+ tmp_buf_size = (rate / 1000) * PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+ }
+ /* Set period size to samples_per_frame frames. */
+ stream->pb_frames = param->samples_per_frame;
+ stream->param.output_latency_ms = tmp_buf_size / (rate / 1000);
+
+ /* Set our buffer */
+ stream->pb_buf_size = stream->pb_frames * param->channel_count *
+ (param->bits_per_sample/8);
+ stream->pb_buf = (char *) pj_pool_alloc(stream->pool, stream->pb_buf_size);
+
+ TRACE_((THIS_FILE, "bb10_open_playback: pb_frames = %d clock = %d",
+ stream->pb_frames, param->clock_rate));
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t bb10_open_capture (struct bb10_stream *stream,
+ const pjmedia_aud_param *param)
+{
+ int ret = 0;
+ unsigned int rate;
+ unsigned long tmp_buf_size;
+ int card = -1;
+ int dev = 0;
+ int frame_size;
+ snd_pcm_channel_info_t pi;
+ snd_mixer_group_t group;
+ snd_pcm_channel_params_t pp;
+ snd_pcm_channel_setup_t setup;
+
+ if (param->rec_id < 0 || param->rec_id >= stream->af->dev_cnt)
+ return PJMEDIA_EAUD_INVDEV;
+
+ /* BB10 Audio init here (not prepare) */
+ if ((ret = snd_pcm_open_preferred (&stream->ca_pcm, &card, &dev,
+ SND_PCM_OPEN_CAPTURE)) < 0)
+ {
+ TRACE_((THIS_FILE, "snd_pcm_open_preferred ret = %d", ret));
+ return PJMEDIA_EAUD_SYSERR;
+ }
+
+ /* sample reads the capabilities of the capture */
+ memset (&pi, 0, sizeof (pi));
+ pi.channel = SND_PCM_CHANNEL_CAPTURE;
+ if ((ret = snd_pcm_plugin_info (stream->ca_pcm, &pi)) < 0) {
+ TRACE_((THIS_FILE, "snd_pcm_plugin_info ret = %d", ret));
+ return PJMEDIA_EAUD_SYSERR;
+ }
+
+ /* Request the VoIP parameters
+ * These parameters are different to waverec sample
+ */
+ memset (&pp, 0, sizeof (pp));
+ /* Blocking read */
+ pp.mode = SND_PCM_MODE_BLOCK;
+ pp.channel = SND_PCM_CHANNEL_CAPTURE;
+ pp.start_mode = SND_PCM_START_DATA;
+ /* Auto-recover from errors */
+ pp.stop_mode = SND_PCM_STOP_ROLLOVER;
+ /* HARD CODE for the time being PJMEDIA expects 640 for 16khz */
+ pp.buf.block.frag_size = PREFERRED_FRAME_SIZE*2;
+ /* Not applicable for capture hence -1 */
+ pp.buf.block.frags_max = -1;
+ pp.buf.block.frags_min = 1;
+ pp.format.interleave = 1;
+ /* HARD CODE for the time being PJMEDIA expects 16khz */
+ PJ_TODO(REMOVE_SAMPLE_RATE_HARD_CODE);
+ pj_assert(param->clock_rate == VOIP_SAMPLE_RATE * 2);
+ pp.format.rate = VOIP_SAMPLE_RATE*2;
+ pp.format.voices = 1;
+ pp.format.format = SND_PCM_SFMT_S16_LE;
+
+ /* make the request */
+ if ((ret = snd_pcm_plugin_params (stream->ca_pcm, &pp)) < 0) {
+ TRACE_((THIS_FILE, "snd_pcm_plugin_params ret = %d", ret));
+ return PJMEDIA_EAUD_SYSERR;
+ }
+
+ /* Again based on the sample */
+ memset (&setup, 0, sizeof (setup));
+ memset (&group, 0, sizeof (group));
+ setup.channel = SND_PCM_CHANNEL_CAPTURE;
+ setup.mixer_gid = &group.gid;
+ if ((ret = snd_pcm_plugin_setup (stream->ca_pcm, &setup)) < 0) {
+ TRACE_((THIS_FILE, "snd_pcm_plugin_setup ret = %d", ret));
+ return PJMEDIA_EAUD_SYSERR;
+ }
+
+ frame_size = setup.buf.block.frag_size;
+
+ if (group.gid.name[0] == 0) {
+ } else {
+ }
+
+ if ((ret = snd_mixer_open (&stream->ca_mixer, card,
+ setup.mixer_device)) < 0)
+ {
+ TRACE_((THIS_FILE,"snd_mixer_open ret = %d",ret));
+ return PJMEDIA_EAUD_SYSERR;
+ }
+
+ /* frag_size should be 160 */
+ frame_size = setup.buf.block.frag_size;
+
+ /* END BB10 init */
+
+ /* Set clock rate */
+ rate = param->clock_rate;
+ stream->ca_frames = (unsigned long) param->samples_per_frame /
+ param->channel_count;
+
+ /* Set the sound device buffer size and latency */
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) {
+ tmp_buf_size = (rate / 1000) * param->input_latency_ms;
+ } else {
+ tmp_buf_size = (rate / 1000) * PJMEDIA_SND_DEFAULT_REC_LATENCY;
+ }
+
+ stream->param.input_latency_ms = tmp_buf_size / (rate / 1000);
+
+ /* Set our buffer */
+ stream->ca_buf_size = stream->ca_frames * param->channel_count *
+ (param->bits_per_sample/8);
+ stream->ca_buf = (char *)pj_pool_alloc (stream->pool, stream->ca_buf_size);
+
+ TRACE_((THIS_FILE, "bb10_open_capture: ca_frames = %d clock = %d",
+ stream->ca_frames, param->clock_rate));
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: create stream */
+static pj_status_t bb10_factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_strm)
+{
+ struct bb10_factory *af = (struct bb10_factory*)f;
+ pj_status_t status;
+ pj_pool_t* pool;
+ struct bb10_stream* stream;
+
+ pool = pj_pool_create (af->pf, "bb10%p", 1024, 1024, NULL);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ /* Allocate and initialize comon stream data */
+ stream = PJ_POOL_ZALLOC_T (pool, struct bb10_stream);
+ stream->base.op = &bb10_stream_op;
+ stream->pool = pool;
+ stream->af = af;
+ stream->user_data = user_data;
+ stream->pb_cb = play_cb;
+ stream->ca_cb = rec_cb;
+ stream->quit = 0;
+ pj_memcpy(&stream->param, param, sizeof(*param));
+
+ /* Init playback */
+ if (param->dir & PJMEDIA_DIR_PLAYBACK) {
+ status = bb10_open_playback (stream, param);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release (pool);
+ return status;
+ }
+ }
+
+ /* Init capture */
+ if (param->dir & PJMEDIA_DIR_CAPTURE) {
+ status = bb10_open_capture (stream, param);
+ if (status != PJ_SUCCESS) {
+ if (param->dir & PJMEDIA_DIR_PLAYBACK) {
+ close_play_mixer(stream);
+ close_play_pcm(stream);
+ }
+ pj_pool_release (pool);
+ return status;
+ }
+ }
+
+ *p_strm = &stream->base;
+ return PJ_SUCCESS;
+}
+
+
+/* API: get running parameter */
+static pj_status_t bb10_stream_get_param(pjmedia_aud_stream *s,
+ pjmedia_aud_param *pi)
+{
+ struct bb10_stream *stream = (struct bb10_stream*)s;
+
+ PJ_ASSERT_RETURN(s && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &stream->param, sizeof(*pi));
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: get capability */
+static pj_status_t bb10_stream_get_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ void *pval)
+{
+ struct bb10_stream *stream = (struct bb10_stream*)s;
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY &&
+ (stream->param.dir & PJMEDIA_DIR_CAPTURE))
+ {
+ /* Recording latency */
+ *(unsigned*)pval = stream->param.input_latency_ms;
+ return PJ_SUCCESS;
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY &&
+ (stream->param.dir & PJMEDIA_DIR_PLAYBACK))
+ {
+ /* Playback latency */
+ *(unsigned*)pval = stream->param.output_latency_ms;
+ return PJ_SUCCESS;
+ } else {
+ return PJMEDIA_EAUD_INVCAP;
+ }
+}
+
+
+/* API: set capability */
+static pj_status_t bb10_stream_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value)
+{
+ PJ_UNUSED_ARG(strm);
+ PJ_UNUSED_ARG(cap);
+ PJ_UNUSED_ARG(value);
+
+ return PJMEDIA_EAUD_INVCAP;
+}
+
+
+/* API: start stream */
+static pj_status_t bb10_stream_start (pjmedia_aud_stream *s)
+{
+ struct bb10_stream *stream = (struct bb10_stream*)s;
+ pj_status_t status = PJ_SUCCESS;
+
+ stream->quit = 0;
+ if (stream->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ status = pj_thread_create (stream->pool,
+ "bb10sound_playback",
+ pb_thread_func,
+ stream,
+ 0,
+ 0,
+ &stream->pb_thread);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ if (stream->param.dir & PJMEDIA_DIR_CAPTURE) {
+ status = pj_thread_create (stream->pool,
+ "bb10sound_playback",
+ ca_thread_func,
+ stream,
+ 0,
+ 0,
+ &stream->ca_thread);
+ if (status != PJ_SUCCESS) {
+ stream->quit = PJ_TRUE;
+ pj_thread_join(stream->pb_thread);
+ pj_thread_destroy(stream->pb_thread);
+ stream->pb_thread = NULL;
+ }
+ }
+
+ return status;
+}
+
+
+/* API: stop stream */
+static pj_status_t bb10_stream_stop (pjmedia_aud_stream *s)
+{
+ struct bb10_stream *stream = (struct bb10_stream*)s;
+
+ stream->quit = 1;
+ TRACE_((THIS_FILE,"bb10_stream_stop()"));
+
+ if (stream->pb_thread) {
+ pj_thread_join (stream->pb_thread);
+ pj_thread_destroy(stream->pb_thread);
+ stream->pb_thread = NULL;
+ }
+
+ if (stream->ca_thread) {
+ pj_thread_join (stream->ca_thread);
+ pj_thread_destroy(stream->ca_thread);
+ stream->ca_thread = NULL;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t bb10_stream_destroy (pjmedia_aud_stream *s)
+{
+ struct bb10_stream *stream = (struct bb10_stream*)s;
+
+ TRACE_((THIS_FILE,"bb10_stream_destroy()"));
+
+ bb10_stream_stop (s);
+
+ pj_pool_release (stream->pool);
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_AUDIO_DEV_HAS_BB10 */
diff --git a/pjmedia/src/pjmedia-audiodev/coreaudio_dev.c b/pjmedia/src/pjmedia-audiodev/coreaudio_dev.c
new file mode 100644
index 0000000..b0bad03
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/coreaudio_dev.c
@@ -0,0 +1,2107 @@
+/* $Id: coreaudio_dev.c 4082 2012-04-24 13:09:14Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia-audiodev/audiodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_COREAUDIO
+
+#include "TargetConditionals.h"
+#if TARGET_OS_IPHONE
+ #define COREAUDIO_MAC 0
+#else
+ #define COREAUDIO_MAC 1
+#endif
+
+#include <AudioUnit/AudioUnit.h>
+#include <AudioToolbox/AudioConverter.h>
+#if !COREAUDIO_MAC
+ #include <AudioToolbox/AudioServices.h>
+
+ #define AudioDeviceID unsigned
+
+ /**
+ * As in iOS SDK 4 or later, audio route change property listener is
+ * no longer necessary. Just make surethat your application can receive
+ * remote control events by adding the code:
+ * [[UIApplication sharedApplication]
+ * beginReceivingRemoteControlEvents];
+ * Otherwise audio route change (such as headset plug/unplug) will not be
+ * processed while your application is in the background mode.
+ */
+ #define USE_AUDIO_ROUTE_CHANGE_PROP_LISTENER 0
+
+#endif
+
+/* For Mac OS 10.5.x and earlier */
+#if AUDIO_UNIT_VERSION < 1060
+ #define AudioComponent Component
+ #define AudioComponentDescription ComponentDescription
+ #define AudioComponentInstance ComponentInstance
+ #define AudioComponentFindNext FindNextComponent
+ #define AudioComponentInstanceNew OpenAComponent
+ #define AudioComponentInstanceDispose CloseComponent
+#endif
+
+
+#define THIS_FILE "coreaudio_dev.c"
+
+/* coreaudio device info */
+struct coreaudio_dev_info
+{
+ pjmedia_aud_dev_info info;
+ AudioDeviceID dev_id;
+};
+
+/* linked list of streams */
+struct stream_list
+{
+ PJ_DECL_LIST_MEMBER(struct stream_list);
+ struct coreaudio_stream *stream;
+};
+
+/* coreaudio factory */
+struct coreaudio_factory
+{
+ pjmedia_aud_dev_factory base;
+ pj_pool_t *base_pool;
+ pj_pool_t *pool;
+ pj_pool_factory *pf;
+ pj_mutex_t *mutex;
+
+ unsigned dev_count;
+ struct coreaudio_dev_info *dev_info;
+
+ AudioComponent io_comp;
+ struct stream_list streams;
+};
+
+/* Sound stream. */
+struct coreaudio_stream
+{
+ pjmedia_aud_stream base; /**< Base stream */
+ pjmedia_aud_param param; /**< Settings */
+ pj_pool_t *pool; /**< Memory pool. */
+ struct coreaudio_factory *cf;
+ struct stream_list list_entry;
+
+ pjmedia_aud_rec_cb rec_cb; /**< Capture callback. */
+ pjmedia_aud_play_cb play_cb; /**< Playback callback. */
+ void *user_data; /**< Application data. */
+
+ pj_timestamp play_timestamp;
+ pj_timestamp rec_timestamp;
+
+ pj_int16_t *rec_buf;
+ unsigned rec_buf_count;
+ pj_int16_t *play_buf;
+ unsigned play_buf_count;
+
+ pj_bool_t interrupted;
+ pj_bool_t quit_flag;
+ pj_bool_t running;
+
+ pj_bool_t rec_thread_initialized;
+ pj_thread_desc rec_thread_desc;
+ pj_thread_t *rec_thread;
+
+ pj_bool_t play_thread_initialized;
+ pj_thread_desc play_thread_desc;
+ pj_thread_t *play_thread;
+
+ AudioUnit io_units[2];
+ AudioStreamBasicDescription streamFormat;
+ AudioBufferList *audio_buf;
+
+ AudioConverterRef resample;
+ pj_int16_t *resample_buf;
+ void *resample_buf_ptr;
+ unsigned resample_buf_count;
+ unsigned resample_buf_size;
+};
+
+/* Static variable */
+static struct coreaudio_factory *cf_instance = NULL;
+
+/* Prototypes */
+static pj_status_t ca_factory_init(pjmedia_aud_dev_factory *f);
+static pj_status_t ca_factory_destroy(pjmedia_aud_dev_factory *f);
+static pj_status_t ca_factory_refresh(pjmedia_aud_dev_factory *f);
+static unsigned ca_factory_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t ca_factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info);
+static pj_status_t ca_factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param);
+static pj_status_t ca_factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm);
+
+static pj_status_t ca_stream_get_param(pjmedia_aud_stream *strm,
+ pjmedia_aud_param *param);
+static pj_status_t ca_stream_get_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ void *value);
+static pj_status_t ca_stream_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value);
+static pj_status_t ca_stream_start(pjmedia_aud_stream *strm);
+static pj_status_t ca_stream_stop(pjmedia_aud_stream *strm);
+static pj_status_t ca_stream_destroy(pjmedia_aud_stream *strm);
+static pj_status_t create_audio_unit(AudioComponent io_comp,
+ AudioDeviceID dev_id,
+ pjmedia_dir dir,
+ struct coreaudio_stream *strm,
+ AudioUnit *io_unit);
+#if !COREAUDIO_MAC
+static void interruptionListener(void *inClientData, UInt32 inInterruption);
+static void propListener(void * inClientData,
+ AudioSessionPropertyID inID,
+ UInt32 inDataSize,
+ const void * inData);
+#endif
+
+/* Operations */
+static pjmedia_aud_dev_factory_op factory_op =
+{
+ &ca_factory_init,
+ &ca_factory_destroy,
+ &ca_factory_get_dev_count,
+ &ca_factory_get_dev_info,
+ &ca_factory_default_param,
+ &ca_factory_create_stream,
+ &ca_factory_refresh
+};
+
+static pjmedia_aud_stream_op stream_op =
+{
+ &ca_stream_get_param,
+ &ca_stream_get_cap,
+ &ca_stream_set_cap,
+ &ca_stream_start,
+ &ca_stream_stop,
+ &ca_stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init coreaudio audio driver.
+ */
+pjmedia_aud_dev_factory* pjmedia_coreaudio_factory(pj_pool_factory *pf)
+{
+ struct coreaudio_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "core audio base", 1000, 1000, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct coreaudio_factory);
+ f->pf = pf;
+ f->base_pool = pool;
+ f->base.op = &factory_op;
+
+ return &f->base;
+}
+
+
+/* API: init factory */
+static pj_status_t ca_factory_init(pjmedia_aud_dev_factory *f)
+{
+ struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
+ AudioComponentDescription desc;
+ pj_status_t status;
+#if !COREAUDIO_MAC
+ unsigned i;
+ OSStatus ostatus;
+#endif
+
+ pj_list_init(&cf->streams);
+ status = pj_mutex_create_recursive(cf->base_pool,
+ "coreaudio",
+ &cf->mutex);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ desc.componentType = kAudioUnitType_Output;
+#if COREAUDIO_MAC
+ desc.componentSubType = kAudioUnitSubType_HALOutput;
+#else
+ desc.componentSubType = kAudioUnitSubType_RemoteIO;
+#endif
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+
+ cf->io_comp = AudioComponentFindNext(NULL, &desc);
+ if (cf->io_comp == NULL)
+ return PJMEDIA_EAUD_INIT; // cannot find IO unit;
+
+ status = ca_factory_refresh(f);
+ if (status != PJ_SUCCESS)
+ return status;
+
+#if !COREAUDIO_MAC
+ cf->pool = pj_pool_create(cf->pf, "core audio", 1000, 1000, NULL);
+ cf->dev_count = 1;
+ cf->dev_info = (struct coreaudio_dev_info*)
+ pj_pool_calloc(cf->pool, cf->dev_count,
+ sizeof(struct coreaudio_dev_info));
+ for (i = 0; i < cf->dev_count; i++) {
+ struct coreaudio_dev_info *cdi;
+
+ cdi = &cf->dev_info[i];
+ pj_bzero(cdi, sizeof(*cdi));
+ cdi->dev_id = 0;
+ strcpy(cdi->info.name, "iPhone IO device");
+ strcpy(cdi->info.driver, "apple");
+ cdi->info.input_count = 1;
+ cdi->info.output_count = 1;
+ cdi->info.default_samples_per_sec = 8000;
+
+ /* Set the device capabilities here */
+ cdi->info.caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING |
+ PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE |
+ PJMEDIA_AUD_DEV_CAP_EC;
+ cdi->info.routes = PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER |
+ PJMEDIA_AUD_DEV_ROUTE_EARPIECE |
+ PJMEDIA_AUD_DEV_ROUTE_BLUETOOTH;
+
+ PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (in=%d, out=%d) %dHz",
+ i,
+ cdi->info.name,
+ cdi->info.input_count,
+ cdi->info.output_count,
+ cdi->info.default_samples_per_sec));
+ }
+
+ /* Initialize the Audio Session */
+ ostatus = AudioSessionInitialize(NULL, NULL, interruptionListener, NULL);
+ if (ostatus != kAudioSessionNoError) {
+ PJ_LOG(4, (THIS_FILE,
+ "Warning: cannot initialize audio session services (%i)",
+ ostatus));
+ }
+
+ /* Listen for audio routing change notifications. */
+#if USE_AUDIO_ROUTE_CHANGE_PROP_LISTENER != 0
+ ostatus = AudioSessionAddPropertyListener(
+ kAudioSessionProperty_AudioRouteChange,
+ propListener, cf);
+ if (ostatus != kAudioSessionNoError) {
+ PJ_LOG(4, (THIS_FILE,
+ "Warning: cannot listen for audio route change "
+ "notifications (%i)", ostatus));
+ }
+#endif
+
+ cf_instance = cf;
+#endif
+
+ PJ_LOG(4, (THIS_FILE, "core audio initialized"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t ca_factory_destroy(pjmedia_aud_dev_factory *f)
+{
+ struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
+ pj_pool_t *pool;
+
+ pj_assert(cf);
+ pj_assert(cf->base_pool);
+ pj_assert(pj_list_empty(&cf->streams));
+
+#if !COREAUDIO_MAC
+#if USE_AUDIO_ROUTE_CHANGE_PROP_LISTENER != 0
+ AudioSessionRemovePropertyListenerWithUserData(
+ kAudioSessionProperty_AudioRouteChange, propListener, cf);
+#endif
+#endif
+
+ if (cf->pool) {
+ pj_pool_release(cf->pool);
+ cf->pool = NULL;
+ }
+
+ if (cf->mutex) {
+ pj_mutex_lock(cf->mutex);
+ cf_instance = NULL;
+ pj_mutex_unlock(cf->mutex);
+ pj_mutex_destroy(cf->mutex);
+ cf->mutex = NULL;
+ }
+
+ pool = cf->base_pool;
+ cf->base_pool = NULL;
+ pj_pool_release(pool);
+
+ return PJ_SUCCESS;
+}
+
+/* API: refresh the device list */
+static pj_status_t ca_factory_refresh(pjmedia_aud_dev_factory *f)
+{
+#if !COREAUDIO_MAC
+ /* iPhone doesn't support refreshing the device list */
+ PJ_UNUSED_ARG(f);
+ return PJ_SUCCESS;
+#else
+ struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
+ unsigned i;
+ unsigned dev_count;
+ AudioObjectPropertyAddress addr;
+ AudioDeviceID *dev_ids;
+ UInt32 buf_size, dev_size, size = sizeof(AudioDeviceID);
+ AudioBufferList *buf = NULL;
+ OSStatus ostatus;
+
+ if (cf->pool != NULL) {
+ pj_pool_release(cf->pool);
+ cf->pool = NULL;
+ }
+
+ cf->dev_count = 0;
+ cf->pool = pj_pool_create(cf->pf, "core audio", 1000, 1000, NULL);
+
+ /* Find out how many audio devices there are */
+ addr.mSelector = kAudioHardwarePropertyDevices;
+ addr.mScope = kAudioObjectPropertyScopeGlobal;
+ addr.mElement = kAudioObjectPropertyElementMaster;
+ ostatus = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr,
+ 0, NULL, &dev_size);
+ if (ostatus != noErr) {
+ dev_size = 0;
+ }
+
+ /* Calculate the number of audio devices available */
+ dev_count = dev_size / size;
+ if (dev_count==0) {
+ PJ_LOG(4,(THIS_FILE, "core audio found no sound devices"));
+ /* Enabling this will cause pjsua-lib initialization to fail when
+ * there is no sound device installed in the system, even when pjsua
+ * has been run with --null-audio. Moreover, it might be better to
+ * think that the core audio backend initialization is successful,
+ * regardless there is no audio device installed, as later application
+ * can check it using get_dev_count().
+ return PJMEDIA_EAUD_NODEV;
+ */
+ return PJ_SUCCESS;
+ }
+ PJ_LOG(4, (THIS_FILE, "core audio detected %d devices",
+ dev_count));
+
+ /* Get all the audio device IDs */
+ dev_ids = (AudioDeviceID *)pj_pool_calloc(cf->pool, dev_size, size);
+ if (!dev_ids)
+ return PJ_ENOMEM;
+ pj_bzero(dev_ids, dev_count);
+ ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
+ 0, NULL,
+ &dev_size, (void *)dev_ids);
+ if (ostatus != noErr ) {
+ /* This should not happen since we have successfully retrieved
+ * the property data size before
+ */
+ return PJMEDIA_EAUD_INIT;
+ }
+
+ if (dev_size > 1) {
+ AudioDeviceID dev_id = kAudioObjectUnknown;
+ unsigned idx = 0;
+
+ /* Find default audio input device */
+ addr.mSelector = kAudioHardwarePropertyDefaultInputDevice;
+ addr.mScope = kAudioObjectPropertyScopeGlobal;
+ addr.mElement = kAudioObjectPropertyElementMaster;
+ size = sizeof(dev_id);
+
+ ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &addr, 0, NULL,
+ &size, (void *)&dev_id);
+ if (ostatus != noErr && dev_id != dev_ids[idx]) {
+ AudioDeviceID temp_id = dev_ids[idx];
+
+ for (i = idx + 1; i < dev_size; i++) {
+ if (dev_ids[i] == dev_id) {
+ dev_ids[idx++] = dev_id;
+ dev_ids[i] = temp_id;
+ break;
+ }
+ }
+ }
+
+ /* Find default audio output device */
+ addr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
+ ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &addr, 0, NULL,
+ &size, (void *)&dev_id);
+ if (ostatus != noErr && dev_id != dev_ids[idx]) {
+ AudioDeviceID temp_id = dev_ids[idx];
+
+ for (i = idx + 1; i < dev_size; i++) {
+ if (dev_ids[i] == dev_id) {
+ dev_ids[idx] = dev_id;
+ dev_ids[i] = temp_id;
+ break;
+ }
+ }
+ }
+ }
+
+ /* Build the devices' info */
+ cf->dev_info = (struct coreaudio_dev_info*)
+ pj_pool_calloc(cf->pool, dev_count,
+ sizeof(struct coreaudio_dev_info));
+ buf_size = 0;
+ for (i = 0; i < dev_count; i++) {
+ struct coreaudio_dev_info *cdi;
+ Float64 sampleRate;
+
+ cdi = &cf->dev_info[i];
+ pj_bzero(cdi, sizeof(*cdi));
+ cdi->dev_id = dev_ids[i];
+
+ /* Get device name */
+ addr.mSelector = kAudioDevicePropertyDeviceName;
+ addr.mScope = kAudioObjectPropertyScopeGlobal;
+ addr.mElement = kAudioObjectPropertyElementMaster;
+ size = sizeof(cdi->info.name);
+ AudioObjectGetPropertyData(cdi->dev_id, &addr,
+ 0, NULL,
+ &size, (void *)cdi->info.name);
+
+ strcpy(cdi->info.driver, "core audio");
+
+ /* Get the number of input channels */
+ addr.mSelector = kAudioDevicePropertyStreamConfiguration;
+ addr.mScope = kAudioDevicePropertyScopeInput;
+ size = 0;
+ ostatus = AudioObjectGetPropertyDataSize(cdi->dev_id, &addr,
+ 0, NULL, &size);
+ if (ostatus == noErr && size > 0) {
+
+ if (size > buf_size) {
+ buf = pj_pool_alloc(cf->pool, size);
+ buf_size = size;
+ }
+ if (buf) {
+ UInt32 idx;
+
+ /* Get the input stream configuration */
+ ostatus = AudioObjectGetPropertyData(cdi->dev_id, &addr,
+ 0, NULL,
+ &size, buf);
+ if (ostatus == noErr) {
+ /* Count the total number of input channels in
+ * the stream
+ */
+ for (idx = 0; idx < buf->mNumberBuffers; idx++) {
+ cdi->info.input_count +=
+ buf->mBuffers[idx].mNumberChannels;
+ }
+ }
+ }
+ }
+
+ /* Get the number of output channels */
+ addr.mScope = kAudioDevicePropertyScopeOutput;
+ size = 0;
+ ostatus = AudioObjectGetPropertyDataSize(cdi->dev_id, &addr,
+ 0, NULL, &size);
+ if (ostatus == noErr && size > 0) {
+
+ if (size > buf_size) {
+ buf = pj_pool_alloc(cf->pool, size);
+ buf_size = size;
+ }
+ if (buf) {
+ UInt32 idx;
+
+ /* Get the output stream configuration */
+ ostatus = AudioObjectGetPropertyData(cdi->dev_id, &addr,
+ 0, NULL,
+ &size, buf);
+ if (ostatus == noErr) {
+ /* Count the total number of output channels in
+ * the stream
+ */
+ for (idx = 0; idx < buf->mNumberBuffers; idx++) {
+ cdi->info.output_count +=
+ buf->mBuffers[idx].mNumberChannels;
+ }
+ }
+ }
+ }
+
+ /* Get default sample rate */
+ addr.mSelector = kAudioDevicePropertyNominalSampleRate;
+ addr.mScope = kAudioObjectPropertyScopeGlobal;
+ size = sizeof(Float64);
+ ostatus = AudioObjectGetPropertyData (cdi->dev_id, &addr,
+ 0, NULL,
+ &size, &sampleRate);
+ cdi->info.default_samples_per_sec = (ostatus == noErr ?
+ sampleRate:
+ 16000);
+
+ /* Set device capabilities here */
+ if (cdi->info.input_count > 0) {
+ cdi->info.caps |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+ }
+ if (cdi->info.output_count > 0) {
+ cdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+ addr.mSelector = kAudioDevicePropertyVolumeScalar;
+ addr.mScope = kAudioDevicePropertyScopeOutput;
+ if (AudioObjectHasProperty(cdi->dev_id, &addr)) {
+ cdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+ }
+ }
+
+ cf->dev_count++;
+
+ PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (in=%d, out=%d) %dHz",
+ i,
+ cdi->info.name,
+ cdi->info.input_count,
+ cdi->info.output_count,
+ cdi->info.default_samples_per_sec));
+ }
+
+ return PJ_SUCCESS;
+#endif
+}
+
+/* API: get number of devices */
+static unsigned ca_factory_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+ struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
+ return cf->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t ca_factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info)
+{
+ struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
+
+ PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EAUD_INVDEV);
+
+ pj_memcpy(info, &cf->dev_info[index].info, sizeof(*info));
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t ca_factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param)
+{
+ struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
+ struct coreaudio_dev_info *di = &cf->dev_info[index];
+
+ PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EAUD_INVDEV);
+
+ pj_bzero(param, sizeof(*param));
+ if (di->info.input_count && di->info.output_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param->rec_id = index;
+ param->play_id = index;
+ } else if (di->info.input_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE;
+ param->rec_id = index;
+ param->play_id = PJMEDIA_AUD_INVALID_DEV;
+ } else if (di->info.output_count) {
+ param->dir = PJMEDIA_DIR_PLAYBACK;
+ param->play_id = index;
+ param->rec_id = PJMEDIA_AUD_INVALID_DEV;
+ } else {
+ return PJMEDIA_EAUD_INVDEV;
+ }
+
+ /* Set the mandatory settings here */
+ param->clock_rate = di->info.default_samples_per_sec;
+ param->channel_count = 1;
+ param->samples_per_frame = di->info.default_samples_per_sec * 20 / 1000;
+ param->bits_per_sample = 16;
+
+ /* Set the param for device capabilities here */
+ param->flags = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+ param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+ param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+
+ return PJ_SUCCESS;
+}
+
+OSStatus resampleProc(AudioConverterRef inAudioConverter,
+ UInt32 *ioNumberDataPackets,
+ AudioBufferList *ioData,
+ AudioStreamPacketDescription **outDataPacketDescription,
+ void *inUserData)
+{
+ struct coreaudio_stream *strm = (struct coreaudio_stream*)inUserData;
+
+ if (*ioNumberDataPackets > strm->resample_buf_size)
+ *ioNumberDataPackets = strm->resample_buf_size;
+
+ ioData->mNumberBuffers = 1;
+ ioData->mBuffers[0].mNumberChannels = strm->streamFormat.mChannelsPerFrame;
+ ioData->mBuffers[0].mData = strm->resample_buf_ptr;
+ ioData->mBuffers[0].mDataByteSize = *ioNumberDataPackets *
+ strm->streamFormat.mChannelsPerFrame *
+ strm->param.bits_per_sample >> 3;
+
+ return noErr;
+}
+
+static OSStatus resample_callback(void *inRefCon,
+ AudioUnitRenderActionFlags *ioActionFlags,
+ const AudioTimeStamp *inTimeStamp,
+ UInt32 inBusNumber,
+ UInt32 inNumberFrames,
+ AudioBufferList *ioData)
+{
+ struct coreaudio_stream *strm = (struct coreaudio_stream*)inRefCon;
+ OSStatus ostatus;
+ pj_status_t status = 0;
+ unsigned nsamples;
+ AudioBufferList *buf = strm->audio_buf;
+ pj_int16_t *input;
+ UInt32 resampleSize;
+
+ pj_assert(!strm->quit_flag);
+
+ /* Known cases of callback's thread:
+ * - The thread may be changed in the middle of a session
+ * it happens when plugging/unplugging headphone.
+ * - The same thread may be reused in consecutive sessions. The first
+ * session will leave TLS set, but release the TLS data address,
+ * so the second session must re-register the callback's thread.
+ */
+ if (strm->rec_thread_initialized == 0 || !pj_thread_is_registered())
+ {
+ pj_bzero(strm->rec_thread_desc, sizeof(pj_thread_desc));
+ status = pj_thread_register("ca_rec", strm->rec_thread_desc,
+ &strm->rec_thread);
+ strm->rec_thread_initialized = 1;
+ PJ_LOG(5,(THIS_FILE, "Recorder thread started, (%i frames)",
+ inNumberFrames));
+ }
+
+ buf->mBuffers[0].mData = NULL;
+ buf->mBuffers[0].mDataByteSize = inNumberFrames *
+ strm->streamFormat.mChannelsPerFrame;
+ /* Render the unit to get input data */
+ ostatus = AudioUnitRender(strm->io_units[0],
+ ioActionFlags,
+ inTimeStamp,
+ inBusNumber,
+ inNumberFrames,
+ buf);
+
+ if (ostatus != noErr) {
+ PJ_LOG(5, (THIS_FILE, "Core audio unit render error %i", ostatus));
+ goto on_break;
+ }
+ input = (pj_int16_t *)buf->mBuffers[0].mData;
+
+ resampleSize = strm->resample_buf_size;
+ nsamples = inNumberFrames * strm->param.channel_count +
+ strm->resample_buf_count;
+
+ if (nsamples >= resampleSize) {
+ pjmedia_frame frame;
+ UInt32 resampleOutput = strm->param.samples_per_frame /
+ strm->streamFormat.mChannelsPerFrame;
+ AudioBufferList ab;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = (void*) strm->rec_buf;
+ frame.size = strm->param.samples_per_frame *
+ strm->param.bits_per_sample >> 3;
+ frame.bit_info = 0;
+
+ ab.mNumberBuffers = 1;
+ ab.mBuffers[0].mNumberChannels = strm->streamFormat.mChannelsPerFrame;
+ ab.mBuffers[0].mData = strm->rec_buf;
+ ab.mBuffers[0].mDataByteSize = frame.size;
+
+ /* If buffer is not empty, combine the buffer with the just incoming
+ * samples, then call put_frame.
+ */
+ if (strm->resample_buf_count) {
+ unsigned chunk_count = resampleSize - strm->resample_buf_count;
+ pjmedia_copy_samples(strm->resample_buf + strm->resample_buf_count,
+ input, chunk_count);
+
+ /* Do the resample */
+
+ strm->resample_buf_ptr = strm->resample_buf;
+ ostatus = AudioConverterFillComplexBuffer(strm->resample,
+ resampleProc,
+ strm,
+ &resampleOutput,
+ &ab,
+ NULL);
+ if (ostatus != noErr) {
+ goto on_break;
+ }
+ frame.timestamp.u64 = strm->rec_timestamp.u64;
+
+ status = (*strm->rec_cb)(strm->user_data, &frame);
+
+ input = input + chunk_count;
+ nsamples -= resampleSize;
+ strm->resample_buf_count = 0;
+ strm->rec_timestamp.u64 += strm->param.samples_per_frame /
+ strm->param.channel_count;
+ }
+
+
+ /* Give all frames we have */
+ while (nsamples >= resampleSize && status == 0) {
+ frame.timestamp.u64 = strm->rec_timestamp.u64;
+
+ /* Do the resample */
+ strm->resample_buf_ptr = input;
+ ab.mBuffers[0].mDataByteSize = frame.size;
+ resampleOutput = strm->param.samples_per_frame /
+ strm->streamFormat.mChannelsPerFrame;
+ ostatus = AudioConverterFillComplexBuffer(strm->resample,
+ resampleProc,
+ strm,
+ &resampleOutput,
+ &ab,
+ NULL);
+ if (ostatus != noErr) {
+ goto on_break;
+ }
+
+ status = (*strm->rec_cb)(strm->user_data, &frame);
+
+ input = (pj_int16_t*) input + resampleSize;
+ nsamples -= resampleSize;
+ strm->rec_timestamp.u64 += strm->param.samples_per_frame /
+ strm->param.channel_count;
+ }
+
+ /* Store the remaining samples into the buffer */
+ if (nsamples && status == 0) {
+ strm->resample_buf_count = nsamples;
+ pjmedia_copy_samples(strm->resample_buf, input,
+ nsamples);
+ }
+
+ } else {
+ /* Not enough samples, let's just store them in the buffer */
+ pjmedia_copy_samples(strm->resample_buf + strm->resample_buf_count,
+ input,
+ inNumberFrames * strm->param.channel_count);
+ strm->resample_buf_count += inNumberFrames *
+ strm->param.channel_count;
+ }
+
+ return noErr;
+
+on_break:
+ return -1;
+}
+
+static OSStatus input_callback(void *inRefCon,
+ AudioUnitRenderActionFlags *ioActionFlags,
+ const AudioTimeStamp *inTimeStamp,
+ UInt32 inBusNumber,
+ UInt32 inNumberFrames,
+ AudioBufferList *ioData)
+{
+ struct coreaudio_stream *strm = (struct coreaudio_stream*)inRefCon;
+ OSStatus ostatus;
+ pj_status_t status = 0;
+ unsigned nsamples;
+ AudioBufferList *buf = strm->audio_buf;
+ pj_int16_t *input;
+
+ pj_assert(!strm->quit_flag);
+
+ /* Known cases of callback's thread:
+ * - The thread may be changed in the middle of a session
+ * it happens when plugging/unplugging headphone.
+ * - The same thread may be reused in consecutive sessions. The first
+ * session will leave TLS set, but release the TLS data address,
+ * so the second session must re-register the callback's thread.
+ */
+ if (strm->rec_thread_initialized == 0 || !pj_thread_is_registered())
+ {
+ pj_bzero(strm->rec_thread_desc, sizeof(pj_thread_desc));
+ status = pj_thread_register("ca_rec", strm->rec_thread_desc,
+ &strm->rec_thread);
+ strm->rec_thread_initialized = 1;
+ PJ_LOG(5,(THIS_FILE, "Recorder thread started, (%i frames)",
+ inNumberFrames));
+ }
+
+ buf->mBuffers[0].mData = NULL;
+ buf->mBuffers[0].mDataByteSize = inNumberFrames *
+ strm->streamFormat.mChannelsPerFrame;
+ /* Render the unit to get input data */
+ ostatus = AudioUnitRender(strm->io_units[0],
+ ioActionFlags,
+ inTimeStamp,
+ inBusNumber,
+ inNumberFrames,
+ buf);
+
+ if (ostatus != noErr) {
+ PJ_LOG(5, (THIS_FILE, "Core audio unit render error %i", ostatus));
+ goto on_break;
+ }
+ input = (pj_int16_t *)buf->mBuffers[0].mData;
+
+ /* Calculate number of samples we've got */
+ nsamples = inNumberFrames * strm->param.channel_count +
+ strm->rec_buf_count;
+ if (nsamples >= strm->param.samples_per_frame) {
+ pjmedia_frame frame;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.size = strm->param.samples_per_frame *
+ strm->param.bits_per_sample >> 3;
+ frame.bit_info = 0;
+
+ /* If buffer is not empty, combine the buffer with the just incoming
+ * samples, then call put_frame.
+ */
+ if (strm->rec_buf_count) {
+ unsigned chunk_count = 0;
+
+ chunk_count = strm->param.samples_per_frame - strm->rec_buf_count;
+ pjmedia_copy_samples(strm->rec_buf + strm->rec_buf_count,
+ input, chunk_count);
+
+ frame.buf = (void*) strm->rec_buf;
+ frame.timestamp.u64 = strm->rec_timestamp.u64;
+
+ status = (*strm->rec_cb)(strm->user_data, &frame);
+
+ input = input + chunk_count;
+ nsamples -= strm->param.samples_per_frame;
+ strm->rec_buf_count = 0;
+ strm->rec_timestamp.u64 += strm->param.samples_per_frame /
+ strm->param.channel_count;
+ }
+
+ /* Give all frames we have */
+ while (nsamples >= strm->param.samples_per_frame && status == 0) {
+ frame.buf = (void*) input;
+ frame.timestamp.u64 = strm->rec_timestamp.u64;
+
+ status = (*strm->rec_cb)(strm->user_data, &frame);
+
+ input = (pj_int16_t*) input + strm->param.samples_per_frame;
+ nsamples -= strm->param.samples_per_frame;
+ strm->rec_timestamp.u64 += strm->param.samples_per_frame /
+ strm->param.channel_count;
+ }
+
+ /* Store the remaining samples into the buffer */
+ if (nsamples && status == 0) {
+ strm->rec_buf_count = nsamples;
+ pjmedia_copy_samples(strm->rec_buf, input,
+ nsamples);
+ }
+
+ } else {
+ /* Not enough samples, let's just store them in the buffer */
+ pjmedia_copy_samples(strm->rec_buf + strm->rec_buf_count,
+ input,
+ inNumberFrames * strm->param.channel_count);
+ strm->rec_buf_count += inNumberFrames * strm->param.channel_count;
+ }
+
+ return noErr;
+
+on_break:
+ return -1;
+}
+
+static OSStatus output_renderer(void *inRefCon,
+ AudioUnitRenderActionFlags *ioActionFlags,
+ const AudioTimeStamp *inTimeStamp,
+ UInt32 inBusNumber,
+ UInt32 inNumberFrames,
+ AudioBufferList *ioData)
+{
+ struct coreaudio_stream *stream = (struct coreaudio_stream*)inRefCon;
+ pj_status_t status = 0;
+ unsigned nsamples_req = inNumberFrames * stream->param.channel_count;
+ pj_int16_t *output = ioData->mBuffers[0].mData;
+
+ pj_assert(!stream->quit_flag);
+
+ /* Known cases of callback's thread:
+ * - The thread may be changed in the middle of a session
+ * it happens when plugging/unplugging headphone.
+ * - The same thread may be reused in consecutive sessions. The first
+ * session will leave TLS set, but release the TLS data address,
+ * so the second session must re-register the callback's thread.
+ */
+ if (stream->play_thread_initialized == 0 || !pj_thread_is_registered())
+ {
+ pj_bzero(stream->play_thread_desc, sizeof(pj_thread_desc));
+ status = pj_thread_register("coreaudio", stream->play_thread_desc,
+ &stream->play_thread);
+ stream->play_thread_initialized = 1;
+ PJ_LOG(5,(THIS_FILE, "Player thread started, (%i frames)",
+ inNumberFrames));
+ }
+
+
+ /* Check if any buffered samples */
+ if (stream->play_buf_count) {
+ /* samples buffered >= requested by sound device */
+ if (stream->play_buf_count >= nsamples_req) {
+ pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
+ nsamples_req);
+ stream->play_buf_count -= nsamples_req;
+ pjmedia_move_samples(stream->play_buf,
+ stream->play_buf + nsamples_req,
+ stream->play_buf_count);
+ nsamples_req = 0;
+
+ return noErr;
+ }
+
+ /* samples buffered < requested by sound device */
+ pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
+ stream->play_buf_count);
+ nsamples_req -= stream->play_buf_count;
+ output = (pj_int16_t*)output + stream->play_buf_count;
+ stream->play_buf_count = 0;
+ }
+
+ /* Fill output buffer as requested */
+ while (nsamples_req && status == 0) {
+ pjmedia_frame frame;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.size = stream->param.samples_per_frame *
+ stream->param.bits_per_sample >> 3;
+ frame.timestamp.u64 = stream->play_timestamp.u64;
+ frame.bit_info = 0;
+
+ if (nsamples_req >= stream->param.samples_per_frame) {
+ frame.buf = output;
+ status = (*stream->play_cb)(stream->user_data, &frame);
+ if (status != PJ_SUCCESS)
+ goto on_break;
+
+ if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
+ pj_bzero(frame.buf, frame.size);
+
+ nsamples_req -= stream->param.samples_per_frame;
+ output = (pj_int16_t*)output + stream->param.samples_per_frame;
+ } else {
+ frame.buf = stream->play_buf;
+ status = (*stream->play_cb)(stream->user_data, &frame);
+ if (status != PJ_SUCCESS)
+ goto on_break;
+
+ if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
+ pj_bzero(frame.buf, frame.size);
+
+ pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
+ nsamples_req);
+ stream->play_buf_count = stream->param.samples_per_frame -
+ nsamples_req;
+ pjmedia_move_samples(stream->play_buf,
+ stream->play_buf+nsamples_req,
+ stream->play_buf_count);
+ nsamples_req = 0;
+ }
+
+ stream->play_timestamp.u64 += stream->param.samples_per_frame /
+ stream->param.channel_count;
+ }
+
+ return noErr;
+
+on_break:
+ return -1;
+}
+
+#if !COREAUDIO_MAC
+static void propListener(void *inClientData,
+ AudioSessionPropertyID inID,
+ UInt32 inDataSize,
+ const void * inData)
+{
+ struct coreaudio_factory *cf = (struct coreaudio_factory*)inClientData;
+ struct stream_list *it, *itBegin;
+ CFDictionaryRef routeDictionary;
+ CFNumberRef reason;
+ SInt32 reasonVal;
+ pj_assert(cf);
+
+ if (inID != kAudioSessionProperty_AudioRouteChange)
+ return;
+
+ routeDictionary = (CFDictionaryRef)inData;
+ reason = (CFNumberRef)
+ CFDictionaryGetValue(
+ routeDictionary,
+ CFSTR(kAudioSession_AudioRouteChangeKey_Reason));
+ CFNumberGetValue(reason, kCFNumberSInt32Type, &reasonVal);
+
+ if (reasonVal != kAudioSessionRouteChangeReason_OldDeviceUnavailable) {
+ PJ_LOG(3, (THIS_FILE, "ignoring audio route change..."));
+ return;
+ }
+
+ PJ_LOG(3, (THIS_FILE, "audio route changed"));
+
+ pj_mutex_lock(cf->mutex);
+ itBegin = &cf->streams;
+ for (it = itBegin->next; it != itBegin; it = it->next) {
+ if (it->stream->interrupted)
+ continue;
+
+ /*
+ status = ca_stream_stop((pjmedia_aud_stream *)it->stream);
+ status = ca_stream_start((pjmedia_aud_stream *)it->stream);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE,
+ "Error: failed to restart the audio unit (%i)",
+ status));
+ continue;
+ }
+ PJ_LOG(3, (THIS_FILE, "core audio unit successfully restarted"));
+ */
+ }
+ pj_mutex_unlock(cf->mutex);
+}
+
+static void interruptionListener(void *inClientData, UInt32 inInterruption)
+{
+ struct stream_list *it, *itBegin;
+ pj_status_t status;
+ pj_thread_desc thread_desc;
+ pj_thread_t *thread;
+
+ /* Register the thread with PJLIB, this is must for any external threads
+ * which need to use the PJLIB framework.
+ */
+ if (!pj_thread_is_registered()) {
+ pj_bzero(thread_desc, sizeof(pj_thread_desc));
+ status = pj_thread_register("intListener", thread_desc, &thread);
+ }
+
+ PJ_LOG(3, (THIS_FILE, "Session interrupted! --- %s ---",
+ inInterruption == kAudioSessionBeginInterruption ?
+ "Begin Interruption" : "End Interruption"));
+
+ if (!cf_instance)
+ return;
+
+ pj_mutex_lock(cf_instance->mutex);
+ itBegin = &cf_instance->streams;
+ for (it = itBegin->next; it != itBegin; it = it->next) {
+ if (inInterruption == kAudioSessionEndInterruption &&
+ it->stream->interrupted == PJ_TRUE)
+ {
+ UInt32 audioCategory;
+ OSStatus ostatus;
+
+ /* Make sure that your application can receive remote control
+ * events by adding the code:
+ * [[UIApplication sharedApplication]
+ * beginReceivingRemoteControlEvents];
+ * Otherwise audio unit will fail to restart while your
+ * application is in the background mode.
+ */
+ /* Make sure we set the correct audio category before restarting */
+ audioCategory = kAudioSessionCategory_PlayAndRecord;
+ ostatus = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
+ sizeof(audioCategory),
+ &audioCategory);
+ if (ostatus != kAudioSessionNoError) {
+ PJ_LOG(4, (THIS_FILE,
+ "Warning: cannot set the audio session category (%i)",
+ ostatus));
+ }
+
+ /* Restart the stream */
+ status = ca_stream_start((pjmedia_aud_stream*)it->stream);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE,
+ "Error: failed to restart the audio unit (%i)",
+ status));
+ continue;
+ }
+ PJ_LOG(3, (THIS_FILE, "core audio unit successfully resumed"
+ " after interruption"));
+ } else if (inInterruption == kAudioSessionBeginInterruption &&
+ it->stream->running == PJ_TRUE)
+ {
+ status = ca_stream_stop((pjmedia_aud_stream*)it->stream);
+ it->stream->interrupted = PJ_TRUE;
+ }
+ }
+ pj_mutex_unlock(cf_instance->mutex);
+}
+
+#endif
+
+#if COREAUDIO_MAC
+/* Internal: create audio converter for resampling the recorder device */
+static pj_status_t create_audio_resample(struct coreaudio_stream *strm,
+ AudioStreamBasicDescription *desc)
+{
+ OSStatus ostatus;
+
+ pj_assert(strm->streamFormat.mSampleRate != desc->mSampleRate);
+ pj_assert(NULL == strm->resample);
+ pj_assert(NULL == strm->resample_buf);
+
+ /* Create the audio converter */
+ ostatus = AudioConverterNew(desc, &strm->streamFormat, &strm->resample);
+ if (ostatus != noErr) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+ /*
+ * Allocate the buffer required to hold enough input data
+ */
+ strm->resample_buf_size = (unsigned)(desc->mSampleRate *
+ strm->param.samples_per_frame /
+ strm->param.clock_rate);
+ strm->resample_buf = (pj_int16_t*)
+ pj_pool_alloc(strm->pool,
+ strm->resample_buf_size *
+ strm->param.bits_per_sample >> 3);
+ if (!strm->resample_buf)
+ return PJ_ENOMEM;
+ strm->resample_buf_count = 0;
+
+ return PJ_SUCCESS;
+}
+#endif
+
+/* Internal: create audio unit for recorder/playback device */
+static pj_status_t create_audio_unit(AudioComponent io_comp,
+ AudioDeviceID dev_id,
+ pjmedia_dir dir,
+ struct coreaudio_stream *strm,
+ AudioUnit *io_unit)
+{
+ OSStatus ostatus;
+#if !COREAUDIO_MAC
+ UInt32 audioCategory = kAudioSessionCategory_PlayAndRecord;
+ /* We want to be able to open playback and recording streams */
+ ostatus = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
+ sizeof(audioCategory),
+ &audioCategory);
+ if (ostatus != kAudioSessionNoError) {
+ PJ_LOG(4, (THIS_FILE,
+ "Warning: cannot set the audio session category (%i)",
+ ostatus));
+ }
+#endif
+
+ /* Create an audio unit to interface with the device */
+ ostatus = AudioComponentInstanceNew(io_comp, io_unit);
+ if (ostatus != noErr) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+ /* Set audio unit's properties for capture device */
+ if (dir & PJMEDIA_DIR_CAPTURE) {
+ UInt32 enable = 1;
+
+ /* Enable input */
+ ostatus = AudioUnitSetProperty(*io_unit,
+ kAudioOutputUnitProperty_EnableIO,
+ kAudioUnitScope_Input,
+ 1,
+ &enable,
+ sizeof(enable));
+ if (ostatus != noErr) {
+ PJ_LOG(4, (THIS_FILE,
+ "Warning: cannot enable IO of capture device %d",
+ dev_id));
+ }
+
+ /* Disable output */
+ if (!(dir & PJMEDIA_DIR_PLAYBACK)) {
+ enable = 0;
+ ostatus = AudioUnitSetProperty(*io_unit,
+ kAudioOutputUnitProperty_EnableIO,
+ kAudioUnitScope_Output,
+ 0,
+ &enable,
+ sizeof(enable));
+ if (ostatus != noErr) {
+ PJ_LOG(4, (THIS_FILE,
+ "Warning: cannot disable IO of capture device %d",
+ dev_id));
+ }
+ }
+ }
+
+ /* Set audio unit's properties for playback device */
+ if (dir & PJMEDIA_DIR_PLAYBACK) {
+ UInt32 enable = 1;
+
+ /* Enable output */
+ ostatus = AudioUnitSetProperty(*io_unit,
+ kAudioOutputUnitProperty_EnableIO,
+ kAudioUnitScope_Output,
+ 0,
+ &enable,
+ sizeof(enable));
+ if (ostatus != noErr) {
+ PJ_LOG(4, (THIS_FILE,
+ "Warning: cannot enable IO of playback device %d",
+ dev_id));
+ }
+
+ }
+
+#if COREAUDIO_MAC
+ PJ_LOG(5, (THIS_FILE, "Opening device %d", dev_id));
+ ostatus = AudioUnitSetProperty(*io_unit,
+ kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global,
+ 0,
+ &dev_id,
+ sizeof(dev_id));
+ if (ostatus != noErr) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+#endif
+
+ if (dir & PJMEDIA_DIR_CAPTURE) {
+#if COREAUDIO_MAC
+ AudioStreamBasicDescription deviceFormat;
+ UInt32 size;
+
+ /*
+ * Keep the sample rate from the device, otherwise we will confuse
+ * AUHAL
+ */
+ size = sizeof(AudioStreamBasicDescription);
+ ostatus = AudioUnitGetProperty(*io_unit,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input,
+ 1,
+ &deviceFormat,
+ &size);
+ if (ostatus != noErr) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+ strm->streamFormat.mSampleRate = deviceFormat.mSampleRate;
+#endif
+
+ /* When setting the stream format, we have to make sure the sample
+ * rate is supported. Setting an unsupported sample rate will cause
+ * AudioUnitRender() to fail later.
+ */
+ ostatus = AudioUnitSetProperty(*io_unit,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Output,
+ 1,
+ &strm->streamFormat,
+ sizeof(strm->streamFormat));
+ if (ostatus != noErr) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+#if COREAUDIO_MAC
+ strm->streamFormat.mSampleRate = strm->param.clock_rate;
+ size = sizeof(AudioStreamBasicDescription);
+ ostatus = AudioUnitGetProperty (*io_unit,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Output,
+ 1,
+ &deviceFormat,
+ &size);
+ if (ostatus == noErr) {
+ if (strm->streamFormat.mSampleRate != deviceFormat.mSampleRate) {
+ pj_status_t rc = create_audio_resample(strm, &deviceFormat);
+ if (PJ_SUCCESS != rc)
+ return rc;
+ }
+ } else {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+#endif
+ }
+
+ if (dir & PJMEDIA_DIR_PLAYBACK) {
+ AURenderCallbackStruct output_cb;
+
+ /* Set the stream format */
+ ostatus = AudioUnitSetProperty(*io_unit,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input,
+ 0,
+ &strm->streamFormat,
+ sizeof(strm->streamFormat));
+ if (ostatus != noErr) {
+ PJ_LOG(4, (THIS_FILE,
+ "Warning: cannot set playback stream format of dev %d",
+ dev_id));
+ }
+
+ /* Set render callback */
+ output_cb.inputProc = output_renderer;
+ output_cb.inputProcRefCon = strm;
+ ostatus = AudioUnitSetProperty(*io_unit,
+ kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Input,
+ 0,
+ &output_cb,
+ sizeof(output_cb));
+ if (ostatus != noErr) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+ /* Allocate playback buffer */
+ strm->play_buf = (pj_int16_t*)pj_pool_alloc(strm->pool,
+ strm->param.samples_per_frame *
+ strm->param.bits_per_sample >> 3);
+ if (!strm->play_buf)
+ return PJ_ENOMEM;
+ strm->play_buf_count = 0;
+ }
+
+ if (dir & PJMEDIA_DIR_CAPTURE) {
+ AURenderCallbackStruct input_cb;
+#if COREAUDIO_MAC
+ AudioBuffer *ab;
+ UInt32 size, buf_size;
+#endif
+
+ /* Set input callback */
+ input_cb.inputProc = strm->resample ? resample_callback :
+ input_callback;
+ input_cb.inputProcRefCon = strm;
+ ostatus = AudioUnitSetProperty(
+ *io_unit,
+ kAudioOutputUnitProperty_SetInputCallback,
+ kAudioUnitScope_Global,
+ 0,
+ &input_cb,
+ sizeof(input_cb));
+ if (ostatus != noErr) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+#if COREAUDIO_MAC
+ /* Get device's buffer frame size */
+ size = sizeof(UInt32);
+ ostatus = AudioUnitGetProperty(*io_unit,
+ kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Global,
+ 0,
+ &buf_size,
+ &size);
+ if (ostatus != noErr)
+ {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+ /* Allocate audio buffer */
+ strm->audio_buf = (AudioBufferList*)pj_pool_alloc(strm->pool,
+ sizeof(AudioBufferList) + sizeof(AudioBuffer));
+ if (!strm->audio_buf)
+ return PJ_ENOMEM;
+
+ strm->audio_buf->mNumberBuffers = 1;
+ ab = &strm->audio_buf->mBuffers[0];
+ ab->mNumberChannels = strm->streamFormat.mChannelsPerFrame;
+ ab->mDataByteSize = buf_size * ab->mNumberChannels *
+ strm->param.bits_per_sample >> 3;
+ ab->mData = pj_pool_alloc(strm->pool,
+ ab->mDataByteSize);
+ if (!ab->mData)
+ return PJ_ENOMEM;
+
+#else
+ /* We will let AudioUnitRender() to allocate the buffer
+ * for us later
+ */
+ strm->audio_buf = (AudioBufferList*)pj_pool_alloc(strm->pool,
+ sizeof(AudioBufferList) + sizeof(AudioBuffer));
+ if (!strm->audio_buf)
+ return PJ_ENOMEM;
+
+ strm->audio_buf->mNumberBuffers = 1;
+ strm->audio_buf->mBuffers[0].mNumberChannels =
+ strm->streamFormat.mChannelsPerFrame;
+
+#endif
+
+ /* Allocate recording buffer */
+ strm->rec_buf = (pj_int16_t*)pj_pool_alloc(strm->pool,
+ strm->param.samples_per_frame *
+ strm->param.bits_per_sample >> 3);
+ if (!strm->rec_buf)
+ return PJ_ENOMEM;
+ strm->rec_buf_count = 0;
+ }
+
+ /* Initialize the audio unit */
+ ostatus = AudioUnitInitialize(*io_unit);
+ if (ostatus != noErr) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: create stream */
+static pj_status_t ca_factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm)
+{
+ struct coreaudio_factory *cf = (struct coreaudio_factory*)f;
+ pj_pool_t *pool;
+ struct coreaudio_stream *strm;
+ pj_status_t status;
+
+ /* Create and Initialize stream descriptor */
+ pool = pj_pool_create(cf->pf, "coreaudio-dev", 1000, 1000, NULL);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ strm = PJ_POOL_ZALLOC_T(pool, struct coreaudio_stream);
+ pj_list_init(&strm->list_entry);
+ strm->list_entry.stream = strm;
+ strm->cf = cf;
+ pj_memcpy(&strm->param, param, sizeof(*param));
+ strm->pool = pool;
+ strm->rec_cb = rec_cb;
+ strm->play_cb = play_cb;
+ strm->user_data = user_data;
+
+ /* Set the stream format */
+ strm->streamFormat.mSampleRate = param->clock_rate;
+ strm->streamFormat.mFormatID = kAudioFormatLinearPCM;
+ strm->streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger
+ | kLinearPCMFormatFlagIsPacked;
+ strm->streamFormat.mBitsPerChannel = strm->param.bits_per_sample;
+ strm->streamFormat.mChannelsPerFrame = param->channel_count;
+ strm->streamFormat.mBytesPerFrame = strm->streamFormat.mChannelsPerFrame
+ * strm->param.bits_per_sample >> 3;
+ strm->streamFormat.mFramesPerPacket = 1;
+ strm->streamFormat.mBytesPerPacket = strm->streamFormat.mBytesPerFrame *
+ strm->streamFormat.mFramesPerPacket;
+
+ /* Apply input/output routes settings before we create the audio units */
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE) {
+ ca_stream_set_cap(&strm->base,
+ PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE,
+ &param->input_route);
+ }
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE) {
+ ca_stream_set_cap(&strm->base,
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
+ &param->output_route);
+ }
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_EC) {
+ ca_stream_set_cap(&strm->base,
+ PJMEDIA_AUD_DEV_CAP_EC,
+ &param->ec_enabled);
+ } else {
+ pj_bool_t ec = PJ_FALSE;
+ ca_stream_set_cap(&strm->base,
+ PJMEDIA_AUD_DEV_CAP_EC, &ec);
+ }
+
+ strm->io_units[0] = strm->io_units[1] = NULL;
+ if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK &&
+ param->rec_id == param->play_id)
+ {
+ /* If both input and output are on the same device, only create
+ * one audio unit to interface with the device.
+ */
+ status = create_audio_unit(cf->io_comp,
+ cf->dev_info[param->rec_id].dev_id,
+ param->dir, strm, &strm->io_units[0]);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ } else {
+ unsigned nunits = 0;
+
+ if (param->dir & PJMEDIA_DIR_CAPTURE) {
+ status = create_audio_unit(cf->io_comp,
+ cf->dev_info[param->rec_id].dev_id,
+ PJMEDIA_DIR_CAPTURE,
+ strm, &strm->io_units[nunits++]);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+ if (param->dir & PJMEDIA_DIR_PLAYBACK) {
+
+ status = create_audio_unit(cf->io_comp,
+ cf->dev_info[param->play_id].dev_id,
+ PJMEDIA_DIR_PLAYBACK,
+ strm, &strm->io_units[nunits++]);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+ }
+
+ /* Apply the remaining settings */
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) {
+ ca_stream_get_cap(&strm->base,
+ PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY,
+ &strm->param.input_latency_ms);
+ }
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) {
+ ca_stream_get_cap(&strm->base,
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY,
+ &strm->param.output_latency_ms);
+ }
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
+ ca_stream_set_cap(&strm->base,
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &param->output_vol);
+ }
+
+ pj_mutex_lock(strm->cf->mutex);
+ pj_assert(pj_list_empty(&strm->list_entry));
+ pj_list_insert_after(&strm->cf->streams, &strm->list_entry);
+ pj_mutex_unlock(strm->cf->mutex);
+
+ /* Done */
+ strm->base.op = &stream_op;
+ *p_aud_strm = &strm->base;
+
+ return PJ_SUCCESS;
+
+ on_error:
+ ca_stream_destroy((pjmedia_aud_stream *)strm);
+ return status;
+}
+
+/* API: Get stream info. */
+static pj_status_t ca_stream_get_param(pjmedia_aud_stream *s,
+ pjmedia_aud_param *pi)
+{
+ struct coreaudio_stream *strm = (struct coreaudio_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+ /* Update the device capabilities' values */
+ if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY,
+ &pi->input_latency_ms) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+ }
+ if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY,
+ &pi->output_latency_ms) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+ }
+ if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &pi->output_vol) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+ }
+ if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE,
+ &pi->input_route) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE;
+ }
+ if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
+ &pi->output_route) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE;
+ }
+ if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_EC,
+ &pi->ec_enabled) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_EC;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t ca_stream_get_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ void *pval)
+{
+ struct coreaudio_stream *strm = (struct coreaudio_stream*)s;
+
+ PJ_UNUSED_ARG(strm);
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY &&
+ (strm->param.dir & PJMEDIA_DIR_CAPTURE))
+ {
+#if COREAUDIO_MAC
+ UInt32 latency, size = sizeof(UInt32);
+
+ /* Recording latency */
+ if (AudioUnitGetProperty (strm->io_units[0],
+ kAudioDevicePropertyLatency,
+ kAudioUnitScope_Input,
+ 1,
+ &latency,
+ &size) == noErr)
+ {
+ UInt32 latency2;
+ if (AudioUnitGetProperty (strm->io_units[0],
+ kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Input,
+ 1,
+ &latency2,
+ &size) == noErr)
+ {
+ strm->param.input_latency_ms = (latency + latency2) * 1000 /
+ strm->param.clock_rate;
+ strm->param.input_latency_ms++;
+ }
+ }
+#else
+ Float32 latency, latency2;
+ UInt32 size = sizeof(Float32);
+
+ if ((AudioSessionGetProperty(
+ kAudioSessionProperty_CurrentHardwareInputLatency,
+ &size, &latency) == kAudioSessionNoError) &&
+ (AudioSessionGetProperty(
+ kAudioSessionProperty_CurrentHardwareIOBufferDuration,
+ &size, &latency2) == kAudioSessionNoError))
+ {
+ strm->param.input_latency_ms = (unsigned)
+ ((latency + latency2) * 1000);
+ strm->param.input_latency_ms++;
+ }
+#endif
+
+ *(unsigned*)pval = strm->param.input_latency_ms;
+ return PJ_SUCCESS;
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY &&
+ (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
+ {
+#if COREAUDIO_MAC
+ UInt32 latency, size = sizeof(UInt32);
+ AudioUnit *io_unit = strm->io_units[1] ? &strm->io_units[1] :
+ &strm->io_units[0];
+
+ /* Playback latency */
+ if (AudioUnitGetProperty (*io_unit,
+ kAudioDevicePropertyLatency,
+ kAudioUnitScope_Output,
+ 0,
+ &latency,
+ &size) == noErr)
+ {
+ UInt32 latency2;
+ if (AudioUnitGetProperty (*io_unit,
+ kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Output,
+ 0,
+ &latency2,
+ &size) == noErr)
+ {
+ strm->param.output_latency_ms = (latency + latency2) * 1000 /
+ strm->param.clock_rate;
+ strm->param.output_latency_ms++;
+ }
+ }
+#else
+ Float32 latency, latency2;
+ UInt32 size = sizeof(Float32);
+
+ if ((AudioSessionGetProperty(
+ kAudioSessionProperty_CurrentHardwareOutputLatency,
+ &size, &latency) == kAudioSessionNoError) &&
+ (AudioSessionGetProperty(
+ kAudioSessionProperty_CurrentHardwareIOBufferDuration,
+ &size, &latency2) == kAudioSessionNoError))
+ {
+ strm->param.output_latency_ms = (unsigned)
+ ((latency + latency2) * 1000);
+ strm->param.output_latency_ms++;
+ }
+#endif
+ *(unsigned*)pval = (++strm->param.output_latency_ms * 2);
+ return PJ_SUCCESS;
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
+ (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
+ {
+ OSStatus ostatus;
+ Float32 volume;
+ UInt32 size = sizeof(Float32);
+
+ /* Output volume setting */
+#if COREAUDIO_MAC
+ ostatus = AudioUnitGetProperty (strm->io_units[1] ? strm->io_units[1] :
+ strm->io_units[0],
+ kAudioDevicePropertyVolumeScalar,
+ kAudioUnitScope_Output,
+ 0,
+ &volume,
+ &size);
+ if (ostatus != noErr)
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+#else
+ ostatus = AudioSessionGetProperty(
+ kAudioSessionProperty_CurrentHardwareOutputVolume,
+ &size, &volume);
+ if (ostatus != kAudioSessionNoError) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+#endif
+
+ *(unsigned*)pval = (unsigned)(volume * 100);
+ return PJ_SUCCESS;
+#if !COREAUDIO_MAC
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE &&
+ (strm->param.dir & PJMEDIA_DIR_CAPTURE))
+ {
+ UInt32 btooth, size = sizeof(UInt32);
+ OSStatus ostatus;
+
+ ostatus = AudioSessionGetProperty (
+ kAudioSessionProperty_OverrideCategoryEnableBluetoothInput,
+ &size, &btooth);
+ if (ostatus != kAudioSessionNoError) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+ *(pjmedia_aud_dev_route*)pval = btooth?
+ PJMEDIA_AUD_DEV_ROUTE_BLUETOOTH:
+ PJMEDIA_AUD_DEV_ROUTE_DEFAULT;
+ return PJ_SUCCESS;
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE &&
+ (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
+ {
+ CFStringRef route;
+ UInt32 size = sizeof(CFStringRef);
+ OSStatus ostatus;
+
+ ostatus = AudioSessionGetProperty (kAudioSessionProperty_AudioRoute,
+ &size, &route);
+ if (ostatus != kAudioSessionNoError) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+ if (!route) {
+ *(pjmedia_aud_dev_route*)pval = PJMEDIA_AUD_DEV_ROUTE_DEFAULT;
+ } else if (CFStringHasPrefix(route, CFSTR("Headset"))) {
+ *(pjmedia_aud_dev_route*)pval = PJMEDIA_AUD_DEV_ROUTE_EARPIECE;
+ } else {
+ *(pjmedia_aud_dev_route*)pval = PJMEDIA_AUD_DEV_ROUTE_DEFAULT;
+ }
+
+ CFRelease(route);
+
+ return PJ_SUCCESS;
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_EC) {
+ AudioComponentDescription desc;
+ OSStatus ostatus;
+
+ ostatus = AudioComponentGetDescription(strm->cf->io_comp, &desc);
+ if (ostatus != noErr) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+ *(pj_bool_t*)pval = (desc.componentSubType ==
+ kAudioUnitSubType_VoiceProcessingIO);
+ return PJ_SUCCESS;
+#endif
+ } else {
+ return PJMEDIA_EAUD_INVCAP;
+ }
+}
+
+/* API: set capability */
+static pj_status_t ca_stream_set_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ const void *pval)
+{
+ struct coreaudio_stream *strm = (struct coreaudio_stream*)s;
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+#if COREAUDIO_MAC
+ if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
+ (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
+ {
+ OSStatus ostatus;
+ Float32 volume = *(unsigned*)pval;
+
+ /* Output volume setting */
+ volume /= 100.0;
+ ostatus = AudioUnitSetProperty (strm->io_units[1] ? strm->io_units[1] :
+ strm->io_units[0],
+ kAudioDevicePropertyVolumeScalar,
+ kAudioUnitScope_Output,
+ 0,
+ &volume,
+ sizeof(Float32));
+ if (ostatus != noErr) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+ strm->param.output_vol = *(unsigned*)pval;
+ return PJ_SUCCESS;
+ }
+
+#else
+
+ if ((cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY &&
+ (strm->param.dir & PJMEDIA_DIR_CAPTURE)) ||
+ (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY &&
+ (strm->param.dir & PJMEDIA_DIR_PLAYBACK)))
+ {
+ Float32 bufferDuration = *(unsigned *)pval;
+ OSStatus ostatus;
+ unsigned latency;
+
+ /* For low-latency audio streaming, you can set this value to
+ * as low as 5 ms (the default is 23ms). However, lowering the
+ * latency may cause a decrease in audio quality.
+ */
+ bufferDuration /= 1000;
+ ostatus = AudioSessionSetProperty(
+ kAudioSessionProperty_PreferredHardwareIOBufferDuration,
+ sizeof(bufferDuration), &bufferDuration);
+ if (ostatus != kAudioSessionNoError) {
+ PJ_LOG(4, (THIS_FILE,
+ "Error: cannot set the preferred buffer duration (%i)",
+ ostatus));
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+ ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY, &latency);
+ ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY, &latency);
+
+ return PJ_SUCCESS;
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE &&
+ (strm->param.dir & PJMEDIA_DIR_CAPTURE))
+ {
+ UInt32 btooth = *(pjmedia_aud_dev_route*)pval ==
+ PJMEDIA_AUD_DEV_ROUTE_BLUETOOTH ? 1 : 0;
+ OSStatus ostatus;
+
+ ostatus = AudioSessionSetProperty (
+ kAudioSessionProperty_OverrideCategoryEnableBluetoothInput,
+ sizeof(btooth), &btooth);
+ if (ostatus != kAudioSessionNoError) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+ strm->param.input_route = *(pjmedia_aud_dev_route*)pval;
+ return PJ_SUCCESS;
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE &&
+ (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
+ {
+ OSStatus ostatus;
+ UInt32 route = *(pjmedia_aud_dev_route*)pval ==
+ PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER ?
+ kAudioSessionOverrideAudioRoute_Speaker :
+ kAudioSessionOverrideAudioRoute_None;
+
+ ostatus = AudioSessionSetProperty (
+ kAudioSessionProperty_OverrideAudioRoute,
+ sizeof(route), &route);
+ if (ostatus != kAudioSessionNoError) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+ strm->param.output_route = *(pjmedia_aud_dev_route*)pval;
+ return PJ_SUCCESS;
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_EC) {
+ AudioComponentDescription desc;
+ AudioComponent io_comp;
+
+ desc.componentType = kAudioUnitType_Output;
+ desc.componentSubType = (*(pj_bool_t*)pval)?
+ kAudioUnitSubType_VoiceProcessingIO :
+ kAudioUnitSubType_RemoteIO;
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+
+ io_comp = AudioComponentFindNext(NULL, &desc);
+ if (io_comp == NULL)
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(-1);
+ strm->cf->io_comp = io_comp;
+ strm->param.ec_enabled = *(pj_bool_t*)pval;
+
+ PJ_LOG(4, (THIS_FILE, "Using %s audio unit",
+ (desc.componentSubType ==
+ kAudioUnitSubType_RemoteIO? "RemoteIO":
+ "VoiceProcessingIO")));
+
+ return PJ_SUCCESS;
+ }
+#endif
+
+ return PJMEDIA_EAUD_INVCAP;
+}
+
+/* API: Start stream. */
+static pj_status_t ca_stream_start(pjmedia_aud_stream *strm)
+{
+ struct coreaudio_stream *stream = (struct coreaudio_stream*)strm;
+ OSStatus ostatus;
+ UInt32 i;
+
+ if (stream->running)
+ return PJ_SUCCESS;
+
+ stream->quit_flag = 0;
+ stream->interrupted = PJ_FALSE;
+ stream->rec_buf_count = 0;
+ stream->play_buf_count = 0;
+ stream->resample_buf_count = 0;
+
+ if (stream->resample) {
+ ostatus = AudioConverterReset(stream->resample);
+ if (ostatus != noErr)
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+
+#if !COREAUDIO_MAC
+ AudioSessionSetActive(true);
+#endif
+
+ for (i = 0; i < 2; i++) {
+ if (stream->io_units[i] == NULL) break;
+ ostatus = AudioOutputUnitStart(stream->io_units[i]);
+ if (ostatus != noErr) {
+ if (i == 1)
+ AudioOutputUnitStop(stream->io_units[0]);
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+ }
+
+ stream->running = PJ_TRUE;
+
+ PJ_LOG(4, (THIS_FILE, "core audio stream started"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t ca_stream_stop(pjmedia_aud_stream *strm)
+{
+ struct coreaudio_stream *stream = (struct coreaudio_stream*)strm;
+ OSStatus ostatus;
+ unsigned i;
+ int should_deactivate;
+ struct stream_list *it, *itBegin;
+
+ if (!stream->running)
+ return PJ_SUCCESS;
+
+ for (i = 0; i < 2; i++) {
+ if (stream->io_units[i] == NULL) break;
+ ostatus = AudioOutputUnitStop(stream->io_units[i]);
+ if (ostatus != noErr) {
+ if (i == 0 && stream->io_units[1])
+ AudioOutputUnitStop(stream->io_units[1]);
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus);
+ }
+ }
+
+ /* Check whether we need to deactivate the audio session. */
+ pj_mutex_lock(stream->cf->mutex);
+ pj_assert(!pj_list_empty(&stream->cf->streams));
+ pj_assert(!pj_list_empty(&stream->list_entry));
+ stream->running = PJ_FALSE;
+ should_deactivate = PJ_TRUE;
+ itBegin = &stream->cf->streams;
+ for (it = itBegin->next; it != itBegin; it = it->next) {
+ if (it->stream->running) {
+ should_deactivate = PJ_FALSE;
+ break;
+ }
+ }
+ pj_mutex_unlock(stream->cf->mutex);
+
+#if !COREAUDIO_MAC
+ if (should_deactivate)
+ AudioSessionSetActive(false);
+#endif
+
+ stream->quit_flag = 1;
+ stream->play_thread_initialized = 0;
+ stream->rec_thread_initialized = 0;
+ pj_bzero(stream->rec_thread_desc, sizeof(pj_thread_desc));
+ pj_bzero(stream->play_thread_desc, sizeof(pj_thread_desc));
+
+ PJ_LOG(4, (THIS_FILE, "core audio stream stopped"));
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t ca_stream_destroy(pjmedia_aud_stream *strm)
+{
+ struct coreaudio_stream *stream = (struct coreaudio_stream*)strm;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+ ca_stream_stop(strm);
+
+ for (i = 0; i < 2; i++) {
+ if (stream->io_units[i]) {
+ AudioUnitUninitialize(stream->io_units[i]);
+ AudioComponentInstanceDispose(stream->io_units[i]);
+ stream->io_units[i] = NULL;
+ }
+ }
+
+ if (stream->resample)
+ AudioConverterDispose(stream->resample);
+
+ pj_mutex_lock(stream->cf->mutex);
+ if (!pj_list_empty(&stream->list_entry))
+ pj_list_erase(&stream->list_entry);
+ pj_mutex_unlock(stream->cf->mutex);
+
+ pj_pool_release(stream->pool);
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_AUDIO_DEV_HAS_COREAUDIO */
diff --git a/pjmedia/src/pjmedia-audiodev/errno.c b/pjmedia/src/pjmedia-audiodev/errno.c
new file mode 100644
index 0000000..1066386
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/errno.c
@@ -0,0 +1,206 @@
+/* $Id: errno.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia-audiodev/errno.h>
+#include <pj/string.h>
+#include <pj/unicode.h>
+#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+# include <portaudio.h>
+#endif
+#if PJMEDIA_AUDIO_DEV_HAS_WMME
+# ifdef _MSC_VER
+# pragma warning(push, 3)
+# endif
+# include <windows.h>
+# include <mmsystem.h>
+# ifdef _MSC_VER
+# pragma warning(pop)
+# endif
+#endif
+
+/* PJMEDIA-Audiodev's own error codes/messages
+ * MUST KEEP THIS ARRAY SORTED!!
+ * Message must be limited to 64 chars!
+ */
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+static const struct
+{
+ int code;
+ const char *msg;
+} err_str[] =
+{
+ PJ_BUILD_ERR( PJMEDIA_EAUD_ERR, "Unspecified audio device error" ),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_SYSERR, "Unknown error from audio driver" ),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_INIT, "Audio subsystem not initialized" ),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_INVDEV, "Invalid audio device" ),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_NODEV, "Found no audio devices" ),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_NODEFDEV, "Unable to find default audio device" ),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_NOTREADY, "Audio device not ready" ),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_INVCAP, "Invalid or unsupported audio capability" ),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_INVOP, "Invalid or unsupported audio device operation" ),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_BADFORMAT, "Bad or invalid audio device format" ),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_SAMPFORMAT, "Invalid audio device sample format"),
+ PJ_BUILD_ERR( PJMEDIA_EAUD_BADLATENCY, "Bad audio latency setting")
+
+};
+
+#endif /* PJ_HAS_ERROR_STRING */
+
+
+
+/*
+ * pjmedia_audiodev_strerror()
+ */
+PJ_DEF(pj_str_t) pjmedia_audiodev_strerror(pj_status_t statcode,
+ char *buf, pj_size_t bufsize )
+{
+ pj_str_t errstr;
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+
+ /* See if the error comes from Core Audio. */
+#if PJMEDIA_AUDIO_DEV_HAS_COREAUDIO
+ if (statcode >= PJMEDIA_AUDIODEV_COREAUDIO_ERRNO_START &&
+ statcode <= PJMEDIA_AUDIODEV_COREAUDIO_ERRNO_END)
+ {
+ int ca_err = PJMEDIA_AUDIODEV_COREAUDIO_ERRNO_START - statcode;
+
+ PJ_UNUSED_ARG(ca_err);
+ // TODO: create more helpful error messages
+ errstr.ptr = buf;
+ pj_strcpy2(&errstr, "Core audio error");
+ return errstr;
+ } else
+#endif
+
+ /* See if the error comes from PortAudio. */
+#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+ if (statcode >= PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_START &&
+ statcode <= PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_END)
+ {
+
+ //int pa_err = statcode - PJMEDIA_ERRNO_FROM_PORTAUDIO(0);
+ int pa_err = PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_START - statcode;
+ pj_str_t msg;
+
+ msg.ptr = (char*)Pa_GetErrorText(pa_err);
+ msg.slen = pj_ansi_strlen(msg.ptr);
+
+ errstr.ptr = buf;
+ pj_strncpy_with_null(&errstr, &msg, bufsize);
+ return errstr;
+
+ } else
+#endif /* PJMEDIA_SOUND_IMPLEMENTATION */
+
+ /* See if the error comes from WMME */
+#if PJMEDIA_AUDIO_DEV_HAS_WMME
+ if ((statcode >= PJMEDIA_AUDIODEV_WMME_IN_ERROR_START &&
+ statcode < PJMEDIA_AUDIODEV_WMME_IN_ERROR_END) ||
+ (statcode >= PJMEDIA_AUDIODEV_WMME_OUT_ERROR_START &&
+ statcode < PJMEDIA_AUDIODEV_WMME_OUT_ERROR_END))
+ {
+ MMRESULT native_err, mr;
+ MMRESULT (WINAPI *waveGetErrText)(UINT mmrError, LPTSTR pszText, UINT cchText);
+ PJ_DECL_UNICODE_TEMP_BUF(wbuf, 80)
+
+ if (statcode >= PJMEDIA_AUDIODEV_WMME_IN_ERROR_START &&
+ statcode <= PJMEDIA_AUDIODEV_WMME_IN_ERROR_END)
+ {
+ native_err = statcode - PJMEDIA_AUDIODEV_WMME_IN_ERROR_START;
+ waveGetErrText = &waveInGetErrorText;
+ } else {
+ native_err = statcode - PJMEDIA_AUDIODEV_WMME_OUT_ERROR_START;
+ waveGetErrText = &waveOutGetErrorText;
+ }
+
+#if PJ_NATIVE_STRING_IS_UNICODE
+ mr = (*waveGetErrText)(native_err, wbuf, PJ_ARRAY_SIZE(wbuf));
+ if (mr == MMSYSERR_NOERROR) {
+ int len = wcslen(wbuf);
+ pj_unicode_to_ansi(wbuf, len, buf, bufsize);
+ }
+#else
+ mr = (*waveGetErrText)(native_err, buf, bufsize);
+#endif
+
+ if (mr==MMSYSERR_NOERROR) {
+ errstr.ptr = buf;
+ errstr.slen = pj_ansi_strlen(buf);
+ return errstr;
+ } else {
+ pj_ansi_snprintf(buf, bufsize, "MMSYSTEM native error %d",
+ native_err);
+ return pj_str(buf);
+ }
+
+ } else
+#endif
+
+ /* Audiodev error */
+ if (statcode >= PJMEDIA_AUDIODEV_ERRNO_START &&
+ statcode < PJMEDIA_AUDIODEV_ERRNO_END)
+ {
+ /* Find the error in the table.
+ * Use binary search!
+ */
+ int first = 0;
+ int n = PJ_ARRAY_SIZE(err_str);
+
+ while (n > 0) {
+ int half = n/2;
+ int mid = first + half;
+
+ if (err_str[mid].code < statcode) {
+ first = mid+1;
+ n -= (half+1);
+ } else if (err_str[mid].code > statcode) {
+ n = half;
+ } else {
+ first = mid;
+ break;
+ }
+ }
+
+
+ if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) {
+ pj_str_t msg;
+
+ msg.ptr = (char*)err_str[first].msg;
+ msg.slen = pj_ansi_strlen(err_str[first].msg);
+
+ errstr.ptr = buf;
+ pj_strncpy_with_null(&errstr, &msg, bufsize);
+ return errstr;
+
+ }
+ }
+#endif /* PJ_HAS_ERROR_STRING */
+
+ /* Error not found. */
+ errstr.ptr = buf;
+ errstr.slen = pj_ansi_snprintf(buf, bufsize,
+ "Unknown pjmedia-audiodev error %d",
+ statcode);
+
+ return errstr;
+}
+
diff --git a/pjmedia/src/pjmedia-audiodev/legacy_dev.c b/pjmedia/src/pjmedia-audiodev/legacy_dev.c
new file mode 100644
index 0000000..45c6c5d
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/legacy_dev.c
@@ -0,0 +1,468 @@
+/* $Id: legacy_dev.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia-audiodev/audiodev_imp.h>
+#include <pjmedia/sound.h>
+#include <pj/assert.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_LEGACY_DEVICE
+
+#define THIS_FILE "legacy_dev.c"
+
+/* Legacy devices factory */
+struct legacy_factory
+{
+ pjmedia_aud_dev_factory base;
+ pj_pool_t *pool;
+ pj_pool_factory *pf;
+};
+
+
+struct legacy_stream
+{
+ pjmedia_aud_stream base;
+
+ pj_pool_t *pool;
+ pjmedia_aud_param param;
+ pjmedia_snd_stream *snd_strm;
+ pjmedia_aud_play_cb user_play_cb;
+ pjmedia_aud_rec_cb user_rec_cb;
+ void *user_user_data;
+ unsigned input_latency;
+ unsigned output_latency;
+};
+
+
+/* Prototypes */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f);
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info);
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param);
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm);
+
+static pj_status_t stream_get_param(pjmedia_aud_stream *strm,
+ pjmedia_aud_param *param);
+static pj_status_t stream_get_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ void *value);
+static pj_status_t stream_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value);
+static pj_status_t stream_start(pjmedia_aud_stream *strm);
+static pj_status_t stream_stop(pjmedia_aud_stream *strm);
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm);
+
+
+/* Operations */
+static pjmedia_aud_dev_factory_op factory_op =
+{
+ &factory_init,
+ &factory_destroy,
+ &factory_get_dev_count,
+ &factory_get_dev_info,
+ &factory_default_param,
+ &factory_create_stream,
+ &factory_refresh
+};
+
+static pjmedia_aud_stream_op stream_op =
+{
+ &stream_get_param,
+ &stream_get_cap,
+ &stream_set_cap,
+ &stream_start,
+ &stream_stop,
+ &stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+
+/*
+ * Init legacy audio driver.
+ */
+pjmedia_aud_dev_factory* pjmedia_legacy_factory(pj_pool_factory *pf)
+{
+ struct legacy_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "legacy-snd", 512, 512, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct legacy_factory);
+ f->pf = pf;
+ f->pool = pool;
+ f->base.op = &factory_op;
+
+ return &f->base;
+}
+
+
+/* API: init factory */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
+{
+ struct legacy_factory *wf = (struct legacy_factory*)f;
+
+ return pjmedia_snd_init(wf->pf);
+}
+
+/* API: destroy factory */
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
+{
+ struct legacy_factory *wf = (struct legacy_factory*)f;
+ pj_status_t status;
+
+ status = pjmedia_snd_deinit();
+
+ if (status == PJ_SUCCESS) {
+ pj_pool_t *pool = wf->pool;
+ wf->pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return status;
+}
+
+/* API: refresh the list of devices */
+static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f)
+{
+ PJ_UNUSED_ARG(f);
+ return PJ_ENOTSUP;
+}
+
+/* API: get number of devices */
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+ PJ_UNUSED_ARG(f);
+ return pjmedia_snd_get_dev_count();
+}
+
+/* API: get device info */
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info)
+{
+ const pjmedia_snd_dev_info *si =
+ pjmedia_snd_get_dev_info(index);;
+
+ PJ_UNUSED_ARG(f);
+
+ if (si == NULL)
+ return PJMEDIA_EAUD_INVDEV;
+
+ pj_bzero(info, sizeof(*info));
+ pj_ansi_strncpy(info->name, si->name, sizeof(info->name));
+ info->name[sizeof(info->name)-1] = '\0';
+ info->input_count = si->input_count;
+ info->output_count = si->output_count;
+ info->default_samples_per_sec = si->default_samples_per_sec;
+ pj_ansi_strcpy(info->driver, "legacy");
+ info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param)
+{
+ pjmedia_aud_dev_info di;
+ pj_status_t status;
+
+ status = factory_get_dev_info(f, index, &di);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_bzero(param, sizeof(*param));
+ if (di.input_count && di.output_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param->rec_id = index;
+ param->play_id = index;
+ } else if (di.input_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE;
+ param->rec_id = index;
+ param->play_id = PJMEDIA_AUD_INVALID_DEV;
+ } else if (di.output_count) {
+ param->dir = PJMEDIA_DIR_PLAYBACK;
+ param->play_id = index;
+ param->rec_id = PJMEDIA_AUD_INVALID_DEV;
+ } else {
+ return PJMEDIA_EAUD_INVDEV;
+ }
+
+ param->clock_rate = di.default_samples_per_sec;
+ param->channel_count = 1;
+ param->samples_per_frame = di.default_samples_per_sec * 20 / 1000;
+ param->bits_per_sample = 16;
+ param->flags = di.caps;
+ param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+ param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+
+ return PJ_SUCCESS;
+}
+
+/* Callback from legacy sound device */
+static pj_status_t snd_play_cb(/* in */ void *user_data,
+ /* in */ pj_uint32_t timestamp,
+ /* out */ void *output,
+ /* out */ unsigned size)
+{
+ struct legacy_stream *strm = (struct legacy_stream*)user_data;
+ pjmedia_frame frame;
+ pj_status_t status;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = output;
+ frame.size = size;
+ frame.timestamp.u64 = timestamp;
+
+ status = strm->user_play_cb(strm->user_user_data, &frame);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) {
+ pj_bzero(output, size);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* Callback from legacy sound device */
+static pj_status_t snd_rec_cb( /* in */ void *user_data,
+ /* in */ pj_uint32_t timestamp,
+ /* in */ void *input,
+ /* in*/ unsigned size)
+{
+ struct legacy_stream *strm = (struct legacy_stream*)user_data;
+ pjmedia_frame frame;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = input;
+ frame.size = size;
+ frame.timestamp.u64 = timestamp;
+
+ return strm->user_rec_cb(strm->user_user_data, &frame);
+}
+
+/* API: create stream */
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm)
+{
+ struct legacy_factory *wf = (struct legacy_factory*)f;
+ pj_pool_t *pool;
+ struct legacy_stream *strm;
+ pj_status_t status;
+
+ /* Initialize our stream data */
+ pool = pj_pool_create(wf->pf, "legacy-snd", 512, 512, NULL);
+ strm = PJ_POOL_ZALLOC_T(pool, struct legacy_stream);
+ strm->pool = pool;
+ strm->user_rec_cb = rec_cb;
+ strm->user_play_cb = play_cb;
+ strm->user_user_data = user_data;
+ pj_memcpy(&strm->param, param, sizeof(*param));
+
+ /* Set the latency if wanted */
+ if (param->dir==PJMEDIA_DIR_CAPTURE_PLAYBACK &&
+ param->flags & (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY))
+ {
+ PJ_ASSERT_RETURN(param->input_latency_ms &&
+ param->output_latency_ms,
+ PJMEDIA_EAUD_BADLATENCY);
+
+ strm->input_latency = param->input_latency_ms;
+ strm->output_latency = param->output_latency_ms;
+
+ status = pjmedia_snd_set_latency(param->input_latency_ms,
+ param->output_latency_ms);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(pool);
+ return status;
+ }
+ }
+
+ /* Open the stream */
+ if (param->dir == PJMEDIA_DIR_CAPTURE) {
+ status = pjmedia_snd_open_rec(param->rec_id,
+ param->clock_rate,
+ param->channel_count,
+ param->samples_per_frame,
+ param->bits_per_sample,
+ &snd_rec_cb,
+ strm,
+ &strm->snd_strm);
+ } else if (param->dir == PJMEDIA_DIR_PLAYBACK) {
+ status = pjmedia_snd_open_player(param->play_id,
+ param->clock_rate,
+ param->channel_count,
+ param->samples_per_frame,
+ param->bits_per_sample,
+ &snd_play_cb,
+ strm,
+ &strm->snd_strm);
+
+ } else if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
+ status = pjmedia_snd_open(param->rec_id,
+ param->play_id,
+ param->clock_rate,
+ param->channel_count,
+ param->samples_per_frame,
+ param->bits_per_sample,
+ &snd_rec_cb,
+ &snd_play_cb,
+ strm,
+ &strm->snd_strm);
+ } else {
+ pj_assert(!"Invalid direction!");
+ return PJ_EINVAL;
+ }
+
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(pool);
+ return status;
+ }
+
+ *p_aud_strm = &strm->base;
+ return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t stream_get_param(pjmedia_aud_stream *s,
+ pjmedia_aud_param *pi)
+{
+ struct legacy_stream *strm = (struct legacy_stream*)s;
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+ if (strm->input_latency) {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+ pi->input_latency_ms = strm->input_latency;
+ } else {
+ pi->flags &= ~PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+ }
+
+ if (strm->output_latency) {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+ pi->output_latency_ms = strm->output_latency;
+ } else {
+ pi->flags &= ~PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ void *pval)
+{
+ struct legacy_stream *strm = (struct legacy_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY &&
+ (strm->param.dir & PJMEDIA_DIR_CAPTURE))
+ {
+ /* Recording latency */
+ if (strm->input_latency) {
+ *(unsigned*)pval = strm->input_latency;
+ return PJ_SUCCESS;
+ } else {
+ return PJMEDIA_EAUD_INVCAP;
+ }
+
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY &&
+ (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
+ {
+ /* Playback latency */
+ if (strm->output_latency) {
+ *(unsigned*)pval = strm->output_latency;
+ return PJ_SUCCESS;
+ } else {
+ return PJMEDIA_EAUD_INVCAP;
+ }
+ } else {
+ return PJMEDIA_EAUD_INVCAP;
+ }
+}
+
+/* API: set capability */
+static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ const void *pval)
+{
+ PJ_UNUSED_ARG(s);
+ PJ_UNUSED_ARG(cap);
+ PJ_UNUSED_ARG(pval);
+ return PJMEDIA_EAUD_INVCAP;
+}
+
+/* API: Start stream. */
+static pj_status_t stream_start(pjmedia_aud_stream *s)
+{
+ struct legacy_stream *strm = (struct legacy_stream*)s;
+ return pjmedia_snd_stream_start(strm->snd_strm);
+}
+
+/* API: Stop stream. */
+static pj_status_t stream_stop(pjmedia_aud_stream *s)
+{
+ struct legacy_stream *strm = (struct legacy_stream*)s;
+ return pjmedia_snd_stream_stop(strm->snd_strm);
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t stream_destroy(pjmedia_aud_stream *s)
+{
+ struct legacy_stream *strm = (struct legacy_stream*)s;
+ pj_status_t status;
+
+ status = pjmedia_snd_stream_close(strm->snd_strm);
+
+ if (status == PJ_SUCCESS) {
+ pj_pool_t *pool = strm->pool;
+
+ strm->pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return status;
+}
+
+#endif /* PJMEDIA_AUDIO_DEV_HAS_LEGACY_DEVICE */
+
diff --git a/pjmedia/src/pjmedia-audiodev/null_dev.c b/pjmedia/src/pjmedia-audiodev/null_dev.c
new file mode 100644
index 0000000..6426641
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/null_dev.c
@@ -0,0 +1,388 @@
+/* $Id: null_dev.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia-audiodev/audiodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_NULL_AUDIO
+
+#define THIS_FILE "null_dev.c"
+
+/* null_audio device info */
+struct null_audio_dev_info
+{
+ pjmedia_aud_dev_info info;
+ unsigned dev_id;
+};
+
+/* null_audio factory */
+struct null_audio_factory
+{
+ pjmedia_aud_dev_factory base;
+ pj_pool_t *pool;
+ pj_pool_factory *pf;
+
+ unsigned dev_count;
+ struct null_audio_dev_info *dev_info;
+};
+
+/* Sound stream. */
+struct null_audio_stream
+{
+ pjmedia_aud_stream base; /**< Base stream */
+ pjmedia_aud_param param; /**< Settings */
+ pj_pool_t *pool; /**< Memory pool. */
+
+ pjmedia_aud_rec_cb rec_cb; /**< Capture callback. */
+ pjmedia_aud_play_cb play_cb; /**< Playback callback. */
+ void *user_data; /**< Application data. */
+};
+
+
+/* Prototypes */
+static pj_status_t null_factory_init(pjmedia_aud_dev_factory *f);
+static pj_status_t null_factory_destroy(pjmedia_aud_dev_factory *f);
+static pj_status_t null_factory_refresh(pjmedia_aud_dev_factory *f);
+static unsigned null_factory_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t null_factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info);
+static pj_status_t null_factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param);
+static pj_status_t null_factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm);
+
+static pj_status_t null_stream_get_param(pjmedia_aud_stream *strm,
+ pjmedia_aud_param *param);
+static pj_status_t null_stream_get_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ void *value);
+static pj_status_t null_stream_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value);
+static pj_status_t null_stream_start(pjmedia_aud_stream *strm);
+static pj_status_t null_stream_stop(pjmedia_aud_stream *strm);
+static pj_status_t null_stream_destroy(pjmedia_aud_stream *strm);
+
+/* Operations */
+static pjmedia_aud_dev_factory_op factory_op =
+{
+ &null_factory_init,
+ &null_factory_destroy,
+ &null_factory_get_dev_count,
+ &null_factory_get_dev_info,
+ &null_factory_default_param,
+ &null_factory_create_stream,
+ &null_factory_refresh
+};
+
+static pjmedia_aud_stream_op stream_op =
+{
+ &null_stream_get_param,
+ &null_stream_get_cap,
+ &null_stream_set_cap,
+ &null_stream_start,
+ &null_stream_stop,
+ &null_stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init null_audio audio driver.
+ */
+pjmedia_aud_dev_factory* pjmedia_null_audio_factory(pj_pool_factory *pf)
+{
+ struct null_audio_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "null audio", 1000, 1000, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct null_audio_factory);
+ f->pf = pf;
+ f->pool = pool;
+ f->base.op = &factory_op;
+
+ return &f->base;
+}
+
+
+/* API: init factory */
+static pj_status_t null_factory_init(pjmedia_aud_dev_factory *f)
+{
+ struct null_audio_factory *nf = (struct null_audio_factory*)f;
+ struct null_audio_dev_info *ndi;
+
+ /* Initialize input and output devices here */
+ nf->dev_count = 1;
+ nf->dev_info = (struct null_audio_dev_info*)
+ pj_pool_calloc(nf->pool, nf->dev_count,
+ sizeof(struct null_audio_dev_info));
+ ndi = &nf->dev_info[0];
+ pj_bzero(ndi, sizeof(*ndi));
+ strcpy(ndi->info.name, "null device");
+ strcpy(ndi->info.driver, "null");
+ ndi->info.input_count = 1;
+ ndi->info.output_count = 1;
+ ndi->info.default_samples_per_sec = 16000;
+ /* Set the device capabilities here */
+ ndi->info.caps = 0;
+
+ PJ_LOG(4, (THIS_FILE, "null audio initialized"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t null_factory_destroy(pjmedia_aud_dev_factory *f)
+{
+ struct null_audio_factory *nf = (struct null_audio_factory*)f;
+ pj_pool_t *pool = nf->pool;
+
+ nf->pool = NULL;
+ pj_pool_release(pool);
+
+ return PJ_SUCCESS;
+}
+
+/* API: refresh the list of devices */
+static pj_status_t null_factory_refresh(pjmedia_aud_dev_factory *f)
+{
+ PJ_UNUSED_ARG(f);
+ return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned null_factory_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+ struct null_audio_factory *nf = (struct null_audio_factory*)f;
+ return nf->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t null_factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info)
+{
+ struct null_audio_factory *nf = (struct null_audio_factory*)f;
+
+ PJ_ASSERT_RETURN(index < nf->dev_count, PJMEDIA_EAUD_INVDEV);
+
+ pj_memcpy(info, &nf->dev_info[index].info, sizeof(*info));
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t null_factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param)
+{
+ struct null_audio_factory *nf = (struct null_audio_factory*)f;
+ struct null_audio_dev_info *di = &nf->dev_info[index];
+
+ PJ_ASSERT_RETURN(index < nf->dev_count, PJMEDIA_EAUD_INVDEV);
+
+ pj_bzero(param, sizeof(*param));
+ if (di->info.input_count && di->info.output_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param->rec_id = index;
+ param->play_id = index;
+ } else if (di->info.input_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE;
+ param->rec_id = index;
+ param->play_id = PJMEDIA_AUD_INVALID_DEV;
+ } else if (di->info.output_count) {
+ param->dir = PJMEDIA_DIR_PLAYBACK;
+ param->play_id = index;
+ param->rec_id = PJMEDIA_AUD_INVALID_DEV;
+ } else {
+ return PJMEDIA_EAUD_INVDEV;
+ }
+
+ /* Set the mandatory settings here */
+ /* The values here are just some examples */
+ param->clock_rate = di->info.default_samples_per_sec;
+ param->channel_count = 1;
+ param->samples_per_frame = di->info.default_samples_per_sec * 20 / 1000;
+ param->bits_per_sample = 16;
+
+ /* Set the device capabilities here */
+ param->flags = 0;
+
+ return PJ_SUCCESS;
+}
+
+/* API: create stream */
+static pj_status_t null_factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm)
+{
+ struct null_audio_factory *nf = (struct null_audio_factory*)f;
+ pj_pool_t *pool;
+ struct null_audio_stream *strm;
+
+ /* Create and Initialize stream descriptor */
+ pool = pj_pool_create(nf->pf, "null_audio-dev", 1000, 1000, NULL);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ strm = PJ_POOL_ZALLOC_T(pool, struct null_audio_stream);
+ pj_memcpy(&strm->param, param, sizeof(*param));
+ strm->pool = pool;
+ strm->rec_cb = rec_cb;
+ strm->play_cb = play_cb;
+ strm->user_data = user_data;
+
+ /* Create player stream here */
+ if (param->dir & PJMEDIA_DIR_PLAYBACK) {
+ }
+
+ /* Create capture stream here */
+ if (param->dir & PJMEDIA_DIR_CAPTURE) {
+ }
+
+ /* Apply the remaining settings */
+ /* Below is an example if you want to set the output volume */
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
+ null_stream_set_cap(&strm->base,
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &param->output_vol);
+ }
+
+ /* Done */
+ strm->base.op = &stream_op;
+ *p_aud_strm = &strm->base;
+
+ return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t null_stream_get_param(pjmedia_aud_stream *s,
+ pjmedia_aud_param *pi)
+{
+ struct null_audio_stream *strm = (struct null_audio_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+ /* Example: Update the volume setting */
+ if (null_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &pi->output_vol) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t null_stream_get_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ void *pval)
+{
+ struct null_audio_stream *strm = (struct null_audio_stream*)s;
+
+ PJ_UNUSED_ARG(strm);
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ /* Example: Get the output's volume setting */
+ if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING)
+ {
+ /* Output volume setting */
+ *(unsigned*)pval = 0; // retrieve output device's volume here
+ return PJ_SUCCESS;
+ } else {
+ return PJMEDIA_EAUD_INVCAP;
+ }
+}
+
+/* API: set capability */
+static pj_status_t null_stream_set_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ const void *pval)
+{
+ struct null_audio_stream *strm = (struct null_audio_stream*)s;
+
+ PJ_UNUSED_ARG(strm);
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ /* Example */
+ if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING)
+ {
+ /* Output volume setting */
+ // set output's volume level here
+ return PJ_SUCCESS;
+ }
+
+ return PJMEDIA_EAUD_INVCAP;
+}
+
+/* API: Start stream. */
+static pj_status_t null_stream_start(pjmedia_aud_stream *strm)
+{
+ struct null_audio_stream *stream = (struct null_audio_stream*)strm;
+
+ PJ_UNUSED_ARG(stream);
+
+ PJ_LOG(4, (THIS_FILE, "Starting null audio stream"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t null_stream_stop(pjmedia_aud_stream *strm)
+{
+ struct null_audio_stream *stream = (struct null_audio_stream*)strm;
+
+ PJ_UNUSED_ARG(stream);
+
+ PJ_LOG(4, (THIS_FILE, "Stopping null audio stream"));
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t null_stream_destroy(pjmedia_aud_stream *strm)
+{
+ struct null_audio_stream *stream = (struct null_audio_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+ null_stream_stop(strm);
+
+ pj_pool_release(stream->pool);
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_AUDIO_DEV_HAS_NULL_AUDIO */
diff --git a/pjmedia/src/pjmedia-audiodev/pa_dev.c b/pjmedia/src/pjmedia-audiodev/pa_dev.c
new file mode 100644
index 0000000..fb745a9
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/pa_dev.c
@@ -0,0 +1,1284 @@
+/* $Id: pa_dev.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia-audiodev/audiodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/string.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+
+#include <portaudio.h>
+
+#define THIS_FILE "pa_dev.c"
+#define DRIVER_NAME "PA"
+
+/* Enable call to PaUtil_SetDebugPrintFunction, but this is not always
+ * available across all PortAudio versions (?)
+ */
+/*#define USE_PA_DEBUG_PRINT */
+
+struct pa_aud_factory
+{
+ pjmedia_aud_dev_factory base;
+ pj_pool_factory *pf;
+ pj_pool_t *pool;
+};
+
+
+/*
+ * Sound stream descriptor.
+ * This struct may be used for both unidirectional or bidirectional sound
+ * streams.
+ */
+struct pa_aud_stream
+{
+ pjmedia_aud_stream base;
+
+ pj_pool_t *pool;
+ pj_str_t name;
+ pjmedia_dir dir;
+ int play_id;
+ int rec_id;
+ int bytes_per_sample;
+ pj_uint32_t samples_per_sec;
+ unsigned samples_per_frame;
+ int channel_count;
+
+ PaStream *rec_strm;
+ PaStream *play_strm;
+
+ void *user_data;
+ pjmedia_aud_rec_cb rec_cb;
+ pjmedia_aud_play_cb play_cb;
+
+ pj_timestamp play_timestamp;
+ pj_timestamp rec_timestamp;
+ pj_uint32_t underflow;
+ pj_uint32_t overflow;
+
+ pj_bool_t quit_flag;
+
+ pj_bool_t rec_thread_exited;
+ pj_bool_t rec_thread_initialized;
+ pj_thread_desc rec_thread_desc;
+ pj_thread_t *rec_thread;
+
+ pj_bool_t play_thread_exited;
+ pj_bool_t play_thread_initialized;
+ pj_thread_desc play_thread_desc;
+ pj_thread_t *play_thread;
+
+ /* Sometime the record callback does not return framesize as configured
+ * (e.g: in OSS), while this module must guarantee returning framesize
+ * as configured in the creation settings. In this case, we need a buffer
+ * for the recorded samples.
+ */
+ pj_int16_t *rec_buf;
+ unsigned rec_buf_count;
+
+ /* Sometime the player callback does not request framesize as configured
+ * (e.g: in Linux OSS) while sound device will always get samples from
+ * the other component as many as configured samples_per_frame.
+ */
+ pj_int16_t *play_buf;
+ unsigned play_buf_count;
+};
+
+
+/* Factory prototypes */
+static pj_status_t pa_init(pjmedia_aud_dev_factory *f);
+static pj_status_t pa_destroy(pjmedia_aud_dev_factory *f);
+static pj_status_t pa_refresh(pjmedia_aud_dev_factory *f);
+static unsigned pa_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t pa_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info);
+static pj_status_t pa_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param);
+static pj_status_t pa_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm);
+
+/* Stream prototypes */
+static pj_status_t strm_get_param(pjmedia_aud_stream *strm,
+ pjmedia_aud_param *param);
+static pj_status_t strm_get_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ void *value);
+static pj_status_t strm_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value);
+static pj_status_t strm_start(pjmedia_aud_stream *strm);
+static pj_status_t strm_stop(pjmedia_aud_stream *strm);
+static pj_status_t strm_destroy(pjmedia_aud_stream *strm);
+
+
+static pjmedia_aud_dev_factory_op pa_op =
+{
+ &pa_init,
+ &pa_destroy,
+ &pa_get_dev_count,
+ &pa_get_dev_info,
+ &pa_default_param,
+ &pa_create_stream,
+ &pa_refresh
+};
+
+static pjmedia_aud_stream_op pa_strm_op =
+{
+ &strm_get_param,
+ &strm_get_cap,
+ &strm_set_cap,
+ &strm_start,
+ &strm_stop,
+ &strm_destroy
+};
+
+
+
+static int PaRecorderCallback(const void *input,
+ void *output,
+ unsigned long frameCount,
+ const PaStreamCallbackTimeInfo* timeInfo,
+ PaStreamCallbackFlags statusFlags,
+ void *userData )
+{
+ struct pa_aud_stream *stream = (struct pa_aud_stream*) userData;
+ pj_status_t status = 0;
+ unsigned nsamples;
+
+ PJ_UNUSED_ARG(output);
+ PJ_UNUSED_ARG(timeInfo);
+
+ if (stream->quit_flag)
+ goto on_break;
+
+ if (input == NULL)
+ return paContinue;
+
+ /* Known cases of callback's thread:
+ * - The thread may be changed in the middle of a session, e.g: in MacOS
+ * it happens when plugging/unplugging headphone.
+ * - The same thread may be reused in consecutive sessions. The first
+ * session will leave TLS set, but release the TLS data address,
+ * so the second session must re-register the callback's thread.
+ */
+ if (stream->rec_thread_initialized == 0 || !pj_thread_is_registered())
+ {
+ pj_bzero(stream->rec_thread_desc, sizeof(pj_thread_desc));
+ status = pj_thread_register("pa_rec", stream->rec_thread_desc,
+ &stream->rec_thread);
+ stream->rec_thread_initialized = 1;
+ PJ_LOG(5,(THIS_FILE, "Recorder thread started"));
+ }
+
+ if (statusFlags & paInputUnderflow)
+ ++stream->underflow;
+ if (statusFlags & paInputOverflow)
+ ++stream->overflow;
+
+ /* Calculate number of samples we've got */
+ nsamples = frameCount * stream->channel_count + stream->rec_buf_count;
+
+ if (nsamples >= stream->samples_per_frame)
+ {
+ /* If buffer is not empty, combine the buffer with the just incoming
+ * samples, then call put_frame.
+ */
+ if (stream->rec_buf_count) {
+ unsigned chunk_count = 0;
+ pjmedia_frame frame;
+
+ chunk_count = stream->samples_per_frame - stream->rec_buf_count;
+ pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count,
+ (pj_int16_t*)input, chunk_count);
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = (void*) stream->rec_buf;
+ frame.size = stream->samples_per_frame * stream->bytes_per_sample;
+ frame.timestamp.u64 = stream->rec_timestamp.u64;
+ frame.bit_info = 0;
+
+ status = (*stream->rec_cb)(stream->user_data, &frame);
+
+ input = (pj_int16_t*) input + chunk_count;
+ nsamples -= stream->samples_per_frame;
+ stream->rec_buf_count = 0;
+ stream->rec_timestamp.u64 += stream->samples_per_frame /
+ stream->channel_count;
+ }
+
+ /* Give all frames we have */
+ while (nsamples >= stream->samples_per_frame && status == 0) {
+ pjmedia_frame frame;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = (void*) input;
+ frame.size = stream->samples_per_frame * stream->bytes_per_sample;
+ frame.timestamp.u64 = stream->rec_timestamp.u64;
+ frame.bit_info = 0;
+
+ status = (*stream->rec_cb)(stream->user_data, &frame);
+
+ input = (pj_int16_t*) input + stream->samples_per_frame;
+ nsamples -= stream->samples_per_frame;
+ stream->rec_timestamp.u64 += stream->samples_per_frame /
+ stream->channel_count;
+ }
+
+ /* Store the remaining samples into the buffer */
+ if (nsamples && status == 0) {
+ stream->rec_buf_count = nsamples;
+ pjmedia_copy_samples(stream->rec_buf, (pj_int16_t*)input,
+ nsamples);
+ }
+
+ } else {
+ /* Not enough samples, let's just store them in the buffer */
+ pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count,
+ (pj_int16_t*)input,
+ frameCount * stream->channel_count);
+ stream->rec_buf_count += frameCount * stream->channel_count;
+ }
+
+ if (status==0)
+ return paContinue;
+
+on_break:
+ stream->rec_thread_exited = 1;
+ return paAbort;
+}
+
+static int PaPlayerCallback( const void *input,
+ void *output,
+ unsigned long frameCount,
+ const PaStreamCallbackTimeInfo* timeInfo,
+ PaStreamCallbackFlags statusFlags,
+ void *userData )
+{
+ struct pa_aud_stream *stream = (struct pa_aud_stream*) userData;
+ pj_status_t status = 0;
+ unsigned nsamples_req = frameCount * stream->channel_count;
+
+ PJ_UNUSED_ARG(input);
+ PJ_UNUSED_ARG(timeInfo);
+
+ if (stream->quit_flag)
+ goto on_break;
+
+ if (output == NULL)
+ return paContinue;
+
+ /* Known cases of callback's thread:
+ * - The thread may be changed in the middle of a session, e.g: in MacOS
+ * it happens when plugging/unplugging headphone.
+ * - The same thread may be reused in consecutive sessions. The first
+ * session will leave TLS set, but release the TLS data address,
+ * so the second session must re-register the callback's thread.
+ */
+ if (stream->play_thread_initialized == 0 || !pj_thread_is_registered())
+ {
+ pj_bzero(stream->play_thread_desc, sizeof(pj_thread_desc));
+ status = pj_thread_register("portaudio", stream->play_thread_desc,
+ &stream->play_thread);
+ stream->play_thread_initialized = 1;
+ PJ_LOG(5,(THIS_FILE, "Player thread started"));
+ }
+
+ if (statusFlags & paOutputUnderflow)
+ ++stream->underflow;
+ if (statusFlags & paOutputOverflow)
+ ++stream->overflow;
+
+
+ /* Check if any buffered samples */
+ if (stream->play_buf_count) {
+ /* samples buffered >= requested by sound device */
+ if (stream->play_buf_count >= nsamples_req) {
+ pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
+ nsamples_req);
+ stream->play_buf_count -= nsamples_req;
+ pjmedia_move_samples(stream->play_buf,
+ stream->play_buf + nsamples_req,
+ stream->play_buf_count);
+ nsamples_req = 0;
+
+ return paContinue;
+ }
+
+ /* samples buffered < requested by sound device */
+ pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
+ stream->play_buf_count);
+ nsamples_req -= stream->play_buf_count;
+ output = (pj_int16_t*)output + stream->play_buf_count;
+ stream->play_buf_count = 0;
+ }
+
+ /* Fill output buffer as requested */
+ while (nsamples_req && status == 0) {
+ if (nsamples_req >= stream->samples_per_frame) {
+ pjmedia_frame frame;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = output;
+ frame.size = stream->samples_per_frame * stream->bytes_per_sample;
+ frame.timestamp.u64 = stream->play_timestamp.u64;
+ frame.bit_info = 0;
+
+ status = (*stream->play_cb)(stream->user_data, &frame);
+ if (status != PJ_SUCCESS)
+ goto on_break;
+
+ if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
+ pj_bzero(frame.buf, frame.size);
+
+ nsamples_req -= stream->samples_per_frame;
+ output = (pj_int16_t*)output + stream->samples_per_frame;
+ } else {
+ pjmedia_frame frame;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = stream->play_buf;
+ frame.size = stream->samples_per_frame * stream->bytes_per_sample;
+ frame.timestamp.u64 = stream->play_timestamp.u64;
+ frame.bit_info = 0;
+
+ status = (*stream->play_cb)(stream->user_data, &frame);
+ if (status != PJ_SUCCESS)
+ goto on_break;
+
+ if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
+ pj_bzero(frame.buf, frame.size);
+
+ pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
+ nsamples_req);
+ stream->play_buf_count = stream->samples_per_frame - nsamples_req;
+ pjmedia_move_samples(stream->play_buf,
+ stream->play_buf+nsamples_req,
+ stream->play_buf_count);
+ nsamples_req = 0;
+ }
+
+ stream->play_timestamp.u64 += stream->samples_per_frame /
+ stream->channel_count;
+ }
+
+ if (status==0)
+ return paContinue;
+
+on_break:
+ stream->play_thread_exited = 1;
+ return paAbort;
+}
+
+
+static int PaRecorderPlayerCallback( const void *input,
+ void *output,
+ unsigned long frameCount,
+ const PaStreamCallbackTimeInfo* timeInfo,
+ PaStreamCallbackFlags statusFlags,
+ void *userData )
+{
+ int rc;
+
+ rc = PaRecorderCallback(input, output, frameCount, timeInfo,
+ statusFlags, userData);
+ if (rc != paContinue)
+ return rc;
+
+ rc = PaPlayerCallback(input, output, frameCount, timeInfo,
+ statusFlags, userData);
+ return rc;
+}
+
+#ifdef USE_PA_DEBUG_PRINT
+/* Logging callback from PA */
+static void pa_log_cb(const char *log)
+{
+ PJ_LOG(5,(THIS_FILE, "PA message: %s", log));
+}
+
+/* We should include pa_debugprint.h for this, but the header
+ * is not available publicly. :(
+ */
+typedef void (*PaUtilLogCallback ) (const char *log);
+void PaUtil_SetDebugPrintFunction(PaUtilLogCallback cb);
+#endif
+
+
+/*
+ * Init PortAudio audio driver.
+ */
+pjmedia_aud_dev_factory* pjmedia_pa_factory(pj_pool_factory *pf)
+{
+ struct pa_aud_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "portaudio", 64, 64, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct pa_aud_factory);
+ f->pf = pf;
+ f->pool = pool;
+ f->base.op = &pa_op;
+
+ return &f->base;
+}
+
+
+/* API: Init factory */
+static pj_status_t pa_init(pjmedia_aud_dev_factory *f)
+{
+ int err;
+
+ PJ_UNUSED_ARG(f);
+
+#ifdef USE_PA_DEBUG_PRINT
+ PaUtil_SetDebugPrintFunction(&pa_log_cb);
+#endif
+
+ err = Pa_Initialize();
+
+ PJ_LOG(4,(THIS_FILE,
+ "PortAudio sound library initialized, status=%d", err));
+ PJ_LOG(4,(THIS_FILE, "PortAudio host api count=%d",
+ Pa_GetHostApiCount()));
+ PJ_LOG(4,(THIS_FILE, "Sound device count=%d",
+ pa_get_dev_count(f)));
+
+ return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
+}
+
+
+/* API: Destroy factory */
+static pj_status_t pa_destroy(pjmedia_aud_dev_factory *f)
+{
+ struct pa_aud_factory *pa = (struct pa_aud_factory*)f;
+ pj_pool_t *pool;
+ int err;
+
+ PJ_LOG(4,(THIS_FILE, "PortAudio sound library shutting down.."));
+
+ err = Pa_Terminate();
+
+ pool = pa->pool;
+ pa->pool = NULL;
+ pj_pool_release(pool);
+
+ return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
+}
+
+
+/* API: Refresh the device list. */
+static pj_status_t pa_refresh(pjmedia_aud_dev_factory *f)
+{
+ PJ_UNUSED_ARG(f);
+ return PJ_ENOTSUP;
+}
+
+
+/* API: Get device count. */
+static unsigned pa_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+ int count = Pa_GetDeviceCount();
+ PJ_UNUSED_ARG(f);
+ return count < 0 ? 0 : count;
+}
+
+
+/* API: Get device info. */
+static pj_status_t pa_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info)
+{
+ const PaDeviceInfo *pa_info;
+
+ PJ_UNUSED_ARG(f);
+
+ pa_info = Pa_GetDeviceInfo(index);
+ if (!pa_info)
+ return PJMEDIA_EAUD_INVDEV;
+
+ pj_bzero(info, sizeof(*info));
+ strncpy(info->name, pa_info->name, sizeof(info->name));
+ info->name[sizeof(info->name)-1] = '\0';
+ info->input_count = pa_info->maxInputChannels;
+ info->output_count = pa_info->maxOutputChannels;
+ info->default_samples_per_sec = (unsigned)pa_info->defaultSampleRate;
+ strncpy(info->driver, DRIVER_NAME, sizeof(info->driver));
+ info->driver[sizeof(info->driver)-1] = '\0';
+ info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: fill in with default parameter. */
+static pj_status_t pa_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param)
+{
+ pjmedia_aud_dev_info adi;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(f);
+
+ status = pa_get_dev_info(f, index, &adi);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_bzero(param, sizeof(*param));
+ if (adi.input_count && adi.output_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param->rec_id = index;
+ param->play_id = index;
+ } else if (adi.input_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE;
+ param->rec_id = index;
+ param->play_id = PJMEDIA_AUD_INVALID_DEV;
+ } else if (adi.output_count) {
+ param->dir = PJMEDIA_DIR_PLAYBACK;
+ param->play_id = index;
+ param->rec_id = PJMEDIA_AUD_INVALID_DEV;
+ } else {
+ return PJMEDIA_EAUD_INVDEV;
+ }
+
+ param->clock_rate = adi.default_samples_per_sec;
+ param->channel_count = 1;
+ param->samples_per_frame = adi.default_samples_per_sec * 20 / 1000;
+ param->bits_per_sample = 16;
+ param->flags = adi.caps;
+ param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+ param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+
+ return PJ_SUCCESS;
+}
+
+
+/* Internal: Get PortAudio default input device ID */
+static int pa_get_default_input_dev(int channel_count)
+{
+ int i, count;
+
+ /* Special for Windows - try to use the DirectSound implementation
+ * first since it provides better latency.
+ */
+#if PJMEDIA_PREFER_DIRECT_SOUND
+ if (Pa_HostApiTypeIdToHostApiIndex(paDirectSound) >= 0) {
+ const PaHostApiInfo *pHI;
+ int index = Pa_HostApiTypeIdToHostApiIndex(paDirectSound);
+ pHI = Pa_GetHostApiInfo(index);
+ if (pHI) {
+ const PaDeviceInfo *paDevInfo = NULL;
+ paDevInfo = Pa_GetDeviceInfo(pHI->defaultInputDevice);
+ if (paDevInfo && paDevInfo->maxInputChannels >= channel_count)
+ return pHI->defaultInputDevice;
+ }
+ }
+#endif
+
+ /* Enumerate the host api's for the default devices, and return
+ * the device with suitable channels.
+ */
+ count = Pa_GetHostApiCount();
+ for (i=0; i < count; ++i) {
+ const PaHostApiInfo *pHAInfo;
+
+ pHAInfo = Pa_GetHostApiInfo(i);
+ if (!pHAInfo)
+ continue;
+
+ if (pHAInfo->defaultInputDevice >= 0) {
+ const PaDeviceInfo *paDevInfo;
+
+ paDevInfo = Pa_GetDeviceInfo(pHAInfo->defaultInputDevice);
+
+ if (paDevInfo->maxInputChannels >= channel_count)
+ return pHAInfo->defaultInputDevice;
+ }
+ }
+
+ /* If still no device is found, enumerate all devices */
+ count = Pa_GetDeviceCount();
+ for (i=0; i<count; ++i) {
+ const PaDeviceInfo *paDevInfo;
+
+ paDevInfo = Pa_GetDeviceInfo(i);
+ if (paDevInfo->maxInputChannels >= channel_count)
+ return i;
+ }
+
+ return -1;
+}
+
+/* Internal: Get PortAudio default output device ID */
+static int pa_get_default_output_dev(int channel_count)
+{
+ int i, count;
+
+ /* Special for Windows - try to use the DirectSound implementation
+ * first since it provides better latency.
+ */
+#if PJMEDIA_PREFER_DIRECT_SOUND
+ if (Pa_HostApiTypeIdToHostApiIndex(paDirectSound) >= 0) {
+ const PaHostApiInfo *pHI;
+ int index = Pa_HostApiTypeIdToHostApiIndex(paDirectSound);
+ pHI = Pa_GetHostApiInfo(index);
+ if (pHI) {
+ const PaDeviceInfo *paDevInfo = NULL;
+ paDevInfo = Pa_GetDeviceInfo(pHI->defaultOutputDevice);
+ if (paDevInfo && paDevInfo->maxOutputChannels >= channel_count)
+ return pHI->defaultOutputDevice;
+ }
+ }
+#endif
+
+ /* Enumerate the host api's for the default devices, and return
+ * the device with suitable channels.
+ */
+ count = Pa_GetHostApiCount();
+ for (i=0; i < count; ++i) {
+ const PaHostApiInfo *pHAInfo;
+
+ pHAInfo = Pa_GetHostApiInfo(i);
+ if (!pHAInfo)
+ continue;
+
+ if (pHAInfo->defaultOutputDevice >= 0) {
+ const PaDeviceInfo *paDevInfo;
+
+ paDevInfo = Pa_GetDeviceInfo(pHAInfo->defaultOutputDevice);
+
+ if (paDevInfo->maxOutputChannels >= channel_count)
+ return pHAInfo->defaultOutputDevice;
+ }
+ }
+
+ /* If still no device is found, enumerate all devices */
+ count = Pa_GetDeviceCount();
+ for (i=0; i<count; ++i) {
+ const PaDeviceInfo *paDevInfo;
+
+ paDevInfo = Pa_GetDeviceInfo(i);
+ if (paDevInfo->maxOutputChannels >= channel_count)
+ return i;
+ }
+
+ return -1;
+}
+
+
+/* Internal: create capture/recorder stream */
+static pj_status_t create_rec_stream( struct pa_aud_factory *pa,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_snd_strm)
+{
+ pj_pool_t *pool;
+ pjmedia_aud_dev_index rec_id;
+ struct pa_aud_stream *stream;
+ PaStreamParameters inputParam;
+ int sampleFormat;
+ const PaDeviceInfo *paDevInfo = NULL;
+ const PaHostApiInfo *paHostApiInfo = NULL;
+ unsigned paFrames, paRate, paLatency;
+ const PaStreamInfo *paSI;
+ PaError err;
+
+ PJ_ASSERT_RETURN(rec_cb && p_snd_strm, PJ_EINVAL);
+
+ rec_id = param->rec_id;
+ if (rec_id < 0) {
+ rec_id = pa_get_default_input_dev(param->channel_count);
+ if (rec_id < 0) {
+ /* No such device. */
+ return PJMEDIA_EAUD_NODEFDEV;
+ }
+ }
+
+ paDevInfo = Pa_GetDeviceInfo(rec_id);
+ if (!paDevInfo) {
+ /* Assumed it is "No such device" error. */
+ return PJMEDIA_EAUD_INVDEV;
+ }
+
+ if (param->bits_per_sample == 8)
+ sampleFormat = paUInt8;
+ else if (param->bits_per_sample == 16)
+ sampleFormat = paInt16;
+ else if (param->bits_per_sample == 32)
+ sampleFormat = paInt32;
+ else
+ return PJMEDIA_EAUD_SAMPFORMAT;
+
+ pool = pj_pool_create(pa->pf, "recstrm", 1024, 1024, NULL);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
+ stream->pool = pool;
+ pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
+ stream->dir = PJMEDIA_DIR_CAPTURE;
+ stream->rec_id = rec_id;
+ stream->play_id = -1;
+ stream->user_data = user_data;
+ stream->samples_per_sec = param->clock_rate;
+ stream->samples_per_frame = param->samples_per_frame;
+ stream->bytes_per_sample = param->bits_per_sample / 8;
+ stream->channel_count = param->channel_count;
+ stream->rec_cb = rec_cb;
+
+ stream->rec_buf = (pj_int16_t*)pj_pool_alloc(pool,
+ stream->samples_per_frame * stream->bytes_per_sample);
+ stream->rec_buf_count = 0;
+
+ pj_bzero(&inputParam, sizeof(inputParam));
+ inputParam.device = rec_id;
+ inputParam.channelCount = param->channel_count;
+ inputParam.hostApiSpecificStreamInfo = NULL;
+ inputParam.sampleFormat = sampleFormat;
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)
+ inputParam.suggestedLatency = param->input_latency_ms / 1000.0;
+ else
+ inputParam.suggestedLatency = PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000.0;
+
+ paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi);
+
+ /* Frames in PortAudio is number of samples in a single channel */
+ paFrames = param->samples_per_frame / param->channel_count;
+
+ err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL,
+ param->clock_rate, paFrames,
+ paClipOff, &PaRecorderCallback, stream );
+ if (err != paNoError) {
+ pj_pool_release(pool);
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
+ }
+
+ paSI = Pa_GetStreamInfo(stream->rec_strm);
+ paRate = (unsigned)paSI->sampleRate;
+ paLatency = (unsigned)(paSI->inputLatency * 1000);
+
+ PJ_LOG(5,(THIS_FILE, "Opened device %s (%s) for recording, sample "
+ "rate=%d, ch=%d, "
+ "bits=%d, %d samples per frame, latency=%d ms",
+ paDevInfo->name, paHostApiInfo->name,
+ paRate, param->channel_count,
+ param->bits_per_sample, param->samples_per_frame,
+ paLatency));
+
+ *p_snd_strm = &stream->base;
+ return PJ_SUCCESS;
+}
+
+
+/* Internal: create playback stream */
+static pj_status_t create_play_stream(struct pa_aud_factory *pa,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_snd_strm)
+{
+ pj_pool_t *pool;
+ pjmedia_aud_dev_index play_id;
+ struct pa_aud_stream *stream;
+ PaStreamParameters outputParam;
+ int sampleFormat;
+ const PaDeviceInfo *paDevInfo = NULL;
+ const PaHostApiInfo *paHostApiInfo = NULL;
+ const PaStreamInfo *paSI;
+ unsigned paFrames, paRate, paLatency;
+ PaError err;
+
+ PJ_ASSERT_RETURN(play_cb && p_snd_strm, PJ_EINVAL);
+
+ play_id = param->play_id;
+ if (play_id < 0) {
+ play_id = pa_get_default_output_dev(param->channel_count);
+ if (play_id < 0) {
+ /* No such device. */
+ return PJMEDIA_EAUD_NODEFDEV;
+ }
+ }
+
+ paDevInfo = Pa_GetDeviceInfo(play_id);
+ if (!paDevInfo) {
+ /* Assumed it is "No such device" error. */
+ return PJMEDIA_EAUD_INVDEV;
+ }
+
+ if (param->bits_per_sample == 8)
+ sampleFormat = paUInt8;
+ else if (param->bits_per_sample == 16)
+ sampleFormat = paInt16;
+ else if (param->bits_per_sample == 32)
+ sampleFormat = paInt32;
+ else
+ return PJMEDIA_EAUD_SAMPFORMAT;
+
+ pool = pj_pool_create(pa->pf, "playstrm", 1024, 1024, NULL);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
+ stream->pool = pool;
+ pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
+ stream->dir = PJMEDIA_DIR_PLAYBACK;
+ stream->play_id = play_id;
+ stream->rec_id = -1;
+ stream->user_data = user_data;
+ stream->samples_per_sec = param->clock_rate;
+ stream->samples_per_frame = param->samples_per_frame;
+ stream->bytes_per_sample = param->bits_per_sample / 8;
+ stream->channel_count = param->channel_count;
+ stream->play_cb = play_cb;
+
+ stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool,
+ stream->samples_per_frame *
+ stream->bytes_per_sample);
+ stream->play_buf_count = 0;
+
+ pj_bzero(&outputParam, sizeof(outputParam));
+ outputParam.device = play_id;
+ outputParam.channelCount = param->channel_count;
+ outputParam.hostApiSpecificStreamInfo = NULL;
+ outputParam.sampleFormat = sampleFormat;
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)
+ outputParam.suggestedLatency=param->output_latency_ms / 1000.0;
+ else
+ outputParam.suggestedLatency=PJMEDIA_SND_DEFAULT_PLAY_LATENCY/1000.0;
+
+ paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi);
+
+ /* Frames in PortAudio is number of samples in a single channel */
+ paFrames = param->samples_per_frame / param->channel_count;
+
+ err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam,
+ param->clock_rate, paFrames,
+ paClipOff, &PaPlayerCallback, stream );
+ if (err != paNoError) {
+ pj_pool_release(pool);
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
+ }
+
+ paSI = Pa_GetStreamInfo(stream->play_strm);
+ paRate = (unsigned)(paSI->sampleRate);
+ paLatency = (unsigned)(paSI->outputLatency * 1000);
+
+ PJ_LOG(5,(THIS_FILE, "Opened device %d: %s(%s) for playing, sample rate=%d"
+ ", ch=%d, "
+ "bits=%d, %d samples per frame, latency=%d ms",
+ play_id, paDevInfo->name, paHostApiInfo->name,
+ paRate, param->channel_count,
+ param->bits_per_sample, param->samples_per_frame,
+ paLatency));
+
+ *p_snd_strm = &stream->base;
+
+ return PJ_SUCCESS;
+}
+
+
+/* Internal: Create both player and recorder stream */
+static pj_status_t create_bidir_stream(struct pa_aud_factory *pa,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_snd_strm)
+{
+ pj_pool_t *pool;
+ pjmedia_aud_dev_index rec_id, play_id;
+ struct pa_aud_stream *stream;
+ PaStream *paStream = NULL;
+ PaStreamParameters inputParam;
+ PaStreamParameters outputParam;
+ int sampleFormat;
+ const PaDeviceInfo *paRecDevInfo = NULL;
+ const PaDeviceInfo *paPlayDevInfo = NULL;
+ const PaHostApiInfo *paRecHostApiInfo = NULL;
+ const PaHostApiInfo *paPlayHostApiInfo = NULL;
+ const PaStreamInfo *paSI;
+ unsigned paFrames, paRate, paInputLatency, paOutputLatency;
+ PaError err;
+
+ PJ_ASSERT_RETURN(play_cb && rec_cb && p_snd_strm, PJ_EINVAL);
+
+ rec_id = param->rec_id;
+ if (rec_id < 0) {
+ rec_id = pa_get_default_input_dev(param->channel_count);
+ if (rec_id < 0) {
+ /* No such device. */
+ return PJMEDIA_EAUD_NODEFDEV;
+ }
+ }
+
+ paRecDevInfo = Pa_GetDeviceInfo(rec_id);
+ if (!paRecDevInfo) {
+ /* Assumed it is "No such device" error. */
+ return PJMEDIA_EAUD_INVDEV;
+ }
+
+ play_id = param->play_id;
+ if (play_id < 0) {
+ play_id = pa_get_default_output_dev(param->channel_count);
+ if (play_id < 0) {
+ /* No such device. */
+ return PJMEDIA_EAUD_NODEFDEV;
+ }
+ }
+
+ paPlayDevInfo = Pa_GetDeviceInfo(play_id);
+ if (!paPlayDevInfo) {
+ /* Assumed it is "No such device" error. */
+ return PJMEDIA_EAUD_INVDEV;
+ }
+
+
+ if (param->bits_per_sample == 8)
+ sampleFormat = paUInt8;
+ else if (param->bits_per_sample == 16)
+ sampleFormat = paInt16;
+ else if (param->bits_per_sample == 32)
+ sampleFormat = paInt32;
+ else
+ return PJMEDIA_EAUD_SAMPFORMAT;
+
+ pool = pj_pool_create(pa->pf, "sndstream", 1024, 1024, NULL);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
+ stream->pool = pool;
+ pj_strdup2_with_null(pool, &stream->name, paRecDevInfo->name);
+ stream->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ stream->play_id = play_id;
+ stream->rec_id = rec_id;
+ stream->user_data = user_data;
+ stream->samples_per_sec = param->clock_rate;
+ stream->samples_per_frame = param->samples_per_frame;
+ stream->bytes_per_sample = param->bits_per_sample / 8;
+ stream->channel_count = param->channel_count;
+ stream->rec_cb = rec_cb;
+ stream->play_cb = play_cb;
+
+ stream->rec_buf = (pj_int16_t*)pj_pool_alloc(pool,
+ stream->samples_per_frame * stream->bytes_per_sample);
+ stream->rec_buf_count = 0;
+
+ stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool,
+ stream->samples_per_frame * stream->bytes_per_sample);
+ stream->play_buf_count = 0;
+
+ pj_bzero(&inputParam, sizeof(inputParam));
+ inputParam.device = rec_id;
+ inputParam.channelCount = param->channel_count;
+ inputParam.hostApiSpecificStreamInfo = NULL;
+ inputParam.sampleFormat = sampleFormat;
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)
+ inputParam.suggestedLatency = param->input_latency_ms / 1000.0;
+ else
+ inputParam.suggestedLatency = PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000.0;
+
+ paRecHostApiInfo = Pa_GetHostApiInfo(paRecDevInfo->hostApi);
+
+ pj_bzero(&outputParam, sizeof(outputParam));
+ outputParam.device = play_id;
+ outputParam.channelCount = param->channel_count;
+ outputParam.hostApiSpecificStreamInfo = NULL;
+ outputParam.sampleFormat = sampleFormat;
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)
+ outputParam.suggestedLatency=param->output_latency_ms / 1000.0;
+ else
+ outputParam.suggestedLatency=PJMEDIA_SND_DEFAULT_PLAY_LATENCY/1000.0;
+
+ paPlayHostApiInfo = Pa_GetHostApiInfo(paPlayDevInfo->hostApi);
+
+ /* Frames in PortAudio is number of samples in a single channel */
+ paFrames = param->samples_per_frame / param->channel_count;
+
+ /* If both input and output are on the same device, open a single stream
+ * for both input and output.
+ */
+ if (rec_id == play_id) {
+ err = Pa_OpenStream( &paStream, &inputParam, &outputParam,
+ param->clock_rate, paFrames,
+ paClipOff, &PaRecorderPlayerCallback, stream );
+ if (err == paNoError) {
+ /* Set play stream and record stream to the same stream */
+ stream->play_strm = stream->rec_strm = paStream;
+ }
+ } else {
+ err = -1;
+ }
+
+ /* .. otherwise if input and output are on the same device, OR if we're
+ * unable to open a bidirectional stream, then open two separate
+ * input and output stream.
+ */
+ if (paStream == NULL) {
+ /* Open input stream */
+ err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL,
+ param->clock_rate, paFrames,
+ paClipOff, &PaRecorderCallback, stream );
+ if (err == paNoError) {
+ /* Open output stream */
+ err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam,
+ param->clock_rate, paFrames,
+ paClipOff, &PaPlayerCallback, stream );
+ if (err != paNoError)
+ Pa_CloseStream(stream->rec_strm);
+ }
+ }
+
+ if (err != paNoError) {
+ pj_pool_release(pool);
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
+ }
+
+ paSI = Pa_GetStreamInfo(stream->rec_strm);
+ paRate = (unsigned)(paSI->sampleRate);
+ paInputLatency = (unsigned)(paSI->inputLatency * 1000);
+ paSI = Pa_GetStreamInfo(stream->play_strm);
+ paOutputLatency = (unsigned)(paSI->outputLatency * 1000);
+
+ PJ_LOG(5,(THIS_FILE, "Opened device %s(%s)/%s(%s) for recording and "
+ "playback, sample rate=%d, ch=%d, "
+ "bits=%d, %d samples per frame, input latency=%d ms, "
+ "output latency=%d ms",
+ paRecDevInfo->name, paRecHostApiInfo->name,
+ paPlayDevInfo->name, paPlayHostApiInfo->name,
+ paRate, param->channel_count,
+ param->bits_per_sample, param->samples_per_frame,
+ paInputLatency, paOutputLatency));
+
+ *p_snd_strm = &stream->base;
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: create stream */
+static pj_status_t pa_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm)
+{
+ struct pa_aud_factory *pa = (struct pa_aud_factory*)f;
+ pj_status_t status;
+
+ if (param->dir == PJMEDIA_DIR_CAPTURE) {
+ status = create_rec_stream(pa, param, rec_cb, user_data, p_aud_strm);
+ } else if (param->dir == PJMEDIA_DIR_PLAYBACK) {
+ status = create_play_stream(pa, param, play_cb, user_data, p_aud_strm);
+ } else if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
+ status = create_bidir_stream(pa, param, rec_cb, play_cb, user_data,
+ p_aud_strm);
+ } else {
+ return PJ_EINVAL;
+ }
+
+ if (status != PJ_SUCCESS)
+ return status;
+
+ (*p_aud_strm)->op = &pa_strm_op;
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Get stream parameters */
+static pj_status_t strm_get_param(pjmedia_aud_stream *s,
+ pjmedia_aud_param *pi)
+{
+ struct pa_aud_stream *strm = (struct pa_aud_stream*)s;
+ const PaStreamInfo *paPlaySI = NULL, *paRecSI = NULL;
+
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+ PJ_ASSERT_RETURN(strm->play_strm || strm->rec_strm, PJ_EINVALIDOP);
+
+ if (strm->play_strm) {
+ paPlaySI = Pa_GetStreamInfo(strm->play_strm);
+ }
+ if (strm->rec_strm) {
+ paRecSI = Pa_GetStreamInfo(strm->rec_strm);
+ }
+
+ pj_bzero(pi, sizeof(*pi));
+ pi->dir = strm->dir;
+ pi->play_id = strm->play_id;
+ pi->rec_id = strm->rec_id;
+ pi->clock_rate = (unsigned)(paPlaySI ? paPlaySI->sampleRate :
+ paRecSI->sampleRate);
+ pi->channel_count = strm->channel_count;
+ pi->samples_per_frame = strm->samples_per_frame;
+ pi->bits_per_sample = strm->bytes_per_sample * 8;
+ if (paRecSI) {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+ pi->input_latency_ms = (unsigned)(paRecSI ? paRecSI->inputLatency *
+ 1000 : 0);
+ }
+ if (paPlaySI) {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+ pi->output_latency_ms = (unsigned)(paPlaySI? paPlaySI->outputLatency *
+ 1000 : 0);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: get capability */
+static pj_status_t strm_get_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ void *pval)
+{
+ struct pa_aud_stream *strm = (struct pa_aud_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && strm->rec_strm) {
+ const PaStreamInfo *si = Pa_GetStreamInfo(strm->rec_strm);
+ if (!si)
+ return PJMEDIA_EAUD_SYSERR;
+
+ *(unsigned*)pval = (unsigned)(si->inputLatency * 1000);
+ return PJ_SUCCESS;
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY && strm->play_strm) {
+ const PaStreamInfo *si = Pa_GetStreamInfo(strm->play_strm);
+ if (!si)
+ return PJMEDIA_EAUD_SYSERR;
+
+ *(unsigned*)pval = (unsigned)(si->outputLatency * 1000);
+ return PJ_SUCCESS;
+ } else {
+ return PJMEDIA_EAUD_INVCAP;
+ }
+}
+
+
+/* API: set capability */
+static pj_status_t strm_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value)
+{
+ PJ_UNUSED_ARG(strm);
+ PJ_UNUSED_ARG(cap);
+ PJ_UNUSED_ARG(value);
+
+ /* Nothing is supported */
+ return PJMEDIA_EAUD_INVCAP;
+}
+
+
+/* API: start stream. */
+static pj_status_t strm_start(pjmedia_aud_stream *s)
+{
+ struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
+ int err = 0;
+
+ PJ_LOG(5,(THIS_FILE, "Starting %s stream..", stream->name.ptr));
+
+ if (stream->play_strm)
+ err = Pa_StartStream(stream->play_strm);
+
+ if (err==0 && stream->rec_strm && stream->rec_strm != stream->play_strm) {
+ err = Pa_StartStream(stream->rec_strm);
+ if (err != 0)
+ Pa_StopStream(stream->play_strm);
+ }
+
+ PJ_LOG(5,(THIS_FILE, "Done, status=%d", err));
+
+ return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
+}
+
+
+/* API: stop stream. */
+static pj_status_t strm_stop(pjmedia_aud_stream *s)
+{
+ struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
+ int i, err = 0;
+
+ stream->quit_flag = 1;
+ for (i=0; !stream->rec_thread_exited && i<100; ++i)
+ pj_thread_sleep(10);
+ for (i=0; !stream->play_thread_exited && i<100; ++i)
+ pj_thread_sleep(10);
+
+ pj_thread_sleep(1);
+
+ PJ_LOG(5,(THIS_FILE, "Stopping stream.."));
+
+ if (stream->play_strm)
+ err = Pa_StopStream(stream->play_strm);
+
+ if (stream->rec_strm && stream->rec_strm != stream->play_strm)
+ err = Pa_StopStream(stream->rec_strm);
+
+ stream->play_thread_initialized = 0;
+ stream->rec_thread_initialized = 0;
+
+ PJ_LOG(5,(THIS_FILE, "Done, status=%d", err));
+
+ return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
+}
+
+
+/* API: destroy stream. */
+static pj_status_t strm_destroy(pjmedia_aud_stream *s)
+{
+ struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
+ int i, err = 0;
+
+ stream->quit_flag = 1;
+ for (i=0; !stream->rec_thread_exited && i<100; ++i) {
+ pj_thread_sleep(1);
+ }
+ for (i=0; !stream->play_thread_exited && i<100; ++i) {
+ pj_thread_sleep(1);
+ }
+
+ PJ_LOG(5,(THIS_FILE, "Closing %.*s: %lu underflow, %lu overflow",
+ (int)stream->name.slen,
+ stream->name.ptr,
+ stream->underflow, stream->overflow));
+
+ if (stream->play_strm)
+ err = Pa_CloseStream(stream->play_strm);
+
+ if (stream->rec_strm && stream->rec_strm != stream->play_strm)
+ err = Pa_CloseStream(stream->rec_strm);
+
+ pj_pool_release(stream->pool);
+
+ return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO */
+
diff --git a/pjmedia/src/pjmedia-audiodev/s60_g729_bitstream.h b/pjmedia/src/pjmedia-audiodev/s60_g729_bitstream.h
new file mode 100644
index 0000000..ae13bb1
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/s60_g729_bitstream.h
@@ -0,0 +1,171 @@
+#ifndef __BITSTREAM_H_
+#define __BITSTREAM_H_
+
+#define KPackedFrameLen 10
+#define KUnpackedFrameLen 22
+
+// Below values are taken from the APS design document
+const TUint8 KG729FullPayloadBits[] = { 8, 10, 8, 1, 13, 4, 7, 5, 13, 4, 7 };
+const TUint KNumFullFrameParams = 11;
+const TUint8 KG729SIDPayloadBits[] = { 1, 5, 4, 5 };
+const TUint KNumSIDFrameParams = 4;
+
+/*!
+ @class TBitStream
+
+ @discussion Provides compression from 16-bit-word-aligned G.729 audio frames
+ (used in S60 G.729 DSP codec) to 8-bit stream, and vice versa.
+ */
+class TBitStream
+ {
+public:
+ /*!
+ @function TBitStream
+
+ @discussion Constructor
+ */
+ TBitStream():iDes(iData,KUnpackedFrameLen){}
+ /*!
+ @function CompressG729Frame
+
+ @discussion Compress either a 22-byte G.729 full rate frame to 10 bytes
+ or a 8-byte G.729 Annex.B SID frame to 2 bytes.
+ @param aSrc Reference to the uncompressed source frame data
+ @param aIsSIDFrame True if the source is a SID frame
+ @result a reference to the compressed frame
+ */
+ const TDesC8& CompressG729Frame( const TDesC8& aSrc, TBool aIsSIDFrame = EFalse );
+
+ /*!
+ @function ExpandG729Frame
+
+ @discussion Expand a 10-byte G.729 full rate frame to 22 bytes
+ or a 2-byte G.729 Annex.B SID frame to 8(22) bytes.
+ @param aSrc Reference to the compressed source frame data
+ @param aIsSIDFrame True if the source is a SID frame
+ @result a reference to a descriptor representing the uncompressed frame.
+ Note that SID frames are zero-padded to 22 bytes as well.
+ */
+ const TDesC8& ExpandG729Frame( const TDesC8& aSrc, TBool aIsSIDFrame = EFalse );
+
+private:
+ void Compress( TUint8 aValue, TUint8 aNumOfBits );
+ void Expand( const TUint8* aSrc, TInt aDstIdx, TUint8 aNumOfBits );
+
+private:
+ TUint8 iData[KUnpackedFrameLen];
+ TPtr8 iDes;
+ TInt iIdx;
+ TInt iBitOffset;
+ };
+
+
+const TDesC8& TBitStream::CompressG729Frame( const TDesC8& aSrc, TBool aIsSIDFrame )
+ {
+ // reset data
+ iDes.FillZ(iDes.MaxLength());
+ iIdx = iBitOffset = 0;
+
+ TInt numParams = (aIsSIDFrame) ? KNumSIDFrameParams : KNumFullFrameParams;
+ const TUint8* p = const_cast<TUint8*>(aSrc.Ptr());
+
+ for(TInt i = 0, pIdx = 0; i < numParams; i++, pIdx += 2)
+ {
+ TUint8 paramBits = (aIsSIDFrame) ? KG729SIDPayloadBits[i] : KG729FullPayloadBits[i];
+ if(paramBits > 8)
+ {
+ Compress(p[pIdx+1], paramBits - 8); // msb
+ paramBits = 8;
+ }
+ Compress(p[pIdx], paramBits); // lsb
+ }
+
+ if( iBitOffset )
+ iIdx++;
+
+ iDes.SetLength(iIdx);
+ return iDes;
+ }
+
+
+const TDesC8& TBitStream::ExpandG729Frame( const TDesC8& aSrc, TBool aIsSIDFrame )
+ {
+ // reset data
+ iDes.FillZ(iDes.MaxLength());
+ iIdx = iBitOffset = 0;
+
+ TInt numParams = (aIsSIDFrame) ? KNumSIDFrameParams : KNumFullFrameParams;
+ const TUint8* p = const_cast<TUint8*>(aSrc.Ptr());
+
+ for(TInt i = 0, dIdx = 0; i < numParams; i++, dIdx += 2)
+ {
+ TUint8 paramBits = (aIsSIDFrame) ? KG729SIDPayloadBits[i] : KG729FullPayloadBits[i];
+ if(paramBits > 8)
+ {
+ Expand(p, dIdx+1, paramBits - 8); // msb
+ paramBits = 8;
+ }
+ Expand(p, dIdx, paramBits); // lsb
+ }
+
+ iDes.SetLength(KUnpackedFrameLen);
+ return iDes;
+ }
+
+
+void TBitStream::Compress( TUint8 aValue, TUint8 aNumOfBits )
+ {
+ // clear bits that will be discarded
+ aValue &= (0xff >> (8 - aNumOfBits));
+
+ // calculate required bitwise left shift
+ TInt shl = 8 - (iBitOffset + aNumOfBits);
+
+ if (shl == 0) // no shift required
+ {
+ iData[iIdx++] |= aValue;
+ iBitOffset = 0;
+ }
+ else if (shl > 0) // bits fit into current byte
+ {
+ iData[iIdx] |= (aValue << shl);
+ iBitOffset += aNumOfBits;
+ }
+ else
+ {
+ iBitOffset = -shl;
+ iData[iIdx] |= (aValue >> iBitOffset); // right shift
+ iData[++iIdx] |= (aValue << (8-iBitOffset)); // push remaining bits to next byte
+ }
+ }
+
+
+void TBitStream::Expand( const TUint8* aSrc, TInt aDstIdx, TUint8 aNumOfBits )
+ {
+ TUint8 aValue = aSrc[iIdx] & (0xff >> iBitOffset);
+
+ // calculate required bitwise right shift
+ TInt shr = 8 - (iBitOffset + aNumOfBits);
+
+ if (shr == 0) // no shift required
+ {
+ iData[aDstIdx] = aValue;
+ iIdx++;
+ iBitOffset = 0;
+ }
+ else if (shr > 0) // right shift
+ {
+ iData[aDstIdx] = (aValue >> shr);
+ iBitOffset += aNumOfBits;
+ }
+ else // shift left and take remaining bits from the next src byte
+ {
+ iBitOffset = -shr;
+ iData[aDstIdx] = aValue << iBitOffset;
+ iData[aDstIdx] |= aSrc[++iIdx] >> (8 - iBitOffset);
+ }
+ }
+
+#endif // __BITSTREAM_H_
+
+// eof
diff --git a/pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp b/pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp
new file mode 100644
index 0000000..d2c6564
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp
@@ -0,0 +1,1929 @@
+/* $Id: symb_aps_dev.cpp 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia-audiodev/audiodev_imp.h>
+#include <pjmedia-audiodev/errno.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pjmedia/resample.h>
+#include <pjmedia/stereo.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/os.h>
+#include <pj/string.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
+
+#include <e32msgqueue.h>
+#include <sounddevice.h>
+#include <APSClientSession.h>
+#include <pjmedia-codec/amr_helper.h>
+
+/* Pack/unpack G.729 frame of S60 DSP codec, taken from:
+ * http://wiki.forum.nokia.com/index.php/TSS000776_-_Payload_conversion_for_G.729_audio_format
+ */
+#include "s60_g729_bitstream.h"
+
+
+#define THIS_FILE "symb_aps_dev.c"
+#define BITS_PER_SAMPLE 16
+
+
+#if 1
+# define TRACE_(st) PJ_LOG(3, st)
+#else
+# define TRACE_(st)
+#endif
+
+
+/* App UID to open global APS queues to communicate with the APS server. */
+extern TPtrC APP_UID;
+
+/* APS G.711 frame length */
+static pj_uint8_t aps_g711_frame_len;
+
+
+/* APS factory */
+struct aps_factory
+{
+ pjmedia_aud_dev_factory base;
+ pj_pool_t *pool;
+ pj_pool_factory *pf;
+ pjmedia_aud_dev_info dev_info;
+};
+
+
+/* Forward declaration of CPjAudioEngine */
+class CPjAudioEngine;
+
+
+/* APS stream. */
+struct aps_stream
+{
+ // Base
+ pjmedia_aud_stream base; /**< Base class. */
+
+ // Pool
+ pj_pool_t *pool; /**< Memory pool. */
+
+ // Common settings.
+ pjmedia_aud_param param; /**< Stream param. */
+ pjmedia_aud_rec_cb rec_cb; /**< Record callback. */
+ pjmedia_aud_play_cb play_cb; /**< Playback callback. */
+ void *user_data; /**< Application data. */
+
+ // Audio engine
+ CPjAudioEngine *engine; /**< Internal engine. */
+
+ pj_timestamp ts_play; /**< Playback timestamp.*/
+ pj_timestamp ts_rec; /**< Record timestamp. */
+
+ pj_int16_t *play_buf; /**< Playback buffer. */
+ pj_uint16_t play_buf_len; /**< Playback buffer length. */
+ pj_uint16_t play_buf_start; /**< Playback buffer start index. */
+ pj_int16_t *rec_buf; /**< Record buffer. */
+ pj_uint16_t rec_buf_len; /**< Record buffer length. */
+ void *strm_data; /**< Stream data. */
+
+ /* Resampling is needed, in case audio device is opened with clock rate
+ * other than 8kHz (only for PCM format).
+ */
+ pjmedia_resample *play_resample; /**< Resampler for playback. */
+ pjmedia_resample *rec_resample; /**< Resampler for recording */
+ pj_uint16_t resample_factor; /**< Resample factor, requested
+ clock rate / 8000 */
+
+ /* When stream is working in PCM format, where the samples may need to be
+ * resampled from/to different clock rate and/or channel count, PCM buffer
+ * is needed to perform such resampling operations.
+ */
+ pj_int16_t *pcm_buf; /**< PCM buffer. */
+};
+
+
+/* Prototypes */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f);
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info);
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param);
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm);
+
+static pj_status_t stream_get_param(pjmedia_aud_stream *strm,
+ pjmedia_aud_param *param);
+static pj_status_t stream_get_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ void *value);
+static pj_status_t stream_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value);
+static pj_status_t stream_start(pjmedia_aud_stream *strm);
+static pj_status_t stream_stop(pjmedia_aud_stream *strm);
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm);
+
+
+/* Operations */
+static pjmedia_aud_dev_factory_op factory_op =
+{
+ &factory_init,
+ &factory_destroy,
+ &factory_get_dev_count,
+ &factory_get_dev_info,
+ &factory_default_param,
+ &factory_create_stream,
+ &factory_refresh
+};
+
+static pjmedia_aud_stream_op stream_op =
+{
+ &stream_get_param,
+ &stream_get_cap,
+ &stream_set_cap,
+ &stream_start,
+ &stream_stop,
+ &stream_destroy
+};
+
+
+/****************************************************************************
+ * Internal APS Engine
+ */
+
+/*
+ * Utility: print sound device error
+ */
+static void snd_perror(const char *title, TInt rc)
+{
+ PJ_LOG(1,(THIS_FILE, "%s (error code=%d)", title, rc));
+}
+
+/*
+ * Utility: wait for specified time.
+ */
+static void snd_wait(unsigned ms)
+{
+ TTime start, now;
+
+ start.UniversalTime();
+ do {
+ pj_symbianos_poll(-1, ms);
+ now.UniversalTime();
+ } while (now.MicroSecondsFrom(start) < ms * 1000);
+}
+
+typedef void(*PjAudioCallback)(TAPSCommBuffer &buf, void *user_data);
+
+/**
+ * Abstract class for handler of callbacks from APS client.
+ */
+class MQueueHandlerObserver
+{
+public:
+ MQueueHandlerObserver(PjAudioCallback RecCb_, PjAudioCallback PlayCb_,
+ void *UserData_)
+ : RecCb(RecCb_), PlayCb(PlayCb_), UserData(UserData_)
+ {}
+
+ virtual void InputStreamInitialized(const TInt aStatus) = 0;
+ virtual void OutputStreamInitialized(const TInt aStatus) = 0;
+ virtual void NotifyError(const TInt aError) = 0;
+
+public:
+ PjAudioCallback RecCb;
+ PjAudioCallback PlayCb;
+ void *UserData;
+};
+
+/**
+ * Handler for communication and data queue.
+ */
+class CQueueHandler : public CActive
+{
+public:
+ // Types of queue handler
+ enum TQueueHandlerType {
+ ERecordCommQueue,
+ EPlayCommQueue,
+ ERecordQueue,
+ EPlayQueue
+ };
+
+ // The order corresponds to the APS Server state, do not change!
+ enum TState {
+ EAPSPlayerInitialize = 1,
+ EAPSRecorderInitialize = 2,
+ EAPSPlayData = 3,
+ EAPSRecordData = 4,
+ EAPSPlayerInitComplete = 5,
+ EAPSRecorderInitComplete = 6
+ };
+
+ static CQueueHandler* NewL(MQueueHandlerObserver* aObserver,
+ RMsgQueue<TAPSCommBuffer>* aQ,
+ RMsgQueue<TAPSCommBuffer>* aWriteQ,
+ TQueueHandlerType aType)
+ {
+ CQueueHandler* self = new (ELeave) CQueueHandler(aObserver, aQ, aWriteQ,
+ aType);
+ CleanupStack::PushL(self);
+ self->ConstructL();
+ CleanupStack::Pop(self);
+ return self;
+ }
+
+ // Destructor
+ ~CQueueHandler() { Cancel(); }
+
+ // Start listening queue event
+ void Start() {
+ iQ->NotifyDataAvailable(iStatus);
+ SetActive();
+ }
+
+private:
+ // Constructor
+ CQueueHandler(MQueueHandlerObserver* aObserver,
+ RMsgQueue<TAPSCommBuffer>* aQ,
+ RMsgQueue<TAPSCommBuffer>* aWriteQ,
+ TQueueHandlerType aType)
+ : CActive(CActive::EPriorityHigh),
+ iQ(aQ), iWriteQ(aWriteQ), iObserver(aObserver), iType(aType)
+ {
+ CActiveScheduler::Add(this);
+
+ // use lower priority for comm queues
+ if ((iType == ERecordCommQueue) || (iType == EPlayCommQueue))
+ SetPriority(CActive::EPriorityStandard);
+ }
+
+ // Second phase constructor
+ void ConstructL() {}
+
+ // Inherited from CActive
+ void DoCancel() { iQ->CancelDataAvailable(); }
+
+ void RunL() {
+ if (iStatus != KErrNone) {
+ iObserver->NotifyError(iStatus.Int());
+ return;
+ }
+
+ TAPSCommBuffer buffer;
+ TInt ret = iQ->Receive(buffer);
+
+ if (ret != KErrNone) {
+ iObserver->NotifyError(ret);
+ return;
+ }
+
+ switch (iType) {
+ case ERecordQueue:
+ if (buffer.iCommand == EAPSRecordData) {
+ iObserver->RecCb(buffer, iObserver->UserData);
+ } else {
+ iObserver->NotifyError(buffer.iStatus);
+ }
+ break;
+
+ // Callbacks from the APS main thread
+ case EPlayCommQueue:
+ switch (buffer.iCommand) {
+ case EAPSPlayData:
+ if (buffer.iStatus == KErrUnderflow) {
+ iObserver->PlayCb(buffer, iObserver->UserData);
+ iWriteQ->Send(buffer);
+ }
+ break;
+ case EAPSPlayerInitialize:
+ iObserver->NotifyError(buffer.iStatus);
+ break;
+ case EAPSPlayerInitComplete:
+ iObserver->OutputStreamInitialized(buffer.iStatus);
+ break;
+ case EAPSRecorderInitComplete:
+ iObserver->InputStreamInitialized(buffer.iStatus);
+ break;
+ default:
+ iObserver->NotifyError(buffer.iStatus);
+ break;
+ }
+ break;
+
+ // Callbacks from the APS recorder thread
+ case ERecordCommQueue:
+ switch (buffer.iCommand) {
+ // The APS recorder thread will only report errors
+ // through this handler. All other callbacks will be
+ // sent from the APS main thread through EPlayCommQueue
+ case EAPSRecorderInitialize:
+ case EAPSRecordData:
+ default:
+ iObserver->NotifyError(buffer.iStatus);
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ // issue next request
+ iQ->NotifyDataAvailable(iStatus);
+ SetActive();
+ }
+
+ TInt RunError(TInt) {
+ return 0;
+ }
+
+ // Data
+ RMsgQueue<TAPSCommBuffer> *iQ; // (not owned)
+ RMsgQueue<TAPSCommBuffer> *iWriteQ; // (not owned)
+ MQueueHandlerObserver *iObserver; // (not owned)
+ TQueueHandlerType iType;
+};
+
+/*
+ * Audio setting for CPjAudioEngine.
+ */
+class CPjAudioSetting
+{
+public:
+ TFourCC fourcc;
+ TAPSCodecMode mode;
+ TBool plc;
+ TBool vad;
+ TBool cng;
+ TBool loudspk;
+};
+
+/*
+ * Implementation: Symbian Input & Output Stream.
+ */
+class CPjAudioEngine : public CBase, MQueueHandlerObserver
+{
+public:
+ enum State
+ {
+ STATE_NULL,
+ STATE_INITIALIZING,
+ STATE_READY,
+ STATE_STREAMING,
+ STATE_PENDING_STOP
+ };
+
+ ~CPjAudioEngine();
+
+ static CPjAudioEngine *NewL(struct aps_stream *parent_strm,
+ PjAudioCallback rec_cb,
+ PjAudioCallback play_cb,
+ void *user_data,
+ const CPjAudioSetting &setting);
+
+ TInt StartL();
+ void Stop();
+
+ TInt ActivateSpeaker(TBool active);
+
+ TInt SetVolume(TInt vol) { return iSession.SetVolume(vol); }
+ TInt GetVolume() { return iSession.Volume(); }
+ TInt GetMaxVolume() { return iSession.MaxVolume(); }
+
+ TInt SetGain(TInt gain) { return iSession.SetGain(gain); }
+ TInt GetGain() { return iSession.Gain(); }
+ TInt GetMaxGain() { return iSession.MaxGain(); }
+
+private:
+ CPjAudioEngine(struct aps_stream *parent_strm,
+ PjAudioCallback rec_cb,
+ PjAudioCallback play_cb,
+ void *user_data,
+ const CPjAudioSetting &setting);
+ void ConstructL();
+
+ TInt InitPlayL();
+ TInt InitRecL();
+ TInt StartStreamL();
+ void Deinit();
+
+ // Inherited from MQueueHandlerObserver
+ virtual void InputStreamInitialized(const TInt aStatus);
+ virtual void OutputStreamInitialized(const TInt aStatus);
+ virtual void NotifyError(const TInt aError);
+
+ TBool session_opened;
+ State state_;
+ struct aps_stream *parentStrm_;
+ CPjAudioSetting setting_;
+
+ RAPSSession iSession;
+ TAPSInitSettings iPlaySettings;
+ TAPSInitSettings iRecSettings;
+
+ RMsgQueue<TAPSCommBuffer> iReadQ;
+ RMsgQueue<TAPSCommBuffer> iReadCommQ;
+ TBool readq_opened;
+ RMsgQueue<TAPSCommBuffer> iWriteQ;
+ RMsgQueue<TAPSCommBuffer> iWriteCommQ;
+ TBool writeq_opened;
+
+ CQueueHandler *iPlayCommHandler;
+ CQueueHandler *iRecCommHandler;
+ CQueueHandler *iRecHandler;
+};
+
+
+CPjAudioEngine* CPjAudioEngine::NewL(struct aps_stream *parent_strm,
+ PjAudioCallback rec_cb,
+ PjAudioCallback play_cb,
+ void *user_data,
+ const CPjAudioSetting &setting)
+{
+ CPjAudioEngine* self = new (ELeave) CPjAudioEngine(parent_strm,
+ rec_cb, play_cb,
+ user_data,
+ setting);
+ CleanupStack::PushL(self);
+ self->ConstructL();
+ CleanupStack::Pop(self);
+ return self;
+}
+
+CPjAudioEngine::CPjAudioEngine(struct aps_stream *parent_strm,
+ PjAudioCallback rec_cb,
+ PjAudioCallback play_cb,
+ void *user_data,
+ const CPjAudioSetting &setting)
+ : MQueueHandlerObserver(rec_cb, play_cb, user_data),
+ session_opened(EFalse),
+ state_(STATE_NULL),
+ parentStrm_(parent_strm),
+ setting_(setting),
+ readq_opened(EFalse),
+ writeq_opened(EFalse),
+ iPlayCommHandler(0),
+ iRecCommHandler(0),
+ iRecHandler(0)
+{
+}
+
+CPjAudioEngine::~CPjAudioEngine()
+{
+ Deinit();
+
+ TRACE_((THIS_FILE, "Sound device destroyed"));
+}
+
+TInt CPjAudioEngine::InitPlayL()
+{
+ TInt err = iSession.InitializePlayer(iPlaySettings);
+ if (err != KErrNone) {
+ Deinit();
+ snd_perror("Failed to initialize player", err);
+ return err;
+ }
+
+ // Open message queues for the output stream
+ TBuf<128> buf2 = iPlaySettings.iGlobal;
+ buf2.Append(_L("PlayQueue"));
+ TBuf<128> buf3 = iPlaySettings.iGlobal;
+ buf3.Append(_L("PlayCommQueue"));
+
+ while (iWriteQ.OpenGlobal(buf2))
+ User::After(10);
+ while (iWriteCommQ.OpenGlobal(buf3))
+ User::After(10);
+
+ writeq_opened = ETrue;
+
+ // Construct message queue handler
+ iPlayCommHandler = CQueueHandler::NewL(this, &iWriteCommQ, &iWriteQ,
+ CQueueHandler::EPlayCommQueue);
+
+ // Start observing APS callbacks on output stream message queue
+ iPlayCommHandler->Start();
+
+ return 0;
+}
+
+TInt CPjAudioEngine::InitRecL()
+{
+ // Initialize input stream device
+ TInt err = iSession.InitializeRecorder(iRecSettings);
+ if (err != KErrNone && err != KErrAlreadyExists) {
+ Deinit();
+ snd_perror("Failed to initialize recorder", err);
+ return err;
+ }
+
+ TBuf<128> buf1 = iRecSettings.iGlobal;
+ buf1.Append(_L("RecordQueue"));
+ TBuf<128> buf4 = iRecSettings.iGlobal;
+ buf4.Append(_L("RecordCommQueue"));
+
+ // Must wait for APS thread to finish creating message queues
+ // before we can open and use them.
+ while (iReadQ.OpenGlobal(buf1))
+ User::After(10);
+ while (iReadCommQ.OpenGlobal(buf4))
+ User::After(10);
+
+ readq_opened = ETrue;
+
+ // Construct message queue handlers
+ iRecHandler = CQueueHandler::NewL(this, &iReadQ, NULL,
+ CQueueHandler::ERecordQueue);
+ iRecCommHandler = CQueueHandler::NewL(this, &iReadCommQ, NULL,
+ CQueueHandler::ERecordCommQueue);
+
+ // Start observing APS callbacks from on input stream message queue
+ iRecHandler->Start();
+ iRecCommHandler->Start();
+
+ return 0;
+}
+
+TInt CPjAudioEngine::StartL()
+{
+ if (state_ == STATE_READY)
+ return StartStreamL();
+
+ PJ_ASSERT_RETURN(state_ == STATE_NULL, PJMEDIA_EAUD_INVOP);
+
+ if (!session_opened) {
+ TInt err = iSession.Connect();
+ if (err != KErrNone)
+ return err;
+ session_opened = ETrue;
+ }
+
+ // Even if only capturer are opened, playback thread of APS Server need
+ // to be run(?). Since some messages will be delivered via play comm queue.
+ state_ = STATE_INITIALIZING;
+
+ return InitPlayL();
+}
+
+void CPjAudioEngine::Stop()
+{
+ if (state_ == STATE_STREAMING) {
+ iSession.Stop();
+ state_ = STATE_READY;
+ TRACE_((THIS_FILE, "Sound device stopped"));
+ } else if (state_ == STATE_INITIALIZING) {
+ // Initialization is on progress, so let's set the state to
+ // STATE_PENDING_STOP to prevent it starting the stream.
+ state_ = STATE_PENDING_STOP;
+
+ // Then wait until initialization done.
+ while (state_ != STATE_READY && state_ != STATE_NULL)
+ pj_symbianos_poll(-1, 100);
+ }
+}
+
+void CPjAudioEngine::ConstructL()
+{
+ // Recorder settings
+ iRecSettings.iFourCC = setting_.fourcc;
+ iRecSettings.iGlobal = APP_UID;
+ iRecSettings.iPriority = TMdaPriority(100);
+ iRecSettings.iPreference = TMdaPriorityPreference(0x05210001);
+ iRecSettings.iSettings.iChannels = EMMFMono;
+ iRecSettings.iSettings.iSampleRate = EMMFSampleRate8000Hz;
+
+ // Player settings
+ iPlaySettings.iFourCC = setting_.fourcc;
+ iPlaySettings.iGlobal = APP_UID;
+ iPlaySettings.iPriority = TMdaPriority(100);
+ iPlaySettings.iPreference = TMdaPriorityPreference(0x05220001);
+ iPlaySettings.iSettings.iChannels = EMMFMono;
+ iPlaySettings.iSettings.iSampleRate = EMMFSampleRate8000Hz;
+ iPlaySettings.iSettings.iVolume = 0;
+
+ User::LeaveIfError(iSession.Connect());
+ session_opened = ETrue;
+}
+
+TInt CPjAudioEngine::StartStreamL()
+{
+ pj_assert(state_==STATE_READY || state_==STATE_INITIALIZING);
+
+ iSession.SetCng(setting_.cng);
+ iSession.SetVadMode(setting_.vad);
+ iSession.SetPlc(setting_.plc);
+ iSession.SetEncoderMode(setting_.mode);
+ iSession.SetDecoderMode(setting_.mode);
+ iSession.ActivateLoudspeaker(setting_.loudspk);
+
+ // Not only capture
+ if (parentStrm_->param.dir != PJMEDIA_DIR_CAPTURE) {
+ iSession.Write();
+ TRACE_((THIS_FILE, "Player started"));
+ }
+
+ // Not only playback
+ if (parentStrm_->param.dir != PJMEDIA_DIR_PLAYBACK) {
+ iSession.Read();
+ TRACE_((THIS_FILE, "Recorder started"));
+ }
+
+ state_ = STATE_STREAMING;
+
+ return 0;
+}
+
+void CPjAudioEngine::Deinit()
+{
+ Stop();
+
+ delete iRecHandler;
+ delete iPlayCommHandler;
+ delete iRecCommHandler;
+
+ if (session_opened) {
+ enum { APS_CLOSE_WAIT_TIME = 200 }; /* in msecs */
+
+ // On some devices, immediate closing after stopping may cause
+ // APS server panic KERN-EXEC 0, so let's wait for sometime before
+ // closing the client session.
+ snd_wait(APS_CLOSE_WAIT_TIME);
+
+ iSession.Close();
+ session_opened = EFalse;
+ }
+
+ if (readq_opened) {
+ iReadQ.Close();
+ iReadCommQ.Close();
+ readq_opened = EFalse;
+ }
+
+ if (writeq_opened) {
+ iWriteQ.Close();
+ iWriteCommQ.Close();
+ writeq_opened = EFalse;
+ }
+
+ state_ = STATE_NULL;
+}
+
+void CPjAudioEngine::InputStreamInitialized(const TInt aStatus)
+{
+ TRACE_((THIS_FILE, "Recorder initialized, err=%d", aStatus));
+
+ if (aStatus == KErrNone) {
+ // Don't start the stream since Stop() has been requested.
+ if (state_ != STATE_PENDING_STOP) {
+ StartStreamL();
+ } else {
+ state_ = STATE_READY;
+ }
+ } else {
+ Deinit();
+ }
+}
+
+void CPjAudioEngine::OutputStreamInitialized(const TInt aStatus)
+{
+ TRACE_((THIS_FILE, "Player initialized, err=%d", aStatus));
+
+ if (aStatus == KErrNone) {
+ if (parentStrm_->param.dir == PJMEDIA_DIR_PLAYBACK) {
+ // Don't start the stream since Stop() has been requested.
+ if (state_ != STATE_PENDING_STOP) {
+ StartStreamL();
+ } else {
+ state_ = STATE_READY;
+ }
+ } else
+ InitRecL();
+ } else {
+ Deinit();
+ }
+}
+
+void CPjAudioEngine::NotifyError(const TInt aError)
+{
+ Deinit();
+ snd_perror("Error from CQueueHandler", aError);
+}
+
+TInt CPjAudioEngine::ActivateSpeaker(TBool active)
+{
+ if (state_ == STATE_READY || state_ == STATE_STREAMING) {
+ iSession.ActivateLoudspeaker(active);
+ TRACE_((THIS_FILE, "Loudspeaker turned %s", (active? "on":"off")));
+ return KErrNone;
+ }
+ return KErrNotReady;
+}
+
+/****************************************************************************
+ * Internal APS callbacks for PCM format
+ */
+
+static void RecCbPcm(TAPSCommBuffer &buf, void *user_data)
+{
+ struct aps_stream *strm = (struct aps_stream*) user_data;
+
+ /* Buffer has to contain normal speech. */
+ pj_assert(buf.iBuffer[0] == 1 && buf.iBuffer[1] == 0);
+
+ /* Detect the recorder G.711 frame size, player frame size will follow
+ * this recorder frame size.
+ */
+ if (aps_g711_frame_len == 0) {
+ aps_g711_frame_len = buf.iBuffer.Length() < 160? 80 : 160;
+ TRACE_((THIS_FILE, "Detected APS G.711 frame size = %u samples",
+ aps_g711_frame_len));
+ }
+
+ /* Decode APS buffer (coded in G.711) and put the PCM result into rec_buf.
+ * Whenever rec_buf is full, call parent stream callback.
+ */
+ unsigned samples_processed = 0;
+
+ while (samples_processed < aps_g711_frame_len) {
+ unsigned samples_to_process;
+ unsigned samples_req;
+
+ samples_to_process = aps_g711_frame_len - samples_processed;
+ samples_req = (strm->param.samples_per_frame /
+ strm->param.channel_count /
+ strm->resample_factor) -
+ strm->rec_buf_len;
+ if (samples_to_process > samples_req)
+ samples_to_process = samples_req;
+
+ pjmedia_ulaw_decode(&strm->rec_buf[strm->rec_buf_len],
+ buf.iBuffer.Ptr() + 2 + samples_processed,
+ samples_to_process);
+
+ strm->rec_buf_len += samples_to_process;
+ samples_processed += samples_to_process;
+
+ /* Buffer is full, time to call parent callback */
+ if (strm->rec_buf_len == strm->param.samples_per_frame /
+ strm->param.channel_count /
+ strm->resample_factor)
+ {
+ pjmedia_frame f;
+
+ /* Need to resample clock rate? */
+ if (strm->rec_resample) {
+ unsigned resampled = 0;
+
+ while (resampled < strm->rec_buf_len) {
+ pjmedia_resample_run(strm->rec_resample,
+ &strm->rec_buf[resampled],
+ strm->pcm_buf +
+ resampled * strm->resample_factor);
+ resampled += 80;
+ }
+ f.buf = strm->pcm_buf;
+ } else {
+ f.buf = strm->rec_buf;
+ }
+
+ /* Need to convert channel count? */
+ if (strm->param.channel_count != 1) {
+ pjmedia_convert_channel_1ton((pj_int16_t*)f.buf,
+ (pj_int16_t*)f.buf,
+ strm->param.channel_count,
+ strm->param.samples_per_frame /
+ strm->param.channel_count,
+ 0);
+ }
+
+ /* Call parent callback */
+ f.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ f.size = strm->param.samples_per_frame << 1;
+ strm->rec_cb(strm->user_data, &f);
+ strm->rec_buf_len = 0;
+ }
+ }
+}
+
+static void PlayCbPcm(TAPSCommBuffer &buf, void *user_data)
+{
+ struct aps_stream *strm = (struct aps_stream*) user_data;
+ unsigned g711_frame_len = aps_g711_frame_len;
+
+ /* Init buffer attributes and header. */
+ buf.iCommand = CQueueHandler::EAPSPlayData;
+ buf.iStatus = 0;
+ buf.iBuffer.Zero();
+ buf.iBuffer.Append(1);
+ buf.iBuffer.Append(0);
+
+ /* Assume frame size is 10ms if frame size hasn't been known. */
+ if (g711_frame_len == 0)
+ g711_frame_len = 80;
+
+ /* Call parent stream callback to get PCM samples to play,
+ * encode the PCM samples into G.711 and put it into APS buffer.
+ */
+ unsigned samples_processed = 0;
+
+ while (samples_processed < g711_frame_len) {
+ /* Need more samples to play, time to call parent callback */
+ if (strm->play_buf_len == 0) {
+ pjmedia_frame f;
+ unsigned samples_got;
+
+ f.size = strm->param.samples_per_frame << 1;
+ if (strm->play_resample || strm->param.channel_count != 1)
+ f.buf = strm->pcm_buf;
+ else
+ f.buf = strm->play_buf;
+
+ /* Call parent callback */
+ strm->play_cb(strm->user_data, &f);
+ if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) {
+ pjmedia_zero_samples((pj_int16_t*)f.buf,
+ strm->param.samples_per_frame);
+ }
+
+ samples_got = strm->param.samples_per_frame /
+ strm->param.channel_count /
+ strm->resample_factor;
+
+ /* Need to convert channel count? */
+ if (strm->param.channel_count != 1) {
+ pjmedia_convert_channel_nto1((pj_int16_t*)f.buf,
+ (pj_int16_t*)f.buf,
+ strm->param.channel_count,
+ strm->param.samples_per_frame,
+ PJ_FALSE,
+ 0);
+ }
+
+ /* Need to resample clock rate? */
+ if (strm->play_resample) {
+ unsigned resampled = 0;
+
+ while (resampled < samples_got)
+ {
+ pjmedia_resample_run(strm->play_resample,
+ strm->pcm_buf +
+ resampled * strm->resample_factor,
+ &strm->play_buf[resampled]);
+ resampled += 80;
+ }
+ }
+
+ strm->play_buf_len = samples_got;
+ strm->play_buf_start = 0;
+ }
+
+ unsigned tmp;
+
+ tmp = PJ_MIN(strm->play_buf_len, g711_frame_len - samples_processed);
+ pjmedia_ulaw_encode((pj_uint8_t*)&strm->play_buf[strm->play_buf_start],
+ &strm->play_buf[strm->play_buf_start],
+ tmp);
+ buf.iBuffer.Append((TUint8*)&strm->play_buf[strm->play_buf_start], tmp);
+ samples_processed += tmp;
+ strm->play_buf_len -= tmp;
+ strm->play_buf_start += tmp;
+ }
+}
+
+/****************************************************************************
+ * Internal APS callbacks for non-PCM format
+ */
+
+static void RecCb(TAPSCommBuffer &buf, void *user_data)
+{
+ struct aps_stream *strm = (struct aps_stream*) user_data;
+ pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->rec_buf;
+
+ switch(strm->param.ext_fmt.id) {
+ case PJMEDIA_FORMAT_AMR:
+ {
+ const pj_uint8_t *p = (const pj_uint8_t*)buf.iBuffer.Ptr() + 1;
+ unsigned len = buf.iBuffer.Length() - 1;
+
+ pjmedia_frame_ext_append_subframe(frame, p, len << 3, 160);
+ if (frame->samples_cnt == strm->param.samples_per_frame) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ break;
+
+ case PJMEDIA_FORMAT_G729:
+ {
+ /* Check if we got a normal or SID frame. */
+ if (buf.iBuffer[0] != 0 || buf.iBuffer[1] != 0) {
+ enum { NORMAL_LEN = 22, SID_LEN = 8 };
+ TBitStream *bitstream = (TBitStream*)strm->strm_data;
+ unsigned src_len = buf.iBuffer.Length()- 2;
+
+ pj_assert(src_len == NORMAL_LEN || src_len == SID_LEN);
+
+ const TDesC8& p = bitstream->CompressG729Frame(
+ buf.iBuffer.Right(src_len),
+ src_len == SID_LEN);
+
+ pjmedia_frame_ext_append_subframe(frame, p.Ptr(),
+ p.Length() << 3, 80);
+ } else { /* We got null frame. */
+ pjmedia_frame_ext_append_subframe(frame, NULL, 0, 80);
+ }
+
+ if (frame->samples_cnt == strm->param.samples_per_frame) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ break;
+
+ case PJMEDIA_FORMAT_ILBC:
+ {
+ unsigned samples_got;
+
+ samples_got = strm->param.ext_fmt.bitrate == 15200? 160 : 240;
+
+ /* Check if we got a normal frame. */
+ if (buf.iBuffer[0] == 1 && buf.iBuffer[1] == 0) {
+ const pj_uint8_t *p = (const pj_uint8_t*)buf.iBuffer.Ptr() + 2;
+ unsigned len = buf.iBuffer.Length() - 2;
+
+ pjmedia_frame_ext_append_subframe(frame, p, len << 3,
+ samples_got);
+ } else { /* We got null frame. */
+ pjmedia_frame_ext_append_subframe(frame, NULL, 0, samples_got);
+ }
+
+ if (frame->samples_cnt == strm->param.samples_per_frame) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ break;
+
+ case PJMEDIA_FORMAT_PCMU:
+ case PJMEDIA_FORMAT_PCMA:
+ {
+ unsigned samples_processed = 0;
+
+ /* Make sure it is normal frame. */
+ pj_assert(buf.iBuffer[0] == 1 && buf.iBuffer[1] == 0);
+
+ /* Detect the recorder G.711 frame size, player frame size will
+ * follow this recorder frame size.
+ */
+ if (aps_g711_frame_len == 0) {
+ aps_g711_frame_len = buf.iBuffer.Length() < 160? 80 : 160;
+ TRACE_((THIS_FILE, "Detected APS G.711 frame size = %u samples",
+ aps_g711_frame_len));
+ }
+
+ /* Convert APS buffer format into pjmedia_frame_ext. Whenever
+ * samples count in the frame is equal to stream's samples per
+ * frame, call parent stream callback.
+ */
+ while (samples_processed < aps_g711_frame_len) {
+ unsigned tmp;
+ const pj_uint8_t *pb = (const pj_uint8_t*)buf.iBuffer.Ptr() +
+ 2 + samples_processed;
+
+ tmp = PJ_MIN(strm->param.samples_per_frame - frame->samples_cnt,
+ aps_g711_frame_len - samples_processed);
+
+ pjmedia_frame_ext_append_subframe(frame, pb, tmp << 3, tmp);
+ samples_processed += tmp;
+
+ if (frame->samples_cnt == strm->param.samples_per_frame) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void PlayCb(TAPSCommBuffer &buf, void *user_data)
+{
+ struct aps_stream *strm = (struct aps_stream*) user_data;
+ pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->play_buf;
+
+ /* Init buffer attributes and header. */
+ buf.iCommand = CQueueHandler::EAPSPlayData;
+ buf.iStatus = 0;
+ buf.iBuffer.Zero();
+
+ switch(strm->param.ext_fmt.id) {
+ case PJMEDIA_FORMAT_AMR:
+ {
+ if (frame->samples_cnt == 0) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
+ pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
+ frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
+ }
+
+ if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ pjmedia_frame_ext_subframe *sf;
+ unsigned samples_cnt;
+
+ sf = pjmedia_frame_ext_get_subframe(frame, 0);
+ samples_cnt = frame->samples_cnt / frame->subframe_cnt;
+
+ if (sf->data && sf->bitlen) {
+ /* AMR header for APS is one byte, the format (may be!):
+ * 0xxxxy00, where xxxx:frame type, y:not sure.
+ */
+ unsigned len = (sf->bitlen+7)>>3;
+ enum {SID_FT = 8 };
+ pj_uint8_t amr_header = 4, ft = SID_FT;
+
+ if (len >= pjmedia_codec_amrnb_framelen[0])
+ ft = pjmedia_codec_amr_get_mode2(PJ_TRUE, len);
+
+ amr_header |= ft << 3;
+ buf.iBuffer.Append(amr_header);
+
+ buf.iBuffer.Append((TUint8*)sf->data, len);
+ } else {
+ enum {NO_DATA_FT = 15 };
+ pj_uint8_t amr_header = 4 | (NO_DATA_FT << 3);
+
+ buf.iBuffer.Append(amr_header);
+ }
+
+ pjmedia_frame_ext_pop_subframes(frame, 1);
+
+ } else { /* PJMEDIA_FRAME_TYPE_NONE */
+ enum {NO_DATA_FT = 15 };
+ pj_uint8_t amr_header = 4 | (NO_DATA_FT << 3);
+
+ buf.iBuffer.Append(amr_header);
+
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ break;
+
+ case PJMEDIA_FORMAT_G729:
+ {
+ if (frame->samples_cnt == 0) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
+ pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
+ frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
+ }
+
+ if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ pjmedia_frame_ext_subframe *sf;
+ unsigned samples_cnt;
+
+ sf = pjmedia_frame_ext_get_subframe(frame, 0);
+ samples_cnt = frame->samples_cnt / frame->subframe_cnt;
+
+ if (sf->data && sf->bitlen) {
+ enum { NORMAL_LEN = 10, SID_LEN = 2 };
+ pj_bool_t sid_frame = ((sf->bitlen >> 3) == SID_LEN);
+ TBitStream *bitstream = (TBitStream*)strm->strm_data;
+ const TPtrC8 src(sf->data, sf->bitlen>>3);
+ const TDesC8 &dst = bitstream->ExpandG729Frame(src,
+ sid_frame);
+ if (sid_frame) {
+ buf.iBuffer.Append(2);
+ buf.iBuffer.Append(0);
+ } else {
+ buf.iBuffer.Append(1);
+ buf.iBuffer.Append(0);
+ }
+ buf.iBuffer.Append(dst);
+ } else {
+ buf.iBuffer.Append(2);
+ buf.iBuffer.Append(0);
+ buf.iBuffer.AppendFill(0, 22);
+ }
+
+ pjmedia_frame_ext_pop_subframes(frame, 1);
+
+ } else { /* PJMEDIA_FRAME_TYPE_NONE */
+ buf.iBuffer.Append(2);
+ buf.iBuffer.Append(0);
+ buf.iBuffer.AppendFill(0, 22);
+
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ break;
+
+ case PJMEDIA_FORMAT_ILBC:
+ {
+ if (frame->samples_cnt == 0) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
+ pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
+ frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
+ }
+
+ if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ pjmedia_frame_ext_subframe *sf;
+ unsigned samples_cnt;
+
+ sf = pjmedia_frame_ext_get_subframe(frame, 0);
+ samples_cnt = frame->samples_cnt / frame->subframe_cnt;
+
+ pj_assert((strm->param.ext_fmt.bitrate == 15200 &&
+ samples_cnt == 160) ||
+ (strm->param.ext_fmt.bitrate != 15200 &&
+ samples_cnt == 240));
+
+ if (sf->data && sf->bitlen) {
+ buf.iBuffer.Append(1);
+ buf.iBuffer.Append(0);
+ buf.iBuffer.Append((TUint8*)sf->data, sf->bitlen>>3);
+ } else {
+ buf.iBuffer.Append(0);
+ buf.iBuffer.Append(0);
+ }
+
+ pjmedia_frame_ext_pop_subframes(frame, 1);
+
+ } else { /* PJMEDIA_FRAME_TYPE_NONE */
+ buf.iBuffer.Append(0);
+ buf.iBuffer.Append(0);
+
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ break;
+
+ case PJMEDIA_FORMAT_PCMU:
+ case PJMEDIA_FORMAT_PCMA:
+ {
+ unsigned samples_ready = 0;
+ unsigned samples_req = aps_g711_frame_len;
+
+ /* Assume frame size is 10ms if frame size hasn't been known. */
+ if (samples_req == 0)
+ samples_req = 80;
+
+ buf.iBuffer.Append(1);
+ buf.iBuffer.Append(0);
+
+ /* Call parent stream callback to get samples to play. */
+ while (samples_ready < samples_req) {
+ if (frame->samples_cnt == 0) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
+ pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
+ frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
+ }
+
+ if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ pjmedia_frame_ext_subframe *sf;
+ unsigned samples_cnt;
+
+ sf = pjmedia_frame_ext_get_subframe(frame, 0);
+ samples_cnt = frame->samples_cnt / frame->subframe_cnt;
+ if (sf->data && sf->bitlen) {
+ buf.iBuffer.Append((TUint8*)sf->data, sf->bitlen>>3);
+ } else {
+ pj_uint8_t silc;
+ silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)?
+ pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0);
+ buf.iBuffer.AppendFill(silc, samples_cnt);
+ }
+ samples_ready += samples_cnt;
+
+ pjmedia_frame_ext_pop_subframes(frame, 1);
+
+ } else { /* PJMEDIA_FRAME_TYPE_NONE */
+ pj_uint8_t silc;
+
+ silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)?
+ pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0);
+ buf.iBuffer.AppendFill(silc, samples_req - samples_ready);
+
+ samples_ready = samples_req;
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+/****************************************************************************
+ * Factory operations
+ */
+
+/*
+ * C compatible declaration of APS factory.
+ */
+PJ_BEGIN_DECL
+PJ_DECL(pjmedia_aud_dev_factory*) pjmedia_aps_factory(pj_pool_factory *pf);
+PJ_END_DECL
+
+/*
+ * Init APS audio driver.
+ */
+PJ_DEF(pjmedia_aud_dev_factory*) pjmedia_aps_factory(pj_pool_factory *pf)
+{
+ struct aps_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "APS", 1000, 1000, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct aps_factory);
+ f->pf = pf;
+ f->pool = pool;
+ f->base.op = &factory_op;
+
+ return &f->base;
+}
+
+/* API: init factory */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
+{
+ struct aps_factory *af = (struct aps_factory*)f;
+
+ pj_ansi_strcpy(af->dev_info.name, "S60 APS");
+ af->dev_info.default_samples_per_sec = 8000;
+ af->dev_info.caps = PJMEDIA_AUD_DEV_CAP_EXT_FORMAT |
+ //PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE |
+ PJMEDIA_AUD_DEV_CAP_VAD |
+ PJMEDIA_AUD_DEV_CAP_CNG;
+ af->dev_info.routes = PJMEDIA_AUD_DEV_ROUTE_EARPIECE |
+ PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
+ af->dev_info.input_count = 1;
+ af->dev_info.output_count = 1;
+
+ /* Enumerate codecs by trying to initialize each codec and examining
+ * the error code. Consider the following:
+ * - not possible to reinitialize the same APS session with
+ * different settings,
+ * - closing APS session and trying to immediately reconnect may fail,
+ * clients should wait ~5s before attempting to reconnect.
+ */
+
+ unsigned i, fmt_cnt = 0;
+ pj_bool_t g711_supported = PJ_FALSE;
+
+ /* Do not change the order! */
+ TFourCC fourcc[] = {
+ TFourCC(KMCPFourCCIdAMRNB),
+ TFourCC(KMCPFourCCIdG711),
+ TFourCC(KMCPFourCCIdG729),
+ TFourCC(KMCPFourCCIdILBC)
+ };
+
+ for (i = 0; i < PJ_ARRAY_SIZE(fourcc); ++i) {
+ pj_bool_t supported = PJ_FALSE;
+ unsigned retry_cnt = 0;
+ enum { MAX_RETRY = 3 };
+
+#if (PJMEDIA_AUDIO_DEV_SYMB_APS_DETECTS_CODEC == 0)
+ /* Codec detection is disabled */
+ supported = PJ_TRUE;
+#elif (PJMEDIA_AUDIO_DEV_SYMB_APS_DETECTS_CODEC == 1)
+ /* Minimal codec detection, AMR-NB and G.711 only */
+ if (i > 1) {
+ /* If G.711 has been checked, skip G.729 and iLBC checks */
+ retry_cnt = MAX_RETRY;
+ supported = g711_supported;
+ }
+#endif
+
+ while (!supported && ++retry_cnt <= MAX_RETRY) {
+ RAPSSession iSession;
+ TAPSInitSettings iPlaySettings;
+ TAPSInitSettings iRecSettings;
+ TInt err;
+
+ // Recorder settings
+ iRecSettings.iGlobal = APP_UID;
+ iRecSettings.iPriority = TMdaPriority(100);
+ iRecSettings.iPreference = TMdaPriorityPreference(0x05210001);
+ iRecSettings.iSettings.iChannels = EMMFMono;
+ iRecSettings.iSettings.iSampleRate = EMMFSampleRate8000Hz;
+
+ // Player settings
+ iPlaySettings.iGlobal = APP_UID;
+ iPlaySettings.iPriority = TMdaPriority(100);
+ iPlaySettings.iPreference = TMdaPriorityPreference(0x05220001);
+ iPlaySettings.iSettings.iChannels = EMMFMono;
+ iPlaySettings.iSettings.iSampleRate = EMMFSampleRate8000Hz;
+
+ iRecSettings.iFourCC = iPlaySettings.iFourCC = fourcc[i];
+
+ err = iSession.Connect();
+ if (err == KErrNone)
+ err = iSession.InitializePlayer(iPlaySettings);
+ if (err == KErrNone)
+ err = iSession.InitializeRecorder(iRecSettings);
+
+ // On some devices, immediate closing causes APS Server panic,
+ // e.g: N95, so let's just wait for some time before closing.
+ enum { APS_CLOSE_WAIT_TIME = 200 }; /* in msecs */
+ snd_wait(APS_CLOSE_WAIT_TIME);
+
+ iSession.Close();
+
+ if (err == KErrNone) {
+ /* All fine, stop retyring */
+ supported = PJ_TRUE;
+ } else if (err == KErrAlreadyExists && retry_cnt < MAX_RETRY) {
+ /* Seems that the previous session is still arround,
+ * let's wait before retrying.
+ */
+ enum { RETRY_WAIT_TIME = 3000 }; /* in msecs */
+ snd_wait(RETRY_WAIT_TIME);
+ } else {
+ /* Seems that this format is not supported */
+ retry_cnt = MAX_RETRY;
+ }
+ }
+
+ if (supported) {
+ switch(i) {
+ case 0: /* AMRNB */
+ af->dev_info.ext_fmt[fmt_cnt].id = PJMEDIA_FORMAT_AMR;
+ af->dev_info.ext_fmt[fmt_cnt].bitrate = 7400;
+ af->dev_info.ext_fmt[fmt_cnt].vad = PJ_TRUE;
+ ++fmt_cnt;
+ break;
+ case 1: /* G.711 */
+ af->dev_info.ext_fmt[fmt_cnt].id = PJMEDIA_FORMAT_PCMU;
+ af->dev_info.ext_fmt[fmt_cnt].bitrate = 64000;
+ af->dev_info.ext_fmt[fmt_cnt].vad = PJ_FALSE;
+ ++fmt_cnt;
+ af->dev_info.ext_fmt[fmt_cnt].id = PJMEDIA_FORMAT_PCMA;
+ af->dev_info.ext_fmt[fmt_cnt].bitrate = 64000;
+ af->dev_info.ext_fmt[fmt_cnt].vad = PJ_FALSE;
+ ++fmt_cnt;
+ g711_supported = PJ_TRUE;
+ break;
+ case 2: /* G.729 */
+ af->dev_info.ext_fmt[fmt_cnt].id = PJMEDIA_FORMAT_G729;
+ af->dev_info.ext_fmt[fmt_cnt].bitrate = 8000;
+ af->dev_info.ext_fmt[fmt_cnt].vad = PJ_FALSE;
+ ++fmt_cnt;
+ break;
+ case 3: /* iLBC */
+ af->dev_info.ext_fmt[fmt_cnt].id = PJMEDIA_FORMAT_ILBC;
+ af->dev_info.ext_fmt[fmt_cnt].bitrate = 13333;
+ af->dev_info.ext_fmt[fmt_cnt].vad = PJ_TRUE;
+ ++fmt_cnt;
+ break;
+ }
+ }
+ }
+
+ af->dev_info.ext_fmt_cnt = fmt_cnt;
+
+ PJ_LOG(4, (THIS_FILE, "APS initialized"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
+{
+ struct aps_factory *af = (struct aps_factory*)f;
+ pj_pool_t *pool = af->pool;
+
+ af->pool = NULL;
+ pj_pool_release(pool);
+
+ PJ_LOG(4, (THIS_FILE, "APS destroyed"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: refresh the device list */
+static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f)
+{
+ PJ_UNUSED_ARG(f);
+ return PJ_ENOTSUP;
+}
+
+/* API: get number of devices */
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+ PJ_UNUSED_ARG(f);
+ return 1;
+}
+
+/* API: get device info */
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info)
+{
+ struct aps_factory *af = (struct aps_factory*)f;
+
+ PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
+
+ pj_memcpy(info, &af->dev_info, sizeof(*info));
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param)
+{
+ struct aps_factory *af = (struct aps_factory*)f;
+
+ PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
+
+ pj_bzero(param, sizeof(*param));
+ param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param->rec_id = index;
+ param->play_id = index;
+ param->clock_rate = af->dev_info.default_samples_per_sec;
+ param->channel_count = 1;
+ param->samples_per_frame = af->dev_info.default_samples_per_sec * 20 / 1000;
+ param->bits_per_sample = BITS_PER_SAMPLE;
+ param->flags = PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE;
+ param->output_route = PJMEDIA_AUD_DEV_ROUTE_EARPIECE;
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: create stream */
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm)
+{
+ struct aps_factory *af = (struct aps_factory*)f;
+ pj_pool_t *pool;
+ struct aps_stream *strm;
+
+ CPjAudioSetting aps_setting;
+ PjAudioCallback aps_rec_cb;
+ PjAudioCallback aps_play_cb;
+
+ /* Can only support 16bits per sample */
+ PJ_ASSERT_RETURN(param->bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL);
+
+ /* Supported clock rates:
+ * - for non-PCM format: 8kHz
+ * - for PCM format: 8kHz and 16kHz
+ */
+ PJ_ASSERT_RETURN(param->clock_rate == 8000 ||
+ (param->clock_rate == 16000 &&
+ param->ext_fmt.id == PJMEDIA_FORMAT_L16),
+ PJ_EINVAL);
+
+ /* Supported channels number:
+ * - for non-PCM format: mono
+ * - for PCM format: mono and stereo
+ */
+ PJ_ASSERT_RETURN(param->channel_count == 1 ||
+ (param->channel_count == 2 &&
+ param->ext_fmt.id == PJMEDIA_FORMAT_L16),
+ PJ_EINVAL);
+
+ /* Create and Initialize stream descriptor */
+ pool = pj_pool_create(af->pf, "aps-dev", 1000, 1000, NULL);
+ PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
+
+ strm = PJ_POOL_ZALLOC_T(pool, struct aps_stream);
+ strm->pool = pool;
+ strm->param = *param;
+
+ if (strm->param.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT == 0)
+ strm->param.ext_fmt.id = PJMEDIA_FORMAT_L16;
+
+ /* Set audio engine fourcc. */
+ switch(strm->param.ext_fmt.id) {
+ case PJMEDIA_FORMAT_L16:
+ case PJMEDIA_FORMAT_PCMU:
+ case PJMEDIA_FORMAT_PCMA:
+ aps_setting.fourcc = TFourCC(KMCPFourCCIdG711);
+ break;
+ case PJMEDIA_FORMAT_AMR:
+ aps_setting.fourcc = TFourCC(KMCPFourCCIdAMRNB);
+ break;
+ case PJMEDIA_FORMAT_G729:
+ aps_setting.fourcc = TFourCC(KMCPFourCCIdG729);
+ break;
+ case PJMEDIA_FORMAT_ILBC:
+ aps_setting.fourcc = TFourCC(KMCPFourCCIdILBC);
+ break;
+ default:
+ aps_setting.fourcc = 0;
+ break;
+ }
+
+ /* Set audio engine mode. */
+ if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_AMR)
+ {
+ aps_setting.mode = (TAPSCodecMode)strm->param.ext_fmt.bitrate;
+ }
+ else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU ||
+ strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 ||
+ (strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC &&
+ strm->param.ext_fmt.bitrate != 15200))
+ {
+ aps_setting.mode = EULawOr30ms;
+ }
+ else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA ||
+ (strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC &&
+ strm->param.ext_fmt.bitrate == 15200))
+ {
+ aps_setting.mode = EALawOr20ms;
+ }
+
+ /* Disable VAD on L16, G711, and also G729 (G729's VAD potentially
+ * causes noise?).
+ */
+ if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU ||
+ strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA ||
+ strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 ||
+ strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729)
+ {
+ aps_setting.vad = EFalse;
+ } else {
+ aps_setting.vad = strm->param.ext_fmt.vad;
+ }
+
+ /* Set other audio engine attributes. */
+ aps_setting.plc = strm->param.plc_enabled;
+ aps_setting.cng = aps_setting.vad;
+ aps_setting.loudspk =
+ strm->param.output_route==PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
+
+ /* Set audio engine callbacks. */
+ if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16) {
+ aps_play_cb = &PlayCbPcm;
+ aps_rec_cb = &RecCbPcm;
+ } else {
+ aps_play_cb = &PlayCb;
+ aps_rec_cb = &RecCb;
+ }
+
+ strm->rec_cb = rec_cb;
+ strm->play_cb = play_cb;
+ strm->user_data = user_data;
+ strm->resample_factor = strm->param.clock_rate / 8000;
+
+ /* play_buf size is samples per frame scaled in to 8kHz mono. */
+ strm->play_buf = (pj_int16_t*)pj_pool_zalloc(
+ pool,
+ (strm->param.samples_per_frame /
+ strm->resample_factor /
+ strm->param.channel_count) << 1);
+ strm->play_buf_len = 0;
+ strm->play_buf_start = 0;
+
+ /* rec_buf size is samples per frame scaled in to 8kHz mono. */
+ strm->rec_buf = (pj_int16_t*)pj_pool_zalloc(
+ pool,
+ (strm->param.samples_per_frame /
+ strm->resample_factor /
+ strm->param.channel_count) << 1);
+ strm->rec_buf_len = 0;
+
+ if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729) {
+ TBitStream *g729_bitstream = new TBitStream;
+
+ PJ_ASSERT_RETURN(g729_bitstream, PJ_ENOMEM);
+ strm->strm_data = (void*)g729_bitstream;
+ }
+
+ /* Init resampler when format is PCM and clock rate is not 8kHz */
+ if (strm->param.clock_rate != 8000 &&
+ strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16)
+ {
+ pj_status_t status;
+
+ if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+ /* Create resample for recorder */
+ status = pjmedia_resample_create( pool, PJ_TRUE, PJ_FALSE, 1,
+ 8000,
+ strm->param.clock_rate,
+ 80,
+ &strm->rec_resample);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ /* Create resample for player */
+ status = pjmedia_resample_create( pool, PJ_TRUE, PJ_FALSE, 1,
+ strm->param.clock_rate,
+ 8000,
+ 80 * strm->resample_factor,
+ &strm->play_resample);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+ }
+
+ /* Create PCM buffer, when the clock rate is not 8kHz or not mono */
+ if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 &&
+ (strm->resample_factor > 1 || strm->param.channel_count != 1))
+ {
+ strm->pcm_buf = (pj_int16_t*)pj_pool_zalloc(pool,
+ strm->param.samples_per_frame << 1);
+ }
+
+
+ /* Create the audio engine. */
+ TRAPD(err, strm->engine = CPjAudioEngine::NewL(strm,
+ aps_rec_cb, aps_play_cb,
+ strm, aps_setting));
+ if (err != KErrNone) {
+ pj_pool_release(pool);
+ return PJ_RETURN_OS_ERROR(err);
+ }
+
+ /* Apply output volume setting if specified */
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
+ stream_set_cap(&strm->base, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &param->output_vol);
+ }
+
+ /* Done */
+ strm->base.op = &stream_op;
+ *p_aud_strm = &strm->base;
+
+ return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t stream_get_param(pjmedia_aud_stream *s,
+ pjmedia_aud_param *pi)
+{
+ struct aps_stream *strm = (struct aps_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+ /* Update the output volume setting */
+ if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &pi->output_vol) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ void *pval)
+{
+ struct aps_stream *strm = (struct aps_stream*)s;
+ pj_status_t status = PJ_ENOTSUP;
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ switch (cap) {
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE:
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ *(pjmedia_aud_dev_route*)pval = strm->param.output_route;
+ status = PJ_SUCCESS;
+ }
+ break;
+
+ /* There is a case that GetMaxGain() stucks, e.g: in N95. */
+ /*
+ case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
+ if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+ PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+
+ TInt max_gain = strm->engine->GetMaxGain();
+ TInt gain = strm->engine->GetGain();
+
+ if (max_gain > 0 && gain >= 0) {
+ *(unsigned*)pval = gain * 100 / max_gain;
+ status = PJ_SUCCESS;
+ } else {
+ status = PJMEDIA_EAUD_NOTREADY;
+ }
+ }
+ break;
+ */
+
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+
+ TInt max_vol = strm->engine->GetMaxVolume();
+ TInt vol = strm->engine->GetVolume();
+
+ if (max_vol > 0 && vol >= 0) {
+ *(unsigned*)pval = vol * 100 / max_vol;
+ status = PJ_SUCCESS;
+ } else {
+ status = PJMEDIA_EAUD_NOTREADY;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return status;
+}
+
+/* API: set capability */
+static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ const void *pval)
+{
+ struct aps_stream *strm = (struct aps_stream*)s;
+ pj_status_t status = PJ_ENOTSUP;
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ switch (cap) {
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE:
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ pjmedia_aud_dev_route r = *(const pjmedia_aud_dev_route*)pval;
+ TInt err;
+
+ PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+
+ switch (r) {
+ case PJMEDIA_AUD_DEV_ROUTE_DEFAULT:
+ case PJMEDIA_AUD_DEV_ROUTE_EARPIECE:
+ err = strm->engine->ActivateSpeaker(EFalse);
+ status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
+ break;
+ case PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER:
+ err = strm->engine->ActivateSpeaker(ETrue);
+ status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
+ break;
+ default:
+ status = PJ_EINVAL;
+ break;
+ }
+ if (status == PJ_SUCCESS)
+ strm->param.output_route = r;
+ }
+ break;
+
+ /* There is a case that GetMaxGain() stucks, e.g: in N95. */
+ /*
+ case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
+ if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+ PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+
+ TInt max_gain = strm->engine->GetMaxGain();
+ if (max_gain > 0) {
+ TInt gain, err;
+
+ gain = *(unsigned*)pval * max_gain / 100;
+ err = strm->engine->SetGain(gain);
+ status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
+ } else {
+ status = PJMEDIA_EAUD_NOTREADY;
+ }
+ if (status == PJ_SUCCESS)
+ strm->param.input_vol = *(unsigned*)pval;
+ }
+ break;
+ */
+
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+
+ TInt max_vol = strm->engine->GetMaxVolume();
+ if (max_vol > 0) {
+ TInt vol, err;
+
+ vol = *(unsigned*)pval * max_vol / 100;
+ err = strm->engine->SetVolume(vol);
+ status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
+ } else {
+ status = PJMEDIA_EAUD_NOTREADY;
+ }
+ if (status == PJ_SUCCESS)
+ strm->param.output_vol = *(unsigned*)pval;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return status;
+}
+
+/* API: Start stream. */
+static pj_status_t stream_start(pjmedia_aud_stream *strm)
+{
+ struct aps_stream *stream = (struct aps_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ if (stream->engine) {
+ TInt err = stream->engine->StartL();
+ if (err != KErrNone)
+ return PJ_RETURN_OS_ERROR(err);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t stream_stop(pjmedia_aud_stream *strm)
+{
+ struct aps_stream *stream = (struct aps_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ if (stream->engine) {
+ stream->engine->Stop();
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm)
+{
+ struct aps_stream *stream = (struct aps_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ stream_stop(strm);
+
+ delete stream->engine;
+ stream->engine = NULL;
+
+ if (stream->param.ext_fmt.id == PJMEDIA_FORMAT_G729) {
+ TBitStream *g729_bitstream = (TBitStream*)stream->strm_data;
+ stream->strm_data = NULL;
+ delete g729_bitstream;
+ }
+
+ pj_pool_t *pool;
+ pool = stream->pool;
+ if (pool) {
+ stream->pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+#endif // PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
+
diff --git a/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp b/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp
new file mode 100644
index 0000000..b4a6b9c
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp
@@ -0,0 +1,1196 @@
+/* $Id: symb_mda_dev.cpp 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia-audiodev/audiodev_imp.h>
+#include <pjmedia-audiodev/errno.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/os.h>
+#include <pj/string.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA
+
+/*
+ * This file provides sound implementation for Symbian Audio Streaming
+ * device. Application using this sound abstraction must link with:
+ * - mediaclientaudiostream.lib, and
+ * - mediaclientaudioinputstream.lib
+ */
+#include <mda/common/audio.h>
+#include <mdaaudiooutputstream.h>
+#include <mdaaudioinputstream.h>
+
+
+#define THIS_FILE "symb_mda_dev.c"
+#define BITS_PER_SAMPLE 16
+#define BYTES_PER_SAMPLE (BITS_PER_SAMPLE/8)
+
+
+#if 1
+# define TRACE_(st) PJ_LOG(3, st)
+#else
+# define TRACE_(st)
+#endif
+
+
+/* MDA factory */
+struct mda_factory
+{
+ pjmedia_aud_dev_factory base;
+ pj_pool_t *pool;
+ pj_pool_factory *pf;
+ pjmedia_aud_dev_info dev_info;
+};
+
+/* Forward declaration of internal engine. */
+class CPjAudioInputEngine;
+class CPjAudioOutputEngine;
+
+/* MDA stream. */
+struct mda_stream
+{
+ // Base
+ pjmedia_aud_stream base; /**< Base class. */
+
+ // Pool
+ pj_pool_t *pool; /**< Memory pool. */
+
+ // Common settings.
+ pjmedia_aud_param param; /**< Stream param. */
+
+ // Audio engine
+ CPjAudioInputEngine *in_engine; /**< Record engine. */
+ CPjAudioOutputEngine *out_engine; /**< Playback engine. */
+};
+
+
+/* Prototypes */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f);
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info);
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param);
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm);
+
+static pj_status_t stream_get_param(pjmedia_aud_stream *strm,
+ pjmedia_aud_param *param);
+static pj_status_t stream_get_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ void *value);
+static pj_status_t stream_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value);
+static pj_status_t stream_start(pjmedia_aud_stream *strm);
+static pj_status_t stream_stop(pjmedia_aud_stream *strm);
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm);
+
+
+/* Operations */
+static pjmedia_aud_dev_factory_op factory_op =
+{
+ &factory_init,
+ &factory_destroy,
+ &factory_get_dev_count,
+ &factory_get_dev_info,
+ &factory_default_param,
+ &factory_create_stream,
+ &factory_refresh
+};
+
+static pjmedia_aud_stream_op stream_op =
+{
+ &stream_get_param,
+ &stream_get_cap,
+ &stream_set_cap,
+ &stream_start,
+ &stream_stop,
+ &stream_destroy
+};
+
+
+/*
+ * Convert clock rate to Symbian's TMdaAudioDataSettings capability.
+ */
+static TInt get_clock_rate_cap(unsigned clock_rate)
+{
+ switch (clock_rate) {
+ case 8000: return TMdaAudioDataSettings::ESampleRate8000Hz;
+ case 11025: return TMdaAudioDataSettings::ESampleRate11025Hz;
+ case 12000: return TMdaAudioDataSettings::ESampleRate12000Hz;
+ case 16000: return TMdaAudioDataSettings::ESampleRate16000Hz;
+ case 22050: return TMdaAudioDataSettings::ESampleRate22050Hz;
+ case 24000: return TMdaAudioDataSettings::ESampleRate24000Hz;
+ case 32000: return TMdaAudioDataSettings::ESampleRate32000Hz;
+ case 44100: return TMdaAudioDataSettings::ESampleRate44100Hz;
+ case 48000: return TMdaAudioDataSettings::ESampleRate48000Hz;
+ case 64000: return TMdaAudioDataSettings::ESampleRate64000Hz;
+ case 96000: return TMdaAudioDataSettings::ESampleRate96000Hz;
+ default:
+ return 0;
+ }
+}
+
+/*
+ * Convert number of channels into Symbian's TMdaAudioDataSettings capability.
+ */
+static TInt get_channel_cap(unsigned channel_count)
+{
+ switch (channel_count) {
+ case 1: return TMdaAudioDataSettings::EChannelsMono;
+ case 2: return TMdaAudioDataSettings::EChannelsStereo;
+ default:
+ return 0;
+ }
+}
+
+/*
+ * Utility: print sound device error
+ */
+static void snd_perror(const char *title, TInt rc)
+{
+ PJ_LOG(1,(THIS_FILE, "%s: error code %d", title, rc));
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+
+/*
+ * Implementation: Symbian Input Stream.
+ */
+class CPjAudioInputEngine : public CBase, MMdaAudioInputStreamCallback
+{
+public:
+ enum State
+ {
+ STATE_INACTIVE,
+ STATE_ACTIVE,
+ };
+
+ ~CPjAudioInputEngine();
+
+ static CPjAudioInputEngine *NewL(struct mda_stream *parent_strm,
+ pjmedia_aud_rec_cb rec_cb,
+ void *user_data);
+
+ static CPjAudioInputEngine *NewLC(struct mda_stream *parent_strm,
+ pjmedia_aud_rec_cb rec_cb,
+ void *user_data);
+
+ pj_status_t StartRecord();
+ void Stop();
+
+ pj_status_t SetGain(TInt gain) {
+ if (iInputStream_) {
+ iInputStream_->SetGain(gain);
+ return PJ_SUCCESS;
+ } else
+ return PJ_EINVALIDOP;
+ }
+
+ TInt GetGain() {
+ if (iInputStream_) {
+ return iInputStream_->Gain();
+ } else
+ return PJ_EINVALIDOP;
+ }
+
+ TInt GetMaxGain() {
+ if (iInputStream_) {
+ return iInputStream_->MaxGain();
+ } else
+ return PJ_EINVALIDOP;
+ }
+
+private:
+ State state_;
+ struct mda_stream *parentStrm_;
+ pjmedia_aud_rec_cb recCb_;
+ void *userData_;
+ CMdaAudioInputStream *iInputStream_;
+ HBufC8 *iStreamBuffer_;
+ TPtr8 iFramePtr_;
+ TInt lastError_;
+ pj_uint32_t timeStamp_;
+ CActiveSchedulerWait startAsw_;
+
+ // cache variable
+ // to avoid calculating frame length repeatedly
+ TInt frameLen_;
+
+ // sometimes recorded size != requested framesize, so let's
+ // provide a buffer to make sure the rec callback returning
+ // framesize as requested.
+ TUint8 *frameRecBuf_;
+ TInt frameRecBufLen_;
+
+ CPjAudioInputEngine(struct mda_stream *parent_strm,
+ pjmedia_aud_rec_cb rec_cb,
+ void *user_data);
+ void ConstructL();
+ TPtr8 & GetFrame();
+
+public:
+ virtual void MaiscOpenComplete(TInt aError);
+ virtual void MaiscBufferCopied(TInt aError, const TDesC8 &aBuffer);
+ virtual void MaiscRecordComplete(TInt aError);
+
+};
+
+
+CPjAudioInputEngine::CPjAudioInputEngine(struct mda_stream *parent_strm,
+ pjmedia_aud_rec_cb rec_cb,
+ void *user_data)
+ : state_(STATE_INACTIVE), parentStrm_(parent_strm),
+ recCb_(rec_cb), userData_(user_data),
+ iInputStream_(NULL), iStreamBuffer_(NULL), iFramePtr_(0, 0),
+ lastError_(KErrNone), timeStamp_(0),
+ frameLen_(parent_strm->param.samples_per_frame *
+ BYTES_PER_SAMPLE),
+ frameRecBuf_(NULL), frameRecBufLen_(0)
+{
+}
+
+CPjAudioInputEngine::~CPjAudioInputEngine()
+{
+ Stop();
+
+ delete iStreamBuffer_;
+ iStreamBuffer_ = NULL;
+
+ delete [] frameRecBuf_;
+ frameRecBuf_ = NULL;
+ frameRecBufLen_ = 0;
+}
+
+void CPjAudioInputEngine::ConstructL()
+{
+ iStreamBuffer_ = HBufC8::NewL(frameLen_);
+ CleanupStack::PushL(iStreamBuffer_);
+
+ frameRecBuf_ = new TUint8[frameLen_*2];
+ CleanupStack::PushL(frameRecBuf_);
+}
+
+CPjAudioInputEngine *CPjAudioInputEngine::NewLC(struct mda_stream *parent,
+ pjmedia_aud_rec_cb rec_cb,
+ void *user_data)
+{
+ CPjAudioInputEngine* self = new (ELeave) CPjAudioInputEngine(parent,
+ rec_cb,
+ user_data);
+ CleanupStack::PushL(self);
+ self->ConstructL();
+ return self;
+}
+
+CPjAudioInputEngine *CPjAudioInputEngine::NewL(struct mda_stream *parent,
+ pjmedia_aud_rec_cb rec_cb,
+ void *user_data)
+{
+ CPjAudioInputEngine *self = NewLC(parent, rec_cb, user_data);
+ CleanupStack::Pop(self->frameRecBuf_);
+ CleanupStack::Pop(self->iStreamBuffer_);
+ CleanupStack::Pop(self);
+ return self;
+}
+
+
+pj_status_t CPjAudioInputEngine::StartRecord()
+{
+
+ // Ignore command if recording is in progress.
+ if (state_ == STATE_ACTIVE)
+ return PJ_SUCCESS;
+
+ // According to Nokia's AudioStream example, some 2nd Edition, FP2 devices
+ // (such as Nokia 6630) require the stream to be reconstructed each time
+ // before calling Open() - otherwise the callback never gets called.
+ // For uniform behavior, lets just delete/re-create the stream for all
+ // devices.
+
+ // Destroy existing stream.
+ if (iInputStream_) delete iInputStream_;
+ iInputStream_ = NULL;
+
+ // Create the stream.
+ TRAPD(err, iInputStream_ = CMdaAudioInputStream::NewL(*this));
+ if (err != KErrNone)
+ return PJ_RETURN_OS_ERROR(err);
+
+ // Initialize settings.
+ TMdaAudioDataSettings iStreamSettings;
+ iStreamSettings.iChannels =
+ get_channel_cap(parentStrm_->param.channel_count);
+ iStreamSettings.iSampleRate =
+ get_clock_rate_cap(parentStrm_->param.clock_rate);
+
+ pj_assert(iStreamSettings.iChannels != 0 &&
+ iStreamSettings.iSampleRate != 0);
+
+ PJ_LOG(4,(THIS_FILE, "Opening sound device for capture, "
+ "clock rate=%d, channel count=%d..",
+ parentStrm_->param.clock_rate,
+ parentStrm_->param.channel_count));
+
+ // Open stream.
+ lastError_ = KRequestPending;
+ iInputStream_->Open(&iStreamSettings);
+
+#if defined(PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START) && \
+ PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START != 0
+
+ startAsw_.Start();
+
+#endif
+
+ // Success
+ PJ_LOG(4,(THIS_FILE, "Sound capture started."));
+ return PJ_SUCCESS;
+}
+
+
+void CPjAudioInputEngine::Stop()
+{
+ // If capture is in progress, stop it.
+ if (iInputStream_ && state_ == STATE_ACTIVE) {
+ lastError_ = KRequestPending;
+ iInputStream_->Stop();
+
+ // Wait until it's actually stopped
+ while (lastError_ == KRequestPending)
+ pj_symbianos_poll(-1, 100);
+ }
+
+ if (iInputStream_) {
+ delete iInputStream_;
+ iInputStream_ = NULL;
+ }
+
+ if (startAsw_.IsStarted()) {
+ startAsw_.AsyncStop();
+ }
+
+ state_ = STATE_INACTIVE;
+}
+
+
+TPtr8 & CPjAudioInputEngine::GetFrame()
+{
+ //iStreamBuffer_->Des().FillZ(frameLen_);
+ iFramePtr_.Set((TUint8*)(iStreamBuffer_->Ptr()), frameLen_, frameLen_);
+ return iFramePtr_;
+}
+
+void CPjAudioInputEngine::MaiscOpenComplete(TInt aError)
+{
+ if (startAsw_.IsStarted()) {
+ startAsw_.AsyncStop();
+ }
+
+ lastError_ = aError;
+ if (aError != KErrNone) {
+ snd_perror("Error in MaiscOpenComplete()", aError);
+ return;
+ }
+
+ /* Apply input volume setting if specified */
+ if (parentStrm_->param.flags &
+ PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING)
+ {
+ stream_set_cap(&parentStrm_->base,
+ PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING,
+ &parentStrm_->param.input_vol);
+ }
+
+ // set stream priority to normal and time sensitive
+ iInputStream_->SetPriority(EPriorityNormal,
+ EMdaPriorityPreferenceTime);
+
+ // Read the first frame.
+ TPtr8 & frm = GetFrame();
+ TRAPD(err2, iInputStream_->ReadL(frm));
+ if (err2) {
+ PJ_LOG(4,(THIS_FILE, "Exception in iInputStream_->ReadL()"));
+ lastError_ = err2;
+ return;
+ }
+
+ // input stream opened succesfully, set status to Active
+ state_ = STATE_ACTIVE;
+}
+
+void CPjAudioInputEngine::MaiscBufferCopied(TInt aError,
+ const TDesC8 &aBuffer)
+{
+ lastError_ = aError;
+ if (aError != KErrNone) {
+ snd_perror("Error in MaiscBufferCopied()", aError);
+ return;
+ }
+
+ if (frameRecBufLen_ || aBuffer.Length() < frameLen_) {
+ pj_memcpy(frameRecBuf_ + frameRecBufLen_, (void*) aBuffer.Ptr(), aBuffer.Length());
+ frameRecBufLen_ += aBuffer.Length();
+ }
+
+ if (frameRecBufLen_) {
+ while (frameRecBufLen_ >= frameLen_) {
+ pjmedia_frame f;
+
+ f.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ f.buf = frameRecBuf_;
+ f.size = frameLen_;
+ f.timestamp.u32.lo = timeStamp_;
+ f.bit_info = 0;
+
+ // Call the callback.
+ recCb_(userData_, &f);
+ // Increment timestamp.
+ timeStamp_ += parentStrm_->param.samples_per_frame;
+
+ frameRecBufLen_ -= frameLen_;
+ pj_memmove(frameRecBuf_, frameRecBuf_+frameLen_, frameRecBufLen_);
+ }
+ } else {
+ pjmedia_frame f;
+
+ f.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ f.buf = (void*)aBuffer.Ptr();
+ f.size = aBuffer.Length();
+ f.timestamp.u32.lo = timeStamp_;
+ f.bit_info = 0;
+
+ // Call the callback.
+ recCb_(userData_, &f);
+
+ // Increment timestamp.
+ timeStamp_ += parentStrm_->param.samples_per_frame;
+ }
+
+ // Record next frame
+ TPtr8 & frm = GetFrame();
+ TRAPD(err2, iInputStream_->ReadL(frm));
+ if (err2) {
+ PJ_LOG(4,(THIS_FILE, "Exception in iInputStream_->ReadL()"));
+ }
+}
+
+
+void CPjAudioInputEngine::MaiscRecordComplete(TInt aError)
+{
+ lastError_ = aError;
+ state_ = STATE_INACTIVE;
+ if (aError != KErrNone && aError != KErrCancel) {
+ snd_perror("Error in MaiscRecordComplete()", aError);
+ }
+}
+
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+
+/*
+ * Implementation: Symbian Output Stream.
+ */
+
+class CPjAudioOutputEngine : public CBase, MMdaAudioOutputStreamCallback
+{
+public:
+ enum State
+ {
+ STATE_INACTIVE,
+ STATE_ACTIVE,
+ };
+
+ ~CPjAudioOutputEngine();
+
+ static CPjAudioOutputEngine *NewL(struct mda_stream *parent_strm,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data);
+
+ static CPjAudioOutputEngine *NewLC(struct mda_stream *parent_strm,
+ pjmedia_aud_play_cb rec_cb,
+ void *user_data);
+
+ pj_status_t StartPlay();
+ void Stop();
+
+ pj_status_t SetVolume(TInt vol) {
+ if (iOutputStream_) {
+ iOutputStream_->SetVolume(vol);
+ return PJ_SUCCESS;
+ } else
+ return PJ_EINVALIDOP;
+ }
+
+ TInt GetVolume() {
+ if (iOutputStream_) {
+ return iOutputStream_->Volume();
+ } else
+ return PJ_EINVALIDOP;
+ }
+
+ TInt GetMaxVolume() {
+ if (iOutputStream_) {
+ return iOutputStream_->MaxVolume();
+ } else
+ return PJ_EINVALIDOP;
+ }
+
+private:
+ State state_;
+ struct mda_stream *parentStrm_;
+ pjmedia_aud_play_cb playCb_;
+ void *userData_;
+ CMdaAudioOutputStream *iOutputStream_;
+ TUint8 *frameBuf_;
+ unsigned frameBufSize_;
+ TPtrC8 frame_;
+ TInt lastError_;
+ unsigned timestamp_;
+ CActiveSchedulerWait startAsw_;
+
+ CPjAudioOutputEngine(struct mda_stream *parent_strm,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data);
+ void ConstructL();
+
+ virtual void MaoscOpenComplete(TInt aError);
+ virtual void MaoscBufferCopied(TInt aError, const TDesC8& aBuffer);
+ virtual void MaoscPlayComplete(TInt aError);
+};
+
+
+CPjAudioOutputEngine::CPjAudioOutputEngine(struct mda_stream *parent_strm,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data)
+: state_(STATE_INACTIVE), parentStrm_(parent_strm), playCb_(play_cb),
+ userData_(user_data), iOutputStream_(NULL), frameBuf_(NULL),
+ lastError_(KErrNone), timestamp_(0)
+{
+}
+
+
+void CPjAudioOutputEngine::ConstructL()
+{
+ frameBufSize_ = parentStrm_->param.samples_per_frame *
+ BYTES_PER_SAMPLE;
+ frameBuf_ = new TUint8[frameBufSize_];
+}
+
+CPjAudioOutputEngine::~CPjAudioOutputEngine()
+{
+ Stop();
+ delete [] frameBuf_;
+}
+
+CPjAudioOutputEngine *
+CPjAudioOutputEngine::NewLC(struct mda_stream *parent_strm,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data)
+{
+ CPjAudioOutputEngine* self = new (ELeave) CPjAudioOutputEngine(parent_strm,
+ play_cb,
+ user_data);
+ CleanupStack::PushL(self);
+ self->ConstructL();
+ return self;
+}
+
+CPjAudioOutputEngine *
+CPjAudioOutputEngine::NewL(struct mda_stream *parent_strm,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data)
+{
+ CPjAudioOutputEngine *self = NewLC(parent_strm, play_cb, user_data);
+ CleanupStack::Pop(self);
+ return self;
+}
+
+pj_status_t CPjAudioOutputEngine::StartPlay()
+{
+ // Ignore command if playing is in progress.
+ if (state_ == STATE_ACTIVE)
+ return PJ_SUCCESS;
+
+ // Destroy existing stream.
+ if (iOutputStream_) delete iOutputStream_;
+ iOutputStream_ = NULL;
+
+ // Create the stream
+ TRAPD(err, iOutputStream_ = CMdaAudioOutputStream::NewL(*this));
+ if (err != KErrNone)
+ return PJ_RETURN_OS_ERROR(err);
+
+ // Initialize settings.
+ TMdaAudioDataSettings iStreamSettings;
+ iStreamSettings.iChannels =
+ get_channel_cap(parentStrm_->param.channel_count);
+ iStreamSettings.iSampleRate =
+ get_clock_rate_cap(parentStrm_->param.clock_rate);
+
+ pj_assert(iStreamSettings.iChannels != 0 &&
+ iStreamSettings.iSampleRate != 0);
+
+ PJ_LOG(4,(THIS_FILE, "Opening sound device for playback, "
+ "clock rate=%d, channel count=%d..",
+ parentStrm_->param.clock_rate,
+ parentStrm_->param.channel_count));
+
+ // Open stream.
+ lastError_ = KRequestPending;
+ iOutputStream_->Open(&iStreamSettings);
+
+#if defined(PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START) && \
+ PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START != 0
+
+ startAsw_.Start();
+
+#endif
+
+ // Success
+ PJ_LOG(4,(THIS_FILE, "Sound playback started"));
+ return PJ_SUCCESS;
+
+}
+
+void CPjAudioOutputEngine::Stop()
+{
+ // Stop stream if it's playing
+ if (iOutputStream_ && state_ != STATE_INACTIVE) {
+ lastError_ = KRequestPending;
+ iOutputStream_->Stop();
+
+ // Wait until it's actually stopped
+ while (lastError_ == KRequestPending)
+ pj_symbianos_poll(-1, 100);
+ }
+
+ if (iOutputStream_) {
+ delete iOutputStream_;
+ iOutputStream_ = NULL;
+ }
+
+ if (startAsw_.IsStarted()) {
+ startAsw_.AsyncStop();
+ }
+
+ state_ = STATE_INACTIVE;
+}
+
+void CPjAudioOutputEngine::MaoscOpenComplete(TInt aError)
+{
+ if (startAsw_.IsStarted()) {
+ startAsw_.AsyncStop();
+ }
+
+ lastError_ = aError;
+
+ if (aError==KErrNone) {
+ // set stream properties, 16bit 8KHz mono
+ TMdaAudioDataSettings iSettings;
+ iSettings.iChannels =
+ get_channel_cap(parentStrm_->param.channel_count);
+ iSettings.iSampleRate =
+ get_clock_rate_cap(parentStrm_->param.clock_rate);
+
+ iOutputStream_->SetAudioPropertiesL(iSettings.iSampleRate,
+ iSettings.iChannels);
+
+ /* Apply output volume setting if specified */
+ if (parentStrm_->param.flags &
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING)
+ {
+ stream_set_cap(&parentStrm_->base,
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &parentStrm_->param.output_vol);
+ } else {
+ // set volume to 1/2th of stream max volume
+ iOutputStream_->SetVolume(iOutputStream_->MaxVolume()/2);
+ }
+
+ // set stream priority to normal and time sensitive
+ iOutputStream_->SetPriority(EPriorityNormal,
+ EMdaPriorityPreferenceTime);
+
+ // Call callback to retrieve frame from upstream.
+ pjmedia_frame f;
+ pj_status_t status;
+
+ f.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ f.buf = frameBuf_;
+ f.size = frameBufSize_;
+ f.timestamp.u32.lo = timestamp_;
+ f.bit_info = 0;
+
+ status = playCb_(this->userData_, &f);
+ if (status != PJ_SUCCESS) {
+ this->Stop();
+ return;
+ }
+
+ if (f.type != PJMEDIA_FRAME_TYPE_AUDIO)
+ pj_bzero(frameBuf_, frameBufSize_);
+
+ // Increment timestamp.
+ timestamp_ += (frameBufSize_ / BYTES_PER_SAMPLE);
+
+ // issue WriteL() to write the first audio data block,
+ // subsequent calls to WriteL() will be issued in
+ // MMdaAudioOutputStreamCallback::MaoscBufferCopied()
+ // until whole data buffer is written.
+ frame_.Set(frameBuf_, frameBufSize_);
+ iOutputStream_->WriteL(frame_);
+
+ // output stream opened succesfully, set status to Active
+ state_ = STATE_ACTIVE;
+ } else {
+ snd_perror("Error in MaoscOpenComplete()", aError);
+ }
+}
+
+void CPjAudioOutputEngine::MaoscBufferCopied(TInt aError,
+ const TDesC8& aBuffer)
+{
+ PJ_UNUSED_ARG(aBuffer);
+
+ if (aError==KErrNone) {
+ // Buffer successfully written, feed another one.
+
+ // Call callback to retrieve frame from upstream.
+ pjmedia_frame f;
+ pj_status_t status;
+
+ f.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ f.buf = frameBuf_;
+ f.size = frameBufSize_;
+ f.timestamp.u32.lo = timestamp_;
+ f.bit_info = 0;
+
+ status = playCb_(this->userData_, &f);
+ if (status != PJ_SUCCESS) {
+ this->Stop();
+ return;
+ }
+
+ if (f.type != PJMEDIA_FRAME_TYPE_AUDIO)
+ pj_bzero(frameBuf_, frameBufSize_);
+
+ // Increment timestamp.
+ timestamp_ += (frameBufSize_ / BYTES_PER_SAMPLE);
+
+ // Write to playback stream.
+ frame_.Set(frameBuf_, frameBufSize_);
+ iOutputStream_->WriteL(frame_);
+
+ } else if (aError==KErrAbort) {
+ // playing was aborted, due to call to CMdaAudioOutputStream::Stop()
+ state_ = STATE_INACTIVE;
+ } else {
+ // error writing data to output
+ lastError_ = aError;
+ state_ = STATE_INACTIVE;
+ snd_perror("Error in MaoscBufferCopied()", aError);
+ }
+}
+
+void CPjAudioOutputEngine::MaoscPlayComplete(TInt aError)
+{
+ lastError_ = aError;
+ state_ = STATE_INACTIVE;
+ if (aError != KErrNone && aError != KErrCancel) {
+ snd_perror("Error in MaoscPlayComplete()", aError);
+ }
+}
+
+/****************************************************************************
+ * Factory operations
+ */
+
+/*
+ * C compatible declaration of MDA factory.
+ */
+PJ_BEGIN_DECL
+PJ_DECL(pjmedia_aud_dev_factory*) pjmedia_symb_mda_factory(pj_pool_factory *pf);
+PJ_END_DECL
+
+/*
+ * Init Symbian audio driver.
+ */
+pjmedia_aud_dev_factory* pjmedia_symb_mda_factory(pj_pool_factory *pf)
+{
+ struct mda_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "symb_aud", 1000, 1000, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct mda_factory);
+ f->pf = pf;
+ f->pool = pool;
+ f->base.op = &factory_op;
+
+ return &f->base;
+}
+
+/* API: init factory */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
+{
+ struct mda_factory *af = (struct mda_factory*)f;
+
+ pj_ansi_strcpy(af->dev_info.name, "Symbian Audio");
+ af->dev_info.default_samples_per_sec = 8000;
+ af->dev_info.caps = PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+ af->dev_info.input_count = 1;
+ af->dev_info.output_count = 1;
+
+ PJ_LOG(4, (THIS_FILE, "Symb Mda initialized"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
+{
+ struct mda_factory *af = (struct mda_factory*)f;
+ pj_pool_t *pool = af->pool;
+
+ af->pool = NULL;
+ pj_pool_release(pool);
+
+ PJ_LOG(4, (THIS_FILE, "Symbian Mda destroyed"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: refresh the device list */
+static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f)
+{
+ PJ_UNUSED_ARG(f);
+ return PJ_ENOTSUP;
+}
+
+/* API: get number of devices */
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+ PJ_UNUSED_ARG(f);
+ return 1;
+}
+
+/* API: get device info */
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info)
+{
+ struct mda_factory *af = (struct mda_factory*)f;
+
+ PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
+
+ pj_memcpy(info, &af->dev_info, sizeof(*info));
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param)
+{
+ struct mda_factory *af = (struct mda_factory*)f;
+
+ PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
+
+ pj_bzero(param, sizeof(*param));
+ param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param->rec_id = index;
+ param->play_id = index;
+ param->clock_rate = af->dev_info.default_samples_per_sec;
+ param->channel_count = 1;
+ param->samples_per_frame = af->dev_info.default_samples_per_sec * 20 / 1000;
+ param->bits_per_sample = BITS_PER_SAMPLE;
+ // Don't set the flags without specifying the flags value.
+ //param->flags = af->dev_info.caps;
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: create stream */
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm)
+{
+ struct mda_factory *mf = (struct mda_factory*)f;
+ pj_pool_t *pool;
+ struct mda_stream *strm;
+
+ /* Can only support 16bits per sample raw PCM format. */
+ PJ_ASSERT_RETURN(param->bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL);
+ PJ_ASSERT_RETURN((param->flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT)==0 ||
+ param->ext_fmt.id == PJMEDIA_FORMAT_L16,
+ PJ_ENOTSUP);
+
+ /* It seems that MDA recorder only supports for mono channel. */
+ PJ_ASSERT_RETURN(param->channel_count == 1, PJ_EINVAL);
+
+ /* Create and Initialize stream descriptor */
+ pool = pj_pool_create(mf->pf, "symb_aud_dev", 1000, 1000, NULL);
+ PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
+
+ strm = PJ_POOL_ZALLOC_T(pool, struct mda_stream);
+ strm->pool = pool;
+ strm->param = *param;
+
+ // Create the output stream.
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ TRAPD(err, strm->out_engine = CPjAudioOutputEngine::NewL(strm, play_cb,
+ user_data));
+ if (err != KErrNone) {
+ pj_pool_release(pool);
+ return PJ_RETURN_OS_ERROR(err);
+ }
+ }
+
+ // Create the input stream.
+ if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+ TRAPD(err, strm->in_engine = CPjAudioInputEngine::NewL(strm, rec_cb,
+ user_data));
+ if (err != KErrNone) {
+ strm->in_engine = NULL;
+ delete strm->out_engine;
+ strm->out_engine = NULL;
+ pj_pool_release(pool);
+ return PJ_RETURN_OS_ERROR(err);
+ }
+ }
+
+ /* Done */
+ strm->base.op = &stream_op;
+ *p_aud_strm = &strm->base;
+
+ return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t stream_get_param(pjmedia_aud_stream *s,
+ pjmedia_aud_param *pi)
+{
+ struct mda_stream *strm = (struct mda_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+ /* Update the output volume setting */
+ if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &pi->output_vol) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+ }
+
+ /* Update the input volume setting */
+ if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING,
+ &pi->input_vol) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ void *pval)
+{
+ struct mda_stream *strm = (struct mda_stream*)s;
+ pj_status_t status = PJ_ENOTSUP;
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ switch (cap) {
+ case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
+ if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+ PJ_ASSERT_RETURN(strm->in_engine, PJ_EINVAL);
+
+ TInt max_gain = strm->in_engine->GetMaxGain();
+ TInt gain = strm->in_engine->GetGain();
+
+ if (max_gain > 0 && gain >= 0) {
+ *(unsigned*)pval = gain * 100 / max_gain;
+ status = PJ_SUCCESS;
+ } else {
+ status = PJMEDIA_EAUD_NOTREADY;
+ }
+ }
+ break;
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ PJ_ASSERT_RETURN(strm->out_engine, PJ_EINVAL);
+
+ TInt max_vol = strm->out_engine->GetMaxVolume();
+ TInt vol = strm->out_engine->GetVolume();
+
+ if (max_vol > 0 && vol >= 0) {
+ *(unsigned*)pval = vol * 100 / max_vol;
+ status = PJ_SUCCESS;
+ } else {
+ status = PJMEDIA_EAUD_NOTREADY;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return status;
+}
+
+/* API: set capability */
+static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ const void *pval)
+{
+ struct mda_stream *strm = (struct mda_stream*)s;
+ pj_status_t status = PJ_ENOTSUP;
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ switch (cap) {
+ case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
+ if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+ PJ_ASSERT_RETURN(strm->in_engine, PJ_EINVAL);
+
+ TInt max_gain = strm->in_engine->GetMaxGain();
+ if (max_gain > 0) {
+ TInt gain;
+
+ gain = *(unsigned*)pval * max_gain / 100;
+ status = strm->in_engine->SetGain(gain);
+ } else {
+ status = PJMEDIA_EAUD_NOTREADY;
+ }
+ }
+ break;
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ PJ_ASSERT_RETURN(strm->out_engine, PJ_EINVAL);
+
+ TInt max_vol = strm->out_engine->GetMaxVolume();
+ if (max_vol > 0) {
+ TInt vol;
+
+ vol = *(unsigned*)pval * max_vol / 100;
+ status = strm->out_engine->SetVolume(vol);
+ } else {
+ status = PJMEDIA_EAUD_NOTREADY;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return status;
+}
+
+/* API: Start stream. */
+static pj_status_t stream_start(pjmedia_aud_stream *strm)
+{
+ struct mda_stream *stream = (struct mda_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ if (stream->out_engine) {
+ pj_status_t status;
+ status = stream->out_engine->StartPlay();
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ if (stream->in_engine) {
+ pj_status_t status;
+ status = stream->in_engine->StartRecord();
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t stream_stop(pjmedia_aud_stream *strm)
+{
+ struct mda_stream *stream = (struct mda_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ if (stream->in_engine) {
+ stream->in_engine->Stop();
+ }
+
+ if (stream->out_engine) {
+ stream->out_engine->Stop();
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm)
+{
+ struct mda_stream *stream = (struct mda_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ stream_stop(strm);
+
+ delete stream->in_engine;
+ stream->in_engine = NULL;
+
+ delete stream->out_engine;
+ stream->out_engine = NULL;
+
+ pj_pool_t *pool;
+ pool = stream->pool;
+ if (pool) {
+ stream->pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA */
diff --git a/pjmedia/src/pjmedia-audiodev/symb_vas_dev.cpp b/pjmedia/src/pjmedia-audiodev/symb_vas_dev.cpp
new file mode 100644
index 0000000..5b94903
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/symb_vas_dev.cpp
@@ -0,0 +1,2006 @@
+/* $Id: symb_vas_dev.cpp 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia-audiodev/audiodev_imp.h>
+#include <pjmedia-audiodev/errno.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pjmedia/resample.h>
+#include <pjmedia/stereo.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/os.h>
+#include <pj/string.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS
+
+/* VAS headers */
+#include <VoIPUtilityFactory.h>
+#include <VoIPDownlinkStream.h>
+#include <VoIPUplinkStream.h>
+#include <VoIPFormatIntfc.h>
+#include <VoIPG711DecoderIntfc.h>
+#include <VoIPG711EncoderIntfc.h>
+#include <VoIPG729DecoderIntfc.h>
+#include <VoIPILBCDecoderIntfc.h>
+#include <VoIPILBCEncoderIntfc.h>
+
+/* AMR helper */
+#include <pjmedia-codec/amr_helper.h>
+
+/* Pack/unpack G.729 frame of S60 DSP codec, taken from:
+ * http://wiki.forum.nokia.com/index.php/TSS000776_-_Payload_conversion_for_G.729_audio_format
+ */
+#include "s60_g729_bitstream.h"
+
+
+#define THIS_FILE "symb_vas_dev.c"
+#define BITS_PER_SAMPLE 16
+
+
+/* When this macro is set, VAS will use EPCM16 format for PCM input/output,
+ * otherwise VAS will use EG711 then transcode it to PCM.
+ * Note that using native EPCM16 format may introduce (much) delay.
+ */
+//#define USE_NATIVE_PCM
+
+#if 1
+# define TRACE_(st) PJ_LOG(3, st)
+#else
+# define TRACE_(st)
+#endif
+
+/* VAS G.711 frame length */
+static pj_uint8_t vas_g711_frame_len;
+
+
+/* VAS factory */
+struct vas_factory
+{
+ pjmedia_aud_dev_factory base;
+ pj_pool_t *pool;
+ pj_pool_factory *pf;
+ pjmedia_aud_dev_info dev_info;
+};
+
+
+/* Forward declaration of CPjAudioEngine */
+class CPjAudioEngine;
+
+
+/* VAS stream. */
+struct vas_stream
+{
+ // Base
+ pjmedia_aud_stream base; /**< Base class. */
+
+ // Pool
+ pj_pool_t *pool; /**< Memory pool. */
+
+ // Common settings.
+ pjmedia_aud_param param; /**< Stream param. */
+ pjmedia_aud_rec_cb rec_cb; /**< Record callback. */
+ pjmedia_aud_play_cb play_cb; /**< Playback callback. */
+ void *user_data; /**< Application data. */
+
+ // Audio engine
+ CPjAudioEngine *engine; /**< Internal engine. */
+
+ pj_timestamp ts_play; /**< Playback timestamp.*/
+ pj_timestamp ts_rec; /**< Record timestamp. */
+
+ pj_int16_t *play_buf; /**< Playback buffer. */
+ pj_uint16_t play_buf_len; /**< Playback buffer length. */
+ pj_uint16_t play_buf_start; /**< Playback buffer start index. */
+ pj_int16_t *rec_buf; /**< Record buffer. */
+ pj_uint16_t rec_buf_len; /**< Record buffer length. */
+ void *strm_data; /**< Stream data. */
+
+ /* Resampling is needed, in case audio device is opened with clock rate
+ * other than 8kHz (only for PCM format).
+ */
+ pjmedia_resample *play_resample; /**< Resampler for playback. */
+ pjmedia_resample *rec_resample; /**< Resampler for recording */
+ pj_uint16_t resample_factor; /**< Resample factor, requested
+ clock rate / 8000 */
+
+ /* When stream is working in PCM format, where the samples may need to be
+ * resampled from/to different clock rate and/or channel count, PCM buffer
+ * is needed to perform such resampling operations.
+ */
+ pj_int16_t *pcm_buf; /**< PCM buffer. */
+};
+
+
+/* Prototypes */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f);
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info);
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param);
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm);
+
+static pj_status_t stream_get_param(pjmedia_aud_stream *strm,
+ pjmedia_aud_param *param);
+static pj_status_t stream_get_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ void *value);
+static pj_status_t stream_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value);
+static pj_status_t stream_start(pjmedia_aud_stream *strm);
+static pj_status_t stream_stop(pjmedia_aud_stream *strm);
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm);
+
+
+/* Operations */
+static pjmedia_aud_dev_factory_op factory_op =
+{
+ &factory_init,
+ &factory_destroy,
+ &factory_get_dev_count,
+ &factory_get_dev_info,
+ &factory_default_param,
+ &factory_create_stream,
+ &factory_refresh
+};
+
+static pjmedia_aud_stream_op stream_op =
+{
+ &stream_get_param,
+ &stream_get_cap,
+ &stream_set_cap,
+ &stream_start,
+ &stream_stop,
+ &stream_destroy
+};
+
+
+/****************************************************************************
+ * Internal VAS Engine
+ */
+
+/*
+ * Utility: print sound device error
+ */
+static void snd_perror(const char *title, TInt rc)
+{
+ PJ_LOG(1,(THIS_FILE, "%s (error code=%d)", title, rc));
+}
+
+typedef void(*PjAudioCallback)(CVoIPDataBuffer *buf, void *user_data);
+
+/*
+ * Audio setting for CPjAudioEngine.
+ */
+class CPjAudioSetting
+{
+public:
+ TVoIPCodecFormat format;
+ TInt mode;
+ TBool plc;
+ TBool vad;
+ TBool cng;
+ TBool loudspk;
+};
+
+/*
+ * Implementation: Symbian Input & Output Stream.
+ */
+class CPjAudioEngine : public CBase,
+ public MVoIPDownlinkObserver,
+ public MVoIPUplinkObserver,
+ public MVoIPFormatObserver
+{
+public:
+ enum State
+ {
+ STATE_NULL,
+ STATE_STARTING,
+ STATE_READY,
+ STATE_STREAMING
+ };
+
+ ~CPjAudioEngine();
+
+ static CPjAudioEngine *NewL(struct vas_stream *parent_strm,
+ PjAudioCallback rec_cb,
+ PjAudioCallback play_cb,
+ void *user_data,
+ const CPjAudioSetting &setting);
+
+ TInt Start();
+ void Stop();
+
+ TInt ActivateSpeaker(TBool active);
+
+ TInt SetVolume(TInt vol) { return iVoIPDnlink->SetVolume(vol); }
+ TInt GetVolume() { TInt vol;iVoIPDnlink->GetVolume(vol);return vol; }
+ TInt GetMaxVolume() { TInt vol;iVoIPDnlink->GetMaxVolume(vol);return vol; }
+
+ TInt SetGain(TInt gain) { return iVoIPUplink->SetGain(gain); }
+ TInt GetGain() { TInt gain;iVoIPUplink->GetGain(gain);return gain; }
+ TInt GetMaxGain() { TInt gain;iVoIPUplink->GetMaxGain(gain);return gain; }
+
+ TBool IsStarted();
+
+private:
+ CPjAudioEngine(struct vas_stream *parent_strm,
+ PjAudioCallback rec_cb,
+ PjAudioCallback play_cb,
+ void *user_data,
+ const CPjAudioSetting &setting);
+ void ConstructL();
+
+ TInt InitPlay();
+ TInt InitRec();
+
+ TInt StartPlay();
+ TInt StartRec();
+
+ // From MVoIPDownlinkObserver
+ void FillBuffer(const CVoIPAudioDownlinkStream& aSrc,
+ CVoIPDataBuffer* aBuffer);
+ void Event(const CVoIPAudioDownlinkStream& aSrc,
+ TInt aEventType,
+ TInt aError);
+
+ // From MVoIPUplinkObserver
+ void EmptyBuffer(const CVoIPAudioUplinkStream& aSrc,
+ CVoIPDataBuffer* aBuffer);
+ void Event(const CVoIPAudioUplinkStream& aSrc,
+ TInt aEventType,
+ TInt aError);
+
+ // From MVoIPFormatObserver
+ void Event(const CVoIPFormatIntfc& aSrc, TInt aEventType);
+
+ State dn_state_;
+ State up_state_;
+ struct vas_stream *parentStrm_;
+ CPjAudioSetting setting_;
+ PjAudioCallback rec_cb_;
+ PjAudioCallback play_cb_;
+ void *user_data_;
+
+ // VAS objects
+ CVoIPUtilityFactory *iFactory;
+ CVoIPAudioDownlinkStream *iVoIPDnlink;
+ CVoIPAudioUplinkStream *iVoIPUplink;
+ CVoIPFormatIntfc *enc_fmt_if;
+ CVoIPFormatIntfc *dec_fmt_if;
+};
+
+
+CPjAudioEngine* CPjAudioEngine::NewL(struct vas_stream *parent_strm,
+ PjAudioCallback rec_cb,
+ PjAudioCallback play_cb,
+ void *user_data,
+ const CPjAudioSetting &setting)
+{
+ CPjAudioEngine* self = new (ELeave) CPjAudioEngine(parent_strm,
+ rec_cb, play_cb,
+ user_data,
+ setting);
+ CleanupStack::PushL(self);
+ self->ConstructL();
+ CleanupStack::Pop(self);
+ return self;
+}
+
+void CPjAudioEngine::ConstructL()
+{
+ TInt err;
+ const TVersion ver(1, 0, 0); /* Not really used at this time */
+
+ err = CVoIPUtilityFactory::CreateFactory(iFactory);
+ User::LeaveIfError(err);
+
+ if (parentStrm_->param.dir != PJMEDIA_DIR_CAPTURE) {
+ err = iFactory->CreateDownlinkStream(ver,
+ CVoIPUtilityFactory::EVoIPCall,
+ iVoIPDnlink);
+ User::LeaveIfError(err);
+ }
+
+ if (parentStrm_->param.dir != PJMEDIA_DIR_PLAYBACK) {
+ err = iFactory->CreateUplinkStream(ver,
+ CVoIPUtilityFactory::EVoIPCall,
+ iVoIPUplink);
+ User::LeaveIfError(err);
+ }
+}
+
+CPjAudioEngine::CPjAudioEngine(struct vas_stream *parent_strm,
+ PjAudioCallback rec_cb,
+ PjAudioCallback play_cb,
+ void *user_data,
+ const CPjAudioSetting &setting)
+ : dn_state_(STATE_NULL),
+ up_state_(STATE_NULL),
+ parentStrm_(parent_strm),
+ setting_(setting),
+ rec_cb_(rec_cb),
+ play_cb_(play_cb),
+ user_data_(user_data),
+ iFactory(NULL),
+ iVoIPDnlink(NULL),
+ iVoIPUplink(NULL),
+ enc_fmt_if(NULL),
+ dec_fmt_if(NULL)
+{
+}
+
+CPjAudioEngine::~CPjAudioEngine()
+{
+ Stop();
+
+ if (iVoIPUplink)
+ iVoIPUplink->Close();
+
+ if (iVoIPDnlink)
+ iVoIPDnlink->Close();
+
+ delete enc_fmt_if;
+ delete dec_fmt_if;
+ delete iVoIPDnlink;
+ delete iVoIPUplink;
+ delete iFactory;
+
+ TRACE_((THIS_FILE, "Sound device destroyed"));
+}
+
+TBool CPjAudioEngine::IsStarted()
+{
+ return ((((parentStrm_->param.dir & PJMEDIA_DIR_CAPTURE) == 0) ||
+ up_state_ == STATE_STREAMING) &&
+ (((parentStrm_->param.dir & PJMEDIA_DIR_PLAYBACK) == 0) ||
+ dn_state_ == STATE_STREAMING));
+}
+
+TInt CPjAudioEngine::InitPlay()
+{
+ TInt err;
+
+ pj_assert(iVoIPDnlink);
+
+ delete dec_fmt_if;
+ dec_fmt_if = NULL;
+ err = iVoIPDnlink->SetFormat(setting_.format, dec_fmt_if);
+ if (err != KErrNone)
+ return err;
+
+ err = dec_fmt_if->SetObserver(*this);
+ if (err != KErrNone)
+ return err;
+
+ return iVoIPDnlink->Open(*this);
+}
+
+TInt CPjAudioEngine::InitRec()
+{
+ TInt err;
+
+ pj_assert(iVoIPUplink);
+
+ delete enc_fmt_if;
+ enc_fmt_if = NULL;
+ err = iVoIPUplink->SetFormat(setting_.format, enc_fmt_if);
+ if (err != KErrNone)
+ return err;
+
+ err = enc_fmt_if->SetObserver(*this);
+ if (err != KErrNone)
+ return err;
+
+ return iVoIPUplink->Open(*this);
+}
+
+TInt CPjAudioEngine::StartPlay()
+{
+ TInt err = KErrNone;
+
+ pj_assert(iVoIPDnlink);
+ pj_assert(dn_state_ == STATE_READY);
+
+ /* Configure specific codec setting */
+ switch (setting_.format) {
+ case EG711:
+ {
+ CVoIPG711DecoderIntfc *g711dec_if = (CVoIPG711DecoderIntfc*)
+ dec_fmt_if;
+ err = g711dec_if->SetMode((CVoIPFormatIntfc::TG711CodecMode)
+ setting_.mode);
+ }
+ break;
+
+ case EILBC:
+ {
+ CVoIPILBCDecoderIntfc *ilbcdec_if = (CVoIPILBCDecoderIntfc*)
+ dec_fmt_if;
+ err = ilbcdec_if->SetMode((CVoIPFormatIntfc::TILBCCodecMode)
+ setting_.mode);
+ }
+ break;
+
+ case EAMR_NB:
+ /* Ticket #1008: AMR playback issue on few devices, e.g: E72, E52 */
+ err = dec_fmt_if->SetFrameMode(ETrue);
+ break;
+
+ default:
+ break;
+ }
+
+ if (err != KErrNone)
+ goto on_return;
+
+ /* Configure audio routing */
+ ActivateSpeaker(setting_.loudspk);
+
+ /* Start player */
+ err = iVoIPDnlink->Start();
+
+on_return:
+ if (err == KErrNone) {
+ dn_state_ = STATE_STREAMING;
+ TRACE_((THIS_FILE, "Downlink started"));
+ } else {
+ snd_perror("Failed starting downlink", err);
+ }
+
+ return err;
+}
+
+TInt CPjAudioEngine::StartRec()
+{
+ TInt err = KErrNone;
+
+ pj_assert(iVoIPUplink);
+ pj_assert(up_state_ == STATE_READY);
+
+ /* Configure specific codec setting */
+ switch (setting_.format) {
+ case EG711:
+ {
+ CVoIPG711EncoderIntfc *g711enc_if = (CVoIPG711EncoderIntfc*)
+ enc_fmt_if;
+ err = g711enc_if->SetMode((CVoIPFormatIntfc::TG711CodecMode)
+ setting_.mode);
+ }
+ break;
+
+ case EILBC:
+ {
+ CVoIPILBCEncoderIntfc *ilbcenc_if = (CVoIPILBCEncoderIntfc*)
+ enc_fmt_if;
+ err = ilbcenc_if->SetMode((CVoIPFormatIntfc::TILBCCodecMode)
+ setting_.mode);
+ }
+ break;
+
+ case EAMR_NB:
+ err = enc_fmt_if->SetBitRate(setting_.mode);
+ break;
+
+ default:
+ break;
+ }
+
+ if (err != KErrNone)
+ goto on_return;
+
+ /* Configure general codec setting */
+ enc_fmt_if->SetVAD(setting_.vad);
+
+ /* Start recorder */
+ err = iVoIPUplink->Start();
+
+on_return:
+ if (err == KErrNone) {
+ up_state_ = STATE_STREAMING;
+ TRACE_((THIS_FILE, "Uplink started"));
+ } else {
+ snd_perror("Failed starting uplink", err);
+ }
+
+ return err;
+}
+
+TInt CPjAudioEngine::Start()
+{
+ TInt err = KErrNone;
+
+ if (iVoIPDnlink) {
+ switch(dn_state_) {
+ case STATE_READY:
+ err = StartPlay();
+ break;
+ case STATE_NULL:
+ err = InitPlay();
+ if (err != KErrNone)
+ return err;
+ dn_state_ = STATE_STARTING;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (iVoIPUplink) {
+ switch(up_state_) {
+ case STATE_READY:
+ err = StartRec();
+ break;
+ case STATE_NULL:
+ err = InitRec();
+ if (err != KErrNone)
+ return err;
+ up_state_ = STATE_STARTING;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return err;
+}
+
+void CPjAudioEngine::Stop()
+{
+ if (iVoIPDnlink) {
+ switch(dn_state_) {
+ case STATE_STREAMING:
+ iVoIPDnlink->Stop();
+ dn_state_ = STATE_READY;
+ break;
+ case STATE_STARTING:
+ dn_state_ = STATE_NULL;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (iVoIPUplink) {
+ switch(up_state_) {
+ case STATE_STREAMING:
+ iVoIPUplink->Stop();
+ up_state_ = STATE_READY;
+ break;
+ case STATE_STARTING:
+ up_state_ = STATE_NULL;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+
+TInt CPjAudioEngine::ActivateSpeaker(TBool active)
+{
+ TInt err = KErrNotSupported;
+
+ if (iVoIPDnlink) {
+ err = iVoIPDnlink->SetAudioDevice(active?
+ CVoIPAudioDownlinkStream::ELoudSpeaker :
+ CVoIPAudioDownlinkStream::EHandset);
+ TRACE_((THIS_FILE, "Loudspeaker turned %s", (active? "on":"off")));
+ }
+
+ return err;
+}
+
+// Callback from MVoIPDownlinkObserver
+void CPjAudioEngine::FillBuffer(const CVoIPAudioDownlinkStream& aSrc,
+ CVoIPDataBuffer* aBuffer)
+{
+ play_cb_(aBuffer, user_data_);
+ iVoIPDnlink->BufferFilled(aBuffer);
+}
+
+// Callback from MVoIPUplinkObserver
+void CPjAudioEngine::EmptyBuffer(const CVoIPAudioUplinkStream& aSrc,
+ CVoIPDataBuffer* aBuffer)
+{
+ rec_cb_(aBuffer, user_data_);
+ iVoIPUplink->BufferEmptied(aBuffer);
+}
+
+// Callback from MVoIPDownlinkObserver
+void CPjAudioEngine::Event(const CVoIPAudioDownlinkStream& /*aSrc*/,
+ TInt aEventType,
+ TInt aError)
+{
+ switch (aEventType) {
+ case MVoIPDownlinkObserver::KOpenComplete:
+ if (aError == KErrNone) {
+ State last_state = dn_state_;
+
+ dn_state_ = STATE_READY;
+ TRACE_((THIS_FILE, "Downlink opened"));
+
+ if (last_state == STATE_STARTING)
+ StartPlay();
+ }
+ break;
+
+ case MVoIPDownlinkObserver::KDownlinkClosed:
+ dn_state_ = STATE_NULL;
+ TRACE_((THIS_FILE, "Downlink closed"));
+ break;
+
+ case MVoIPDownlinkObserver::KDownlinkError:
+ dn_state_ = STATE_READY;
+ snd_perror("Downlink problem", aError);
+ break;
+ default:
+ break;
+ }
+}
+
+// Callback from MVoIPUplinkObserver
+void CPjAudioEngine::Event(const CVoIPAudioUplinkStream& /*aSrc*/,
+ TInt aEventType,
+ TInt aError)
+{
+ switch (aEventType) {
+ case MVoIPUplinkObserver::KOpenComplete:
+ if (aError == KErrNone) {
+ State last_state = up_state_;
+
+ up_state_ = STATE_READY;
+ TRACE_((THIS_FILE, "Uplink opened"));
+
+ if (last_state == STATE_STARTING)
+ StartRec();
+ }
+ break;
+
+ case MVoIPUplinkObserver::KUplinkClosed:
+ up_state_ = STATE_NULL;
+ TRACE_((THIS_FILE, "Uplink closed"));
+ break;
+
+ case MVoIPUplinkObserver::KUplinkError:
+ up_state_ = STATE_READY;
+ snd_perror("Uplink problem", aError);
+ break;
+ default:
+ break;
+ }
+}
+
+// Callback from MVoIPFormatObserver
+void CPjAudioEngine::Event(const CVoIPFormatIntfc& /*aSrc*/,
+ TInt aEventType)
+{
+ snd_perror("Format event", aEventType);
+}
+
+/****************************************************************************
+ * Internal VAS callbacks for PCM format
+ */
+
+#ifdef USE_NATIVE_PCM
+
+static void RecCbPcm2(CVoIPDataBuffer *buf, void *user_data)
+{
+ struct vas_stream *strm = (struct vas_stream*) user_data;
+ TPtr8 buffer(0, 0, 0);
+ pj_int16_t *p_buf;
+ unsigned buf_len;
+
+ /* Get the buffer */
+ buf->GetPayloadPtr(buffer);
+
+ /* Call parent callback */
+ p_buf = (pj_int16_t*) buffer.Ptr();
+ buf_len = buffer.Length() >> 1;
+ while (buf_len) {
+ unsigned req;
+
+ req = strm->param.samples_per_frame - strm->rec_buf_len;
+ if (req > buf_len)
+ req = buf_len;
+ pjmedia_copy_samples(strm->rec_buf + strm->rec_buf_len, p_buf, req);
+ p_buf += req;
+ buf_len -= req;
+ strm->rec_buf_len += req;
+
+ if (strm->rec_buf_len >= strm->param.samples_per_frame) {
+ pjmedia_frame f;
+
+ f.buf = strm->rec_buf;
+ f.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ f.size = strm->param.samples_per_frame << 1;
+ strm->rec_cb(strm->user_data, &f);
+ strm->rec_buf_len = 0;
+ }
+ }
+}
+
+static void PlayCbPcm2(CVoIPDataBuffer *buf, void *user_data)
+{
+ struct vas_stream *strm = (struct vas_stream*) user_data;
+ TPtr8 buffer(0, 0, 0);
+ pjmedia_frame f;
+
+ /* Get the buffer */
+ buf->GetPayloadPtr(buffer);
+
+ /* Call parent callback */
+ f.buf = strm->play_buf;
+ f.size = strm->param.samples_per_frame << 1;
+ strm->play_cb(strm->user_data, &f);
+ if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) {
+ pjmedia_zero_samples((pj_int16_t*)f.buf,
+ strm->param.samples_per_frame);
+ }
+ f.size = strm->param.samples_per_frame << 1;
+
+ /* Init buffer attributes and header. */
+ buffer.Zero();
+ buffer.Append((TUint8*)f.buf, f.size);
+
+ /* Set the buffer */
+ buf->SetPayloadPtr(buffer);
+}
+
+#else // not USE_NATIVE_PCM
+
+static void RecCbPcm(CVoIPDataBuffer *buf, void *user_data)
+{
+ struct vas_stream *strm = (struct vas_stream*) user_data;
+ TPtr8 buffer(0, 0, 0);
+
+ /* Get the buffer */
+ buf->GetPayloadPtr(buffer);
+
+ /* Buffer has to contain normal speech. */
+ pj_assert(buffer[0] == 1 && buffer[1] == 0);
+
+ /* Detect the recorder G.711 frame size, player frame size will follow
+ * this recorder frame size.
+ */
+ if (vas_g711_frame_len == 0) {
+ vas_g711_frame_len = buffer.Length() < 160? 80 : 160;
+ TRACE_((THIS_FILE, "Detected VAS G.711 frame size = %u samples",
+ vas_g711_frame_len));
+ }
+
+ /* Decode VAS buffer (coded in G.711) and put the PCM result into rec_buf.
+ * Whenever rec_buf is full, call parent stream callback.
+ */
+ unsigned samples_processed = 0;
+
+ while (samples_processed < vas_g711_frame_len) {
+ unsigned samples_to_process;
+ unsigned samples_req;
+
+ samples_to_process = vas_g711_frame_len - samples_processed;
+ samples_req = (strm->param.samples_per_frame /
+ strm->param.channel_count /
+ strm->resample_factor) -
+ strm->rec_buf_len;
+ if (samples_to_process > samples_req)
+ samples_to_process = samples_req;
+
+ pjmedia_ulaw_decode(&strm->rec_buf[strm->rec_buf_len],
+ buffer.Ptr() + 2 + samples_processed,
+ samples_to_process);
+
+ strm->rec_buf_len += samples_to_process;
+ samples_processed += samples_to_process;
+
+ /* Buffer is full, time to call parent callback */
+ if (strm->rec_buf_len == strm->param.samples_per_frame /
+ strm->param.channel_count /
+ strm->resample_factor)
+ {
+ pjmedia_frame f;
+
+ /* Need to resample clock rate? */
+ if (strm->rec_resample) {
+ unsigned resampled = 0;
+
+ while (resampled < strm->rec_buf_len) {
+ pjmedia_resample_run(strm->rec_resample,
+ &strm->rec_buf[resampled],
+ strm->pcm_buf +
+ resampled * strm->resample_factor);
+ resampled += 80;
+ }
+ f.buf = strm->pcm_buf;
+ } else {
+ f.buf = strm->rec_buf;
+ }
+
+ /* Need to convert channel count? */
+ if (strm->param.channel_count != 1) {
+ pjmedia_convert_channel_1ton((pj_int16_t*)f.buf,
+ (pj_int16_t*)f.buf,
+ strm->param.channel_count,
+ strm->param.samples_per_frame /
+ strm->param.channel_count,
+ 0);
+ }
+
+ /* Call parent callback */
+ f.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ f.size = strm->param.samples_per_frame << 1;
+ strm->rec_cb(strm->user_data, &f);
+ strm->rec_buf_len = 0;
+ }
+ }
+}
+
+#endif // USE_NATIVE_PCM
+
+static void PlayCbPcm(CVoIPDataBuffer *buf, void *user_data)
+{
+ struct vas_stream *strm = (struct vas_stream*) user_data;
+ unsigned g711_frame_len = vas_g711_frame_len;
+ TPtr8 buffer(0, 0, 0);
+
+ /* Get the buffer */
+ buf->GetPayloadPtr(buffer);
+
+ /* Init buffer attributes and header. */
+ buffer.Zero();
+ buffer.Append(1);
+ buffer.Append(0);
+
+ /* Assume frame size is 10ms if frame size hasn't been known. */
+ if (g711_frame_len == 0)
+ g711_frame_len = 80;
+
+ /* Call parent stream callback to get PCM samples to play,
+ * encode the PCM samples into G.711 and put it into VAS buffer.
+ */
+ unsigned samples_processed = 0;
+
+ while (samples_processed < g711_frame_len) {
+ /* Need more samples to play, time to call parent callback */
+ if (strm->play_buf_len == 0) {
+ pjmedia_frame f;
+ unsigned samples_got;
+
+ f.size = strm->param.samples_per_frame << 1;
+ if (strm->play_resample || strm->param.channel_count != 1)
+ f.buf = strm->pcm_buf;
+ else
+ f.buf = strm->play_buf;
+
+ /* Call parent callback */
+ strm->play_cb(strm->user_data, &f);
+ if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) {
+ pjmedia_zero_samples((pj_int16_t*)f.buf,
+ strm->param.samples_per_frame);
+ }
+
+ samples_got = strm->param.samples_per_frame /
+ strm->param.channel_count /
+ strm->resample_factor;
+
+ /* Need to convert channel count? */
+ if (strm->param.channel_count != 1) {
+ pjmedia_convert_channel_nto1((pj_int16_t*)f.buf,
+ (pj_int16_t*)f.buf,
+ strm->param.channel_count,
+ strm->param.samples_per_frame,
+ PJ_FALSE,
+ 0);
+ }
+
+ /* Need to resample clock rate? */
+ if (strm->play_resample) {
+ unsigned resampled = 0;
+
+ while (resampled < samples_got)
+ {
+ pjmedia_resample_run(strm->play_resample,
+ strm->pcm_buf +
+ resampled * strm->resample_factor,
+ &strm->play_buf[resampled]);
+ resampled += 80;
+ }
+ }
+
+ strm->play_buf_len = samples_got;
+ strm->play_buf_start = 0;
+ }
+
+ unsigned tmp;
+
+ tmp = PJ_MIN(strm->play_buf_len, g711_frame_len - samples_processed);
+ pjmedia_ulaw_encode((pj_uint8_t*)&strm->play_buf[strm->play_buf_start],
+ &strm->play_buf[strm->play_buf_start],
+ tmp);
+ buffer.Append((TUint8*)&strm->play_buf[strm->play_buf_start], tmp);
+ samples_processed += tmp;
+ strm->play_buf_len -= tmp;
+ strm->play_buf_start += tmp;
+ }
+
+ /* Set the buffer */
+ buf->SetPayloadPtr(buffer);
+}
+
+/****************************************************************************
+ * Internal VAS callbacks for non-PCM format
+ */
+
+static void RecCb(CVoIPDataBuffer *buf, void *user_data)
+{
+ struct vas_stream *strm = (struct vas_stream*) user_data;
+ pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->rec_buf;
+ TPtr8 buffer(0, 0, 0);
+
+ /* Get the buffer */
+ buf->GetPayloadPtr(buffer);
+
+ switch(strm->param.ext_fmt.id) {
+ case PJMEDIA_FORMAT_AMR:
+ {
+ const pj_uint8_t *p = (const pj_uint8_t*)buffer.Ptr() + 1;
+ unsigned len = buffer.Length() - 1;
+
+ pjmedia_frame_ext_append_subframe(frame, p, len << 3, 160);
+ if (frame->samples_cnt == strm->param.samples_per_frame) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ break;
+
+ case PJMEDIA_FORMAT_G729:
+ {
+ /* Check if we got a normal or SID frame. */
+ if (buffer[0] != 0) {
+ enum { NORMAL_LEN = 22, SID_LEN = 8 };
+ TBitStream *bitstream = (TBitStream*)strm->strm_data;
+ unsigned src_len = buffer.Length()- 2;
+
+ pj_assert(src_len == NORMAL_LEN || src_len == SID_LEN);
+
+ const TDesC8& p = bitstream->CompressG729Frame(
+ buffer.Right(src_len),
+ src_len == SID_LEN);
+
+ pjmedia_frame_ext_append_subframe(frame, p.Ptr(),
+ p.Length() << 3, 80);
+ } else { /* We got null frame. */
+ pjmedia_frame_ext_append_subframe(frame, NULL, 0, 80);
+ }
+
+ if (frame->samples_cnt == strm->param.samples_per_frame) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ break;
+
+ case PJMEDIA_FORMAT_ILBC:
+ {
+ unsigned samples_got;
+
+ samples_got = strm->param.ext_fmt.bitrate == 15200? 160 : 240;
+
+ /* Check if we got a normal or SID frame. */
+ if (buffer[0] != 0) {
+ const pj_uint8_t *p = (const pj_uint8_t*)buffer.Ptr() + 2;
+ unsigned len = buffer.Length() - 2;
+
+ pjmedia_frame_ext_append_subframe(frame, p, len << 3,
+ samples_got);
+ } else { /* We got null frame. */
+ pjmedia_frame_ext_append_subframe(frame, NULL, 0, samples_got);
+ }
+
+ if (frame->samples_cnt == strm->param.samples_per_frame) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ break;
+
+ case PJMEDIA_FORMAT_PCMU:
+ case PJMEDIA_FORMAT_PCMA:
+ {
+ unsigned samples_processed = 0;
+
+ /* Make sure it is normal frame. */
+ pj_assert(buffer[0] == 1 && buffer[1] == 0);
+
+ /* Detect the recorder G.711 frame size, player frame size will
+ * follow this recorder frame size.
+ */
+ if (vas_g711_frame_len == 0) {
+ vas_g711_frame_len = buffer.Length() < 160? 80 : 160;
+ TRACE_((THIS_FILE, "Detected VAS G.711 frame size = %u samples",
+ vas_g711_frame_len));
+ }
+
+ /* Convert VAS buffer format into pjmedia_frame_ext. Whenever
+ * samples count in the frame is equal to stream's samples per
+ * frame, call parent stream callback.
+ */
+ while (samples_processed < vas_g711_frame_len) {
+ unsigned tmp;
+ const pj_uint8_t *pb = (const pj_uint8_t*)buffer.Ptr() +
+ 2 + samples_processed;
+
+ tmp = PJ_MIN(strm->param.samples_per_frame - frame->samples_cnt,
+ vas_g711_frame_len - samples_processed);
+
+ pjmedia_frame_ext_append_subframe(frame, pb, tmp << 3, tmp);
+ samples_processed += tmp;
+
+ if (frame->samples_cnt == strm->param.samples_per_frame) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void PlayCb(CVoIPDataBuffer *buf, void *user_data)
+{
+ struct vas_stream *strm = (struct vas_stream*) user_data;
+ pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->play_buf;
+ TPtr8 buffer(0, 0, 0);
+
+ /* Get the buffer */
+ buf->GetPayloadPtr(buffer);
+
+ /* Init buffer attributes and header. */
+ buffer.Zero();
+
+ switch(strm->param.ext_fmt.id) {
+ case PJMEDIA_FORMAT_AMR:
+ {
+ if (frame->samples_cnt == 0) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
+ pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
+ frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
+ }
+
+ if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ pjmedia_frame_ext_subframe *sf;
+ unsigned samples_cnt;
+
+ sf = pjmedia_frame_ext_get_subframe(frame, 0);
+ samples_cnt = frame->samples_cnt / frame->subframe_cnt;
+
+ if (sf->data && sf->bitlen) {
+ /* AMR header for VAS is one byte, the format (may be!):
+ * 0xxxxy00, where xxxx:frame type, y:not sure.
+ */
+ unsigned len = (sf->bitlen+7)>>3;
+ enum {SID_FT = 8 };
+ pj_uint8_t amr_header = 4, ft = SID_FT;
+
+ if (len >= pjmedia_codec_amrnb_framelen[0])
+ ft = pjmedia_codec_amr_get_mode2(PJ_TRUE, len);
+
+ amr_header |= ft << 3;
+ buffer.Append(amr_header);
+
+ buffer.Append((TUint8*)sf->data, len);
+ } else {
+ enum {NO_DATA_FT = 15 };
+ pj_uint8_t amr_header = 4 | (NO_DATA_FT << 3);
+
+ buffer.Append(amr_header);
+ }
+
+ pjmedia_frame_ext_pop_subframes(frame, 1);
+
+ } else { /* PJMEDIA_FRAME_TYPE_NONE */
+ enum {NO_DATA_FT = 15 };
+ pj_uint8_t amr_header = 4 | (NO_DATA_FT << 3);
+
+ buffer.Append(amr_header);
+
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ break;
+
+ case PJMEDIA_FORMAT_G729:
+ {
+ if (frame->samples_cnt == 0) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
+ pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
+ frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
+ }
+
+ if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ pjmedia_frame_ext_subframe *sf;
+ unsigned samples_cnt;
+
+ sf = pjmedia_frame_ext_get_subframe(frame, 0);
+ samples_cnt = frame->samples_cnt / frame->subframe_cnt;
+
+ if (sf->data && sf->bitlen) {
+ enum { NORMAL_LEN = 10, SID_LEN = 2 };
+ pj_bool_t sid_frame = ((sf->bitlen >> 3) == SID_LEN);
+ TBitStream *bitstream = (TBitStream*)strm->strm_data;
+ const TPtrC8 src(sf->data, sf->bitlen>>3);
+ const TDesC8 &dst = bitstream->ExpandG729Frame(src,
+ sid_frame);
+ if (sid_frame) {
+ buffer.Append(2);
+ buffer.Append(0);
+ } else {
+ buffer.Append(1);
+ buffer.Append(0);
+ }
+ buffer.Append(dst);
+ } else {
+ buffer.Append(2);
+ buffer.Append(0);
+
+ buffer.AppendFill(0, 22);
+ }
+
+ pjmedia_frame_ext_pop_subframes(frame, 1);
+
+ } else { /* PJMEDIA_FRAME_TYPE_NONE */
+ buffer.Append(2);
+ buffer.Append(0);
+
+ buffer.AppendFill(0, 22);
+ }
+ }
+ break;
+
+ case PJMEDIA_FORMAT_ILBC:
+ {
+ if (frame->samples_cnt == 0) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
+ pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
+ frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
+ }
+
+ if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ pjmedia_frame_ext_subframe *sf;
+ unsigned samples_cnt;
+
+ sf = pjmedia_frame_ext_get_subframe(frame, 0);
+ samples_cnt = frame->samples_cnt / frame->subframe_cnt;
+
+ pj_assert((strm->param.ext_fmt.bitrate == 15200 &&
+ samples_cnt == 160) ||
+ (strm->param.ext_fmt.bitrate != 15200 &&
+ samples_cnt == 240));
+
+ if (sf->data && sf->bitlen) {
+ buffer.Append(1);
+ buffer.Append(0);
+ buffer.Append((TUint8*)sf->data, sf->bitlen>>3);
+ } else {
+ unsigned frame_len;
+
+ buffer.Append(1);
+ buffer.Append(0);
+
+ /* VAS iLBC frame is 20ms or 30ms */
+ frame_len = strm->param.ext_fmt.bitrate == 15200? 38 : 50;
+ buffer.AppendFill(0, frame_len);
+ }
+
+ pjmedia_frame_ext_pop_subframes(frame, 1);
+
+ } else { /* PJMEDIA_FRAME_TYPE_NONE */
+
+ unsigned frame_len;
+
+ buffer.Append(1);
+ buffer.Append(0);
+
+ /* VAS iLBC frame is 20ms or 30ms */
+ frame_len = strm->param.ext_fmt.bitrate == 15200? 38 : 50;
+ buffer.AppendFill(0, frame_len);
+
+ }
+ }
+ break;
+
+ case PJMEDIA_FORMAT_PCMU:
+ case PJMEDIA_FORMAT_PCMA:
+ {
+ unsigned samples_ready = 0;
+ unsigned samples_req = vas_g711_frame_len;
+
+ /* Assume frame size is 10ms if frame size hasn't been known. */
+ if (samples_req == 0)
+ samples_req = 80;
+
+ buffer.Append(1);
+ buffer.Append(0);
+
+ /* Call parent stream callback to get samples to play. */
+ while (samples_ready < samples_req) {
+ if (frame->samples_cnt == 0) {
+ frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
+ pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
+ frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
+ }
+
+ if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ pjmedia_frame_ext_subframe *sf;
+ unsigned samples_cnt;
+
+ sf = pjmedia_frame_ext_get_subframe(frame, 0);
+ samples_cnt = frame->samples_cnt / frame->subframe_cnt;
+ if (sf->data && sf->bitlen) {
+ buffer.Append((TUint8*)sf->data, sf->bitlen>>3);
+ } else {
+ pj_uint8_t silc;
+ silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)?
+ pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0);
+ buffer.AppendFill(silc, samples_cnt);
+ }
+ samples_ready += samples_cnt;
+
+ pjmedia_frame_ext_pop_subframes(frame, 1);
+
+ } else { /* PJMEDIA_FRAME_TYPE_NONE */
+ pj_uint8_t silc;
+
+ silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)?
+ pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0);
+ buffer.AppendFill(silc, samples_req - samples_ready);
+
+ samples_ready = samples_req;
+ frame->samples_cnt = 0;
+ frame->subframe_cnt = 0;
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ /* Set the buffer */
+ buf->SetPayloadPtr(buffer);
+}
+
+
+/****************************************************************************
+ * Factory operations
+ */
+
+/*
+ * C compatible declaration of VAS factory.
+ */
+PJ_BEGIN_DECL
+PJ_DECL(pjmedia_aud_dev_factory*)pjmedia_symb_vas_factory(pj_pool_factory *pf);
+PJ_END_DECL
+
+/*
+ * Init VAS audio driver.
+ */
+PJ_DEF(pjmedia_aud_dev_factory*) pjmedia_symb_vas_factory(pj_pool_factory *pf)
+{
+ struct vas_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "VAS", 1000, 1000, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct vas_factory);
+ f->pf = pf;
+ f->pool = pool;
+ f->base.op = &factory_op;
+
+ return &f->base;
+}
+
+/* API: init factory */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
+{
+ struct vas_factory *af = (struct vas_factory*)f;
+ CVoIPUtilityFactory *vas_factory_;
+ CVoIPAudioUplinkStream *vas_uplink;
+ CVoIPAudioDownlinkStream *vas_dnlink;
+ RArray<TVoIPCodecFormat> uplink_formats, dnlink_formats;
+ unsigned ext_fmt_cnt = 0;
+ TVersion vas_version(1, 0, 0); /* Not really used at this time */
+ TInt err;
+
+ pj_ansi_strcpy(af->dev_info.name, "S60 VAS");
+ af->dev_info.default_samples_per_sec = 8000;
+ af->dev_info.caps = PJMEDIA_AUD_DEV_CAP_EXT_FORMAT |
+ //PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE |
+ PJMEDIA_AUD_DEV_CAP_VAD |
+ PJMEDIA_AUD_DEV_CAP_CNG;
+ af->dev_info.routes = PJMEDIA_AUD_DEV_ROUTE_EARPIECE |
+ PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
+ af->dev_info.input_count = 1;
+ af->dev_info.output_count = 1;
+ af->dev_info.ext_fmt_cnt = 0;
+
+ /* Enumerate supported formats */
+ err = CVoIPUtilityFactory::CreateFactory(vas_factory_);
+ if (err != KErrNone)
+ goto on_error;
+
+ /* On VAS 2.0, uplink & downlink stream should be instantiated before
+ * querying formats.
+ */
+ err = vas_factory_->CreateUplinkStream(vas_version,
+ CVoIPUtilityFactory::EVoIPCall,
+ vas_uplink);
+ if (err != KErrNone)
+ goto on_error;
+
+ err = vas_factory_->CreateDownlinkStream(vas_version,
+ CVoIPUtilityFactory::EVoIPCall,
+ vas_dnlink);
+ if (err != KErrNone)
+ goto on_error;
+
+ uplink_formats.Reset();
+ err = vas_factory_->GetSupportedUplinkFormats(uplink_formats);
+ if (err != KErrNone)
+ goto on_error;
+
+ dnlink_formats.Reset();
+ err = vas_factory_->GetSupportedDownlinkFormats(dnlink_formats);
+ if (err != KErrNone)
+ goto on_error;
+
+ /* Free the streams, they are just used for querying formats */
+ delete vas_uplink;
+ vas_uplink = NULL;
+ delete vas_dnlink;
+ vas_dnlink = NULL;
+ delete vas_factory_;
+ vas_factory_ = NULL;
+
+ for (TInt i = 0; i < dnlink_formats.Count(); i++) {
+ /* Format must be supported by both downlink & uplink. */
+ if (uplink_formats.Find(dnlink_formats[i]) == KErrNotFound)
+ continue;
+
+ switch (dnlink_formats[i]) {
+ case EAMR_NB:
+ af->dev_info.ext_fmt[ext_fmt_cnt].id = PJMEDIA_FORMAT_AMR;
+ af->dev_info.ext_fmt[ext_fmt_cnt].bitrate = 7400;
+ af->dev_info.ext_fmt[ext_fmt_cnt].vad = PJ_TRUE;
+ break;
+
+ case EG729:
+ af->dev_info.ext_fmt[ext_fmt_cnt].id = PJMEDIA_FORMAT_G729;
+ af->dev_info.ext_fmt[ext_fmt_cnt].bitrate = 8000;
+ af->dev_info.ext_fmt[ext_fmt_cnt].vad = PJ_FALSE;
+ break;
+
+ case EILBC:
+ af->dev_info.ext_fmt[ext_fmt_cnt].id = PJMEDIA_FORMAT_ILBC;
+ af->dev_info.ext_fmt[ext_fmt_cnt].bitrate = 13333;
+ af->dev_info.ext_fmt[ext_fmt_cnt].vad = PJ_TRUE;
+ break;
+
+ case EG711:
+#if PJMEDIA_AUDIO_DEV_SYMB_VAS_VERSION==2
+ case EG711_10MS:
+#endif
+ af->dev_info.ext_fmt[ext_fmt_cnt].id = PJMEDIA_FORMAT_PCMU;
+ af->dev_info.ext_fmt[ext_fmt_cnt].bitrate = 64000;
+ af->dev_info.ext_fmt[ext_fmt_cnt].vad = PJ_FALSE;
+ ++ext_fmt_cnt;
+ af->dev_info.ext_fmt[ext_fmt_cnt].id = PJMEDIA_FORMAT_PCMA;
+ af->dev_info.ext_fmt[ext_fmt_cnt].bitrate = 64000;
+ af->dev_info.ext_fmt[ext_fmt_cnt].vad = PJ_FALSE;
+ break;
+
+ default:
+ continue;
+ }
+
+ ++ext_fmt_cnt;
+ }
+
+ af->dev_info.ext_fmt_cnt = ext_fmt_cnt;
+
+ uplink_formats.Close();
+ dnlink_formats.Close();
+
+ PJ_LOG(3, (THIS_FILE, "VAS initialized"));
+
+ return PJ_SUCCESS;
+
+on_error:
+ return PJ_RETURN_OS_ERROR(err);
+}
+
+/* API: destroy factory */
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
+{
+ struct vas_factory *af = (struct vas_factory*)f;
+ pj_pool_t *pool = af->pool;
+
+ af->pool = NULL;
+ pj_pool_release(pool);
+
+ PJ_LOG(3, (THIS_FILE, "VAS destroyed"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: refresh the device list */
+static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f)
+{
+ PJ_UNUSED_ARG(f);
+ return PJ_ENOTSUP;
+}
+
+/* API: get number of devices */
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+ PJ_UNUSED_ARG(f);
+ return 1;
+}
+
+/* API: get device info */
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info)
+{
+ struct vas_factory *af = (struct vas_factory*)f;
+
+ PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
+
+ pj_memcpy(info, &af->dev_info, sizeof(*info));
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param)
+{
+ struct vas_factory *af = (struct vas_factory*)f;
+
+ PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
+
+ pj_bzero(param, sizeof(*param));
+ param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param->rec_id = index;
+ param->play_id = index;
+ param->clock_rate = af->dev_info.default_samples_per_sec;
+ param->channel_count = 1;
+ param->samples_per_frame = af->dev_info.default_samples_per_sec * 20 / 1000;
+ param->bits_per_sample = BITS_PER_SAMPLE;
+ param->flags = PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE;
+ param->output_route = PJMEDIA_AUD_DEV_ROUTE_EARPIECE;
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: create stream */
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm)
+{
+ struct vas_factory *af = (struct vas_factory*)f;
+ pj_pool_t *pool;
+ struct vas_stream *strm;
+
+ CPjAudioSetting vas_setting;
+ PjAudioCallback vas_rec_cb;
+ PjAudioCallback vas_play_cb;
+
+ /* Can only support 16bits per sample */
+ PJ_ASSERT_RETURN(param->bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL);
+
+ /* Supported clock rates:
+ * - for non-PCM format: 8kHz
+ * - for PCM format: 8kHz and 16kHz
+ */
+ PJ_ASSERT_RETURN(param->clock_rate == 8000 ||
+ (param->clock_rate == 16000 &&
+ param->ext_fmt.id == PJMEDIA_FORMAT_L16),
+ PJ_EINVAL);
+
+ /* Supported channels number:
+ * - for non-PCM format: mono
+ * - for PCM format: mono and stereo
+ */
+ PJ_ASSERT_RETURN(param->channel_count == 1 ||
+ (param->channel_count == 2 &&
+ param->ext_fmt.id == PJMEDIA_FORMAT_L16),
+ PJ_EINVAL);
+
+ /* Create and Initialize stream descriptor */
+ pool = pj_pool_create(af->pf, "vas-dev", 1000, 1000, NULL);
+ PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
+
+ strm = PJ_POOL_ZALLOC_T(pool, struct vas_stream);
+ strm->pool = pool;
+ strm->param = *param;
+
+ if (strm->param.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT == 0)
+ strm->param.ext_fmt.id = PJMEDIA_FORMAT_L16;
+
+ /* Set audio engine fourcc. */
+ switch(strm->param.ext_fmt.id) {
+ case PJMEDIA_FORMAT_L16:
+#ifdef USE_NATIVE_PCM
+ vas_setting.format = EPCM16;
+#else
+ vas_setting.format = EG711;
+#endif
+ break;
+ case PJMEDIA_FORMAT_PCMU:
+ case PJMEDIA_FORMAT_PCMA:
+ vas_setting.format = EG711;
+ break;
+ case PJMEDIA_FORMAT_AMR:
+ vas_setting.format = EAMR_NB;
+ break;
+ case PJMEDIA_FORMAT_G729:
+ vas_setting.format = EG729;
+ break;
+ case PJMEDIA_FORMAT_ILBC:
+ vas_setting.format = EILBC;
+ break;
+ default:
+ vas_setting.format = ENULL;
+ break;
+ }
+
+ /* Set audio engine mode. */
+ if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16)
+ {
+#ifdef USE_NATIVE_PCM
+ vas_setting.mode = 0;
+#else
+ vas_setting.mode = CVoIPFormatIntfc::EG711uLaw;
+#endif
+ }
+ else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_AMR)
+ {
+ vas_setting.mode = strm->param.ext_fmt.bitrate;
+ }
+ else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU)
+ {
+ vas_setting.mode = CVoIPFormatIntfc::EG711uLaw;
+ }
+ else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA)
+ {
+ vas_setting.mode = CVoIPFormatIntfc::EG711ALaw;
+ }
+ else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC)
+ {
+ if (strm->param.ext_fmt.bitrate == 15200)
+ vas_setting.mode = CVoIPFormatIntfc::EiLBC20mSecFrame;
+ else
+ vas_setting.mode = CVoIPFormatIntfc::EiLBC30mSecFrame;
+ } else {
+ vas_setting.mode = 0;
+ }
+
+ /* Disable VAD on L16, G711, iLBC, and also G729 (G729's SID
+ * potentially cause noise?).
+ */
+ if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU ||
+ strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA ||
+ strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 ||
+ strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC ||
+ strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729)
+ {
+ vas_setting.vad = EFalse;
+ } else {
+ vas_setting.vad = strm->param.ext_fmt.vad;
+ }
+
+ /* Set other audio engine attributes. */
+ vas_setting.plc = strm->param.plc_enabled;
+ vas_setting.cng = vas_setting.vad;
+ vas_setting.loudspk =
+ strm->param.output_route==PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
+
+ /* Set audio engine callbacks. */
+ if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16) {
+#ifdef USE_NATIVE_PCM
+ vas_play_cb = &PlayCbPcm2;
+ vas_rec_cb = &RecCbPcm2;
+#else
+ vas_play_cb = &PlayCbPcm;
+ vas_rec_cb = &RecCbPcm;
+#endif
+ } else {
+ vas_play_cb = &PlayCb;
+ vas_rec_cb = &RecCb;
+ }
+
+ strm->rec_cb = rec_cb;
+ strm->play_cb = play_cb;
+ strm->user_data = user_data;
+ strm->resample_factor = strm->param.clock_rate / 8000;
+
+ /* play_buf size is samples per frame scaled in to 8kHz mono. */
+ strm->play_buf = (pj_int16_t*)pj_pool_zalloc(
+ pool,
+ (strm->param.samples_per_frame /
+ strm->resample_factor /
+ strm->param.channel_count) << 1);
+ strm->play_buf_len = 0;
+ strm->play_buf_start = 0;
+
+ /* rec_buf size is samples per frame scaled in to 8kHz mono. */
+ strm->rec_buf = (pj_int16_t*)pj_pool_zalloc(
+ pool,
+ (strm->param.samples_per_frame /
+ strm->resample_factor /
+ strm->param.channel_count) << 1);
+ strm->rec_buf_len = 0;
+
+ if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729) {
+ TBitStream *g729_bitstream = new TBitStream;
+
+ PJ_ASSERT_RETURN(g729_bitstream, PJ_ENOMEM);
+ strm->strm_data = (void*)g729_bitstream;
+ }
+
+ /* Init resampler when format is PCM and clock rate is not 8kHz */
+ if (strm->param.clock_rate != 8000 &&
+ strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16)
+ {
+ pj_status_t status;
+
+ if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+ /* Create resample for recorder */
+ status = pjmedia_resample_create( pool, PJ_TRUE, PJ_FALSE, 1,
+ 8000,
+ strm->param.clock_rate,
+ 80,
+ &strm->rec_resample);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ /* Create resample for player */
+ status = pjmedia_resample_create( pool, PJ_TRUE, PJ_FALSE, 1,
+ strm->param.clock_rate,
+ 8000,
+ 80 * strm->resample_factor,
+ &strm->play_resample);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+ }
+
+ /* Create PCM buffer, when the clock rate is not 8kHz or not mono */
+ if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 &&
+ (strm->resample_factor > 1 || strm->param.channel_count != 1))
+ {
+ strm->pcm_buf = (pj_int16_t*)pj_pool_zalloc(pool,
+ strm->param.samples_per_frame << 1);
+ }
+
+
+ /* Create the audio engine. */
+ TRAPD(err, strm->engine = CPjAudioEngine::NewL(strm,
+ vas_rec_cb, vas_play_cb,
+ strm, vas_setting));
+ if (err != KErrNone) {
+ pj_pool_release(pool);
+ return PJ_RETURN_OS_ERROR(err);
+ }
+
+ /* Done */
+ strm->base.op = &stream_op;
+ *p_aud_strm = &strm->base;
+
+ return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t stream_get_param(pjmedia_aud_stream *s,
+ pjmedia_aud_param *pi)
+{
+ struct vas_stream *strm = (struct vas_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+ /* Update the output volume setting */
+ if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &pi->output_vol) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ void *pval)
+{
+ struct vas_stream *strm = (struct vas_stream*)s;
+ pj_status_t status = PJ_ENOTSUP;
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ switch (cap) {
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE:
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ *(pjmedia_aud_dev_route*)pval = strm->param.output_route;
+ status = PJ_SUCCESS;
+ }
+ break;
+
+ /* There is a case that GetMaxGain() stucks, e.g: in N95. */
+ /*
+ case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
+ if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+ PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+
+ TInt max_gain = strm->engine->GetMaxGain();
+ TInt gain = strm->engine->GetGain();
+
+ if (max_gain > 0 && gain >= 0) {
+ *(unsigned*)pval = gain * 100 / max_gain;
+ status = PJ_SUCCESS;
+ } else {
+ status = PJMEDIA_EAUD_NOTREADY;
+ }
+ }
+ break;
+ */
+
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+
+ TInt max_vol = strm->engine->GetMaxVolume();
+ TInt vol = strm->engine->GetVolume();
+
+ if (max_vol > 0 && vol >= 0) {
+ *(unsigned*)pval = vol * 100 / max_vol;
+ status = PJ_SUCCESS;
+ } else {
+ status = PJMEDIA_EAUD_NOTREADY;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return status;
+}
+
+/* API: set capability */
+static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ const void *pval)
+{
+ struct vas_stream *strm = (struct vas_stream*)s;
+ pj_status_t status = PJ_ENOTSUP;
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ switch (cap) {
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE:
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ pjmedia_aud_dev_route r = *(const pjmedia_aud_dev_route*)pval;
+ TInt err;
+
+ PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+
+ switch (r) {
+ case PJMEDIA_AUD_DEV_ROUTE_DEFAULT:
+ case PJMEDIA_AUD_DEV_ROUTE_EARPIECE:
+ err = strm->engine->ActivateSpeaker(EFalse);
+ status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
+ break;
+ case PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER:
+ err = strm->engine->ActivateSpeaker(ETrue);
+ status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
+ break;
+ default:
+ status = PJ_EINVAL;
+ break;
+ }
+ if (status == PJ_SUCCESS)
+ strm->param.output_route = r;
+ }
+ break;
+
+ /* There is a case that GetMaxGain() stucks, e.g: in N95. */
+ /*
+ case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
+ if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
+ PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+
+ TInt max_gain = strm->engine->GetMaxGain();
+ if (max_gain > 0) {
+ TInt gain, err;
+
+ gain = *(unsigned*)pval * max_gain / 100;
+ err = strm->engine->SetGain(gain);
+ status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
+ } else {
+ status = PJMEDIA_EAUD_NOTREADY;
+ }
+ if (status == PJ_SUCCESS)
+ strm->param.input_vol = *(unsigned*)pval;
+ }
+ break;
+ */
+
+ case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
+ PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
+
+ TInt max_vol = strm->engine->GetMaxVolume();
+ if (max_vol > 0) {
+ TInt vol, err;
+
+ vol = *(unsigned*)pval * max_vol / 100;
+ err = strm->engine->SetVolume(vol);
+ status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
+ } else {
+ status = PJMEDIA_EAUD_NOTREADY;
+ }
+ if (status == PJ_SUCCESS)
+ strm->param.output_vol = *(unsigned*)pval;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return status;
+}
+
+/* API: Start stream. */
+static pj_status_t stream_start(pjmedia_aud_stream *strm)
+{
+ struct vas_stream *stream = (struct vas_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ if (stream->engine) {
+ enum { VAS_WAIT_START = 2000 }; /* in msecs */
+ TTime start, now;
+ TInt err = stream->engine->Start();
+
+ if (err != KErrNone)
+ return PJ_RETURN_OS_ERROR(err);
+
+ /* Perform synchronous start, timeout after VAS_WAIT_START ms */
+ start.UniversalTime();
+ do {
+ pj_symbianos_poll(-1, 100);
+ now.UniversalTime();
+ } while (!stream->engine->IsStarted() &&
+ (now.MicroSecondsFrom(start) < VAS_WAIT_START * 1000));
+
+ if (stream->engine->IsStarted()) {
+
+ /* Apply output volume setting if specified */
+ if (stream->param.flags &
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING)
+ {
+ stream_set_cap(strm,
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &stream->param.output_vol);
+ }
+
+ return PJ_SUCCESS;
+ } else {
+ return PJ_ETIMEDOUT;
+ }
+ }
+
+ return PJ_EINVALIDOP;
+}
+
+/* API: Stop stream. */
+static pj_status_t stream_stop(pjmedia_aud_stream *strm)
+{
+ struct vas_stream *stream = (struct vas_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ if (stream->engine) {
+ stream->engine->Stop();
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm)
+{
+ struct vas_stream *stream = (struct vas_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ stream_stop(strm);
+
+ delete stream->engine;
+ stream->engine = NULL;
+
+ if (stream->param.ext_fmt.id == PJMEDIA_FORMAT_G729) {
+ TBitStream *g729_bitstream = (TBitStream*)stream->strm_data;
+ stream->strm_data = NULL;
+ delete g729_bitstream;
+ }
+
+ pj_pool_t *pool;
+ pool = stream->pool;
+ if (pool) {
+ stream->pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+#endif // PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS
+
diff --git a/pjmedia/src/pjmedia-audiodev/wmme_dev.c b/pjmedia/src/pjmedia-audiodev/wmme_dev.c
new file mode 100644
index 0000000..3f9dff7
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/wmme_dev.c
@@ -0,0 +1,1524 @@
+/* $Id: wmme_dev.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia-audiodev/audiodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/string.h>
+#include <pj/unicode.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_WMME
+
+#ifdef _MSC_VER
+# pragma warning(push, 3)
+#endif
+
+#include <windows.h>
+#include <mmsystem.h>
+#include <mmreg.h>
+
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+
+#ifndef PJMEDIA_WMME_DEV_USE_MMDEVICE_API
+# define PJMEDIA_WMME_DEV_USE_MMDEVICE_API \
+ (defined(_WIN32_WINNT) && (_WIN32_WINNT>=0x0600))
+#endif
+
+#if PJMEDIA_WMME_DEV_USE_MMDEVICE_API != 0
+# define DRV_QUERYFUNCTIONINSTANCEID (DRV_RESERVED + 17)
+# define DRV_QUERYFUNCTIONINSTANCEIDSIZE (DRV_RESERVED + 18)
+#endif
+
+/* mingw lacks WAVE_FORMAT_ALAW/MULAW */
+#ifndef WAVE_FORMAT_ALAW
+# define WAVE_FORMAT_ALAW 0x0006
+#endif
+#ifndef WAVE_FORMAT_MULAW
+# define WAVE_FORMAT_MULAW 0x0007
+#endif
+
+#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE!=0
+# pragma comment(lib, "Coredll.lib")
+#elif defined(_MSC_VER)
+# pragma comment(lib, "winmm.lib")
+#endif
+
+
+#define THIS_FILE "wmme_dev.c"
+
+/* WMME device info */
+struct wmme_dev_info
+{
+ pjmedia_aud_dev_info info;
+ unsigned deviceId;
+ const wchar_t *endpointId;
+};
+
+/* WMME factory */
+struct wmme_factory
+{
+ pjmedia_aud_dev_factory base;
+ pj_pool_t *base_pool;
+ pj_pool_t *pool;
+ pj_pool_factory *pf;
+
+ unsigned dev_count;
+ struct wmme_dev_info *dev_info;
+};
+
+
+/* Individual WMME capture/playback stream descriptor */
+struct wmme_channel
+{
+ union
+ {
+ HWAVEIN In;
+ HWAVEOUT Out;
+ } hWave;
+
+ WAVEHDR *WaveHdr;
+ HANDLE hEvent;
+ DWORD dwBufIdx;
+ DWORD dwMaxBufIdx;
+ pj_timestamp timestamp;
+};
+
+
+/* Sound stream. */
+struct wmme_stream
+{
+ pjmedia_aud_stream base; /**< Base stream */
+ pjmedia_aud_param param; /**< Settings */
+ pj_pool_t *pool; /**< Memory pool. */
+
+ pjmedia_aud_rec_cb rec_cb; /**< Capture callback. */
+ pjmedia_aud_play_cb play_cb; /**< Playback callback. */
+ void *user_data; /**< Application data. */
+
+ struct wmme_channel play_strm; /**< Playback stream. */
+ struct wmme_channel rec_strm; /**< Capture stream. */
+
+ void *buffer; /**< Temp. frame buffer. */
+ pjmedia_format_id fmt_id; /**< Frame format */
+ pj_uint8_t silence_char; /**< Silence pattern */
+ unsigned bytes_per_frame; /**< Bytes per frame */
+
+ pjmedia_frame_ext *xfrm; /**< Extended frame buffer */
+ unsigned xfrm_size; /**< Total ext frm size */
+
+ pj_thread_t *thread; /**< Thread handle. */
+ HANDLE thread_quit_event; /**< Quit signal to thread */
+};
+
+
+/* Prototypes */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f);
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info);
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param);
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm);
+
+static pj_status_t stream_get_param(pjmedia_aud_stream *strm,
+ pjmedia_aud_param *param);
+static pj_status_t stream_get_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ void *value);
+static pj_status_t stream_set_cap(pjmedia_aud_stream *strm,
+ pjmedia_aud_dev_cap cap,
+ const void *value);
+static pj_status_t stream_start(pjmedia_aud_stream *strm);
+static pj_status_t stream_stop(pjmedia_aud_stream *strm);
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm);
+
+
+/* Operations */
+static pjmedia_aud_dev_factory_op factory_op =
+{
+ &factory_init,
+ &factory_destroy,
+ &factory_get_dev_count,
+ &factory_get_dev_info,
+ &factory_default_param,
+ &factory_create_stream,
+ &factory_refresh
+};
+
+static pjmedia_aud_stream_op stream_op =
+{
+ &stream_get_param,
+ &stream_get_cap,
+ &stream_set_cap,
+ &stream_start,
+ &stream_stop,
+ &stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init WMME audio driver.
+ */
+pjmedia_aud_dev_factory* pjmedia_wmme_factory(pj_pool_factory *pf)
+{
+ struct wmme_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "WMME base", 1000, 1000, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct wmme_factory);
+ f->pf = pf;
+ f->base_pool = pool;
+ f->base.op = &factory_op;
+
+ return &f->base;
+}
+
+/* Internal: Windows Vista and Windows 7 have their device
+ * names truncated when using the waveXXX api. The names
+ * should be acquired from the MMDevice APIs
+ */
+#if PJMEDIA_WMME_DEV_USE_MMDEVICE_API != 0
+
+#define COBJMACROS
+#include <mmdeviceapi.h>
+#define INITGUID
+#include <Guiddef.h>
+#include <FunctionDiscoveryKeys_devpkey.h>
+
+DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xBCDE0395, 0xE52F, 0x467C,
+ 0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E);
+DEFINE_GUID(IID_IMMDeviceEnumerator, 0xA95664D2, 0x9614, 0x4F35,
+ 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6);
+
+static void get_dev_names(pjmedia_aud_dev_factory *f)
+{
+ struct wmme_factory *wf = (struct wmme_factory*)f;
+ HRESULT coinit = S_OK;
+ HRESULT hr = S_OK;
+ IMMDeviceEnumerator *pEnumerator = NULL;
+ IMMDeviceCollection *pDevices = NULL;
+ UINT cDevices = 0;
+ UINT nDevice = 0;
+
+ coinit = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+ if (coinit == RPC_E_CHANGED_MODE)
+ coinit = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
+ if (FAILED(coinit))
+ goto on_error;
+
+ hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL,
+ CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator,
+ (void**)&pEnumerator);
+ if (FAILED(hr))
+ goto on_error;
+ hr = IMMDeviceEnumerator_EnumAudioEndpoints(pEnumerator, eAll,
+ DEVICE_STATE_ACTIVE,
+ &pDevices);
+ if (FAILED(hr))
+ goto on_error;
+ hr = IMMDeviceCollection_GetCount(pDevices, &cDevices);
+ if (FAILED(hr))
+ goto on_error;
+
+ for (nDevice = 0; nDevice < cDevices; ++nDevice) {
+ IMMDevice *pDevice = NULL;
+ IPropertyStore *pProps = NULL;
+ LPWSTR pwszID = NULL;
+ PROPVARIANT varName;
+ unsigned i;
+
+ PropVariantInit(&varName);
+
+ hr = IMMDeviceCollection_Item(pDevices, nDevice, &pDevice);
+ if (FAILED(hr))
+ goto cleanup;
+ hr = IMMDevice_GetId(pDevice, &pwszID);
+ if (FAILED(hr))
+ goto cleanup;
+ hr = IMMDevice_OpenPropertyStore(pDevice, STGM_READ, &pProps);
+ if (FAILED(hr))
+ goto cleanup;
+ hr = IPropertyStore_GetValue(pProps, &PKEY_Device_FriendlyName,
+ &varName);
+ if (FAILED(hr))
+ goto cleanup;
+
+ for (i = 0; i < wf->dev_count; ++i) {
+ if (0 == wcscmp(wf->dev_info[i].endpointId, pwszID)) {
+ wcstombs(wf->dev_info[i].info.name, varName.pwszVal,
+ sizeof(wf->dev_info[i].info.name));
+ break;
+ }
+ }
+
+ PropVariantClear(&varName);
+
+ cleanup:
+ if (pProps)
+ IPropertyStore_Release(pProps);
+ if (pwszID)
+ CoTaskMemFree(pwszID);
+ if (pDevice)
+ hr = IMMDevice_Release(pDevice);
+ }
+
+on_error:
+ if (pDevices)
+ hr = IMMDeviceCollection_Release(pDevices);
+
+ if (pEnumerator)
+ hr = IMMDeviceEnumerator_Release(pEnumerator);
+
+ if (SUCCEEDED(coinit))
+ CoUninitialize();
+}
+
+#else
+
+static void get_dev_names(pjmedia_aud_dev_factory *f)
+{
+ PJ_UNUSED_ARG(f);
+}
+
+#endif
+
+/* Internal: build device info from WAVEINCAPS/WAVEOUTCAPS */
+static void build_dev_info(UINT deviceId, struct wmme_dev_info *wdi,
+ const WAVEINCAPS *wic, const WAVEOUTCAPS *woc)
+{
+#define WIC_WOC(wic,woc,field) (wic? wic->field : woc->field)
+
+ pj_bzero(wdi, sizeof(*wdi));
+ wdi->deviceId = deviceId;
+
+ /* Device Name */
+ if (deviceId==WAVE_MAPPER) {
+ strncpy(wdi->info.name, "Wave mapper", sizeof(wdi->info.name));
+ wdi->info.name[sizeof(wdi->info.name)-1] = '\0';
+ } else {
+ const pj_char_t *szPname = WIC_WOC(wic, woc, szPname);
+ PJ_DECL_ANSI_TEMP_BUF(wTmp, sizeof(wdi->info.name));
+
+ strncpy(wdi->info.name,
+ PJ_NATIVE_TO_STRING(szPname, wTmp, PJ_ARRAY_SIZE(wTmp)),
+ sizeof(wdi->info.name));
+ wdi->info.name[sizeof(wdi->info.name)-1] = '\0';
+ }
+
+ wdi->info.default_samples_per_sec = 16000;
+ strcpy(wdi->info.driver, "WMME");
+
+ if (wic) {
+ wdi->info.input_count = wic->wChannels;
+ wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+
+ /* Sometimes a device can return a rediculously large number of
+ * channels. This happened with an SBLive card on a Windows ME box.
+ * It also happens on Win XP!
+ */
+ if (wdi->info.input_count<1 || wdi->info.input_count>256) {
+ wdi->info.input_count = 2;
+ }
+ }
+
+ if (woc) {
+ wdi->info.output_count = woc->wChannels;
+ wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+
+ if (woc->dwSupport & WAVECAPS_VOLUME) {
+ wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+ }
+
+ /* Sometimes a device can return a rediculously large number of
+ * channels. This happened with an SBLive card on a Windows ME box.
+ * It also happens on Win XP!
+ */
+ if (wdi->info.output_count<1 || wdi->info.output_count>256) {
+ wdi->info.output_count = 2;
+ }
+ }
+
+ /* Extended formats */
+ wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
+ wdi->info.ext_fmt_cnt = 2;
+ pjmedia_format_init_audio(&wdi->info.ext_fmt[0],
+ PJMEDIA_FORMAT_PCMU, 8000, 1, 8,
+ 20000, 64000, 64000);
+ pjmedia_format_init_audio(&wdi->info.ext_fmt[0],
+ PJMEDIA_FORMAT_PCMA, 8000, 1, 8,
+ 20000, 64000, 64000);
+}
+
+/* API: init factory */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
+{
+ pj_status_t ret = factory_refresh(f);
+ if (ret != PJ_SUCCESS)
+ return ret;
+
+ PJ_LOG(4, (THIS_FILE, "WMME initialized"));
+ return PJ_SUCCESS;
+}
+
+/* API: refresh the device list */
+static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f)
+{
+ struct wmme_factory *wf = (struct wmme_factory*)f;
+ unsigned c;
+ int i;
+ int inputDeviceCount, outputDeviceCount, devCount=0;
+ pj_bool_t waveMapperAdded = PJ_FALSE;
+
+ if (wf->pool != NULL) {
+ pj_pool_release(wf->pool);
+ wf->pool = NULL;
+ }
+
+ /* Enumerate sound devices */
+ wf->dev_count = 0;
+ wf->pool = pj_pool_create(wf->pf, "WMME", 1000, 1000, NULL);
+
+ inputDeviceCount = waveInGetNumDevs();
+ devCount += inputDeviceCount;
+
+ outputDeviceCount = waveOutGetNumDevs();
+ devCount += outputDeviceCount;
+
+ if (devCount) {
+ /* Assume there is WAVE_MAPPER */
+ devCount += 2;
+ }
+
+ if (devCount==0) {
+ PJ_LOG(4,(THIS_FILE, "WMME found no sound devices"));
+ /* Enabling this will cause pjsua-lib initialization to fail when there
+ * is no sound device installed in the system, even when pjsua has been
+ * run with --null-audio. Moreover, it might be better to think that
+ * the WMME backend initialization is successfull, regardless there is
+ * no audio device installed, as later application can check it using
+ * get_dev_count().
+ return PJMEDIA_EAUD_NODEV;
+ */
+ return PJ_SUCCESS;
+ }
+
+ wf->dev_info = (struct wmme_dev_info*)
+ pj_pool_calloc(wf->pool, devCount,
+ sizeof(struct wmme_dev_info));
+
+ if (inputDeviceCount && outputDeviceCount) {
+ /* Attempt to add WAVE_MAPPER as input and output device */
+ WAVEINCAPS wic;
+ MMRESULT mr;
+
+ pj_bzero(&wic, sizeof(WAVEINCAPS));
+ mr = waveInGetDevCaps(WAVE_MAPPER, &wic, sizeof(WAVEINCAPS));
+
+ if (mr == MMSYSERR_NOERROR) {
+ WAVEOUTCAPS woc;
+
+ pj_bzero(&woc, sizeof(WAVEOUTCAPS));
+ mr = waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(WAVEOUTCAPS));
+ if (mr == MMSYSERR_NOERROR) {
+ build_dev_info(WAVE_MAPPER, &wf->dev_info[wf->dev_count],
+ &wic, &woc);
+ wf->dev_info[wf->dev_count].endpointId = L"";
+ ++wf->dev_count;
+ waveMapperAdded = PJ_TRUE;
+ }
+ }
+
+ }
+
+ if (inputDeviceCount > 0) {
+ /* -1 is the WAVE_MAPPER */
+ for (i = (waveMapperAdded? 0 : -1); i < inputDeviceCount; ++i) {
+ UINT uDeviceID = (UINT)((i==-1) ? WAVE_MAPPER : i);
+ WAVEINCAPS wic;
+ MMRESULT mr;
+ DWORD cbEndpointId;
+
+ pj_bzero(&wic, sizeof(WAVEINCAPS));
+
+ mr = waveInGetDevCaps(uDeviceID, &wic, sizeof(WAVEINCAPS));
+
+ if (mr == MMSYSERR_NOMEM)
+ return PJ_ENOMEM;
+
+ if (mr != MMSYSERR_NOERROR)
+ continue;
+
+ build_dev_info(uDeviceID, &wf->dev_info[wf->dev_count],
+ &wic, NULL);
+
+#if PJMEDIA_WMME_DEV_USE_MMDEVICE_API != 0
+ /* Try to get the endpoint id of the audio device */
+ wf->dev_info[wf->dev_count].endpointId = L"";
+
+ mr = waveInMessage((HWAVEIN)IntToPtr(uDeviceID),
+ DRV_QUERYFUNCTIONINSTANCEIDSIZE,
+ (DWORD_PTR)&cbEndpointId, (DWORD_PTR)NULL);
+ if (mr == MMSYSERR_NOERROR) {
+ const wchar_t **epid = &wf->dev_info[wf->dev_count].endpointId;
+ *epid = (const wchar_t*) pj_pool_calloc(wf->pool,
+ cbEndpointId, 1);
+ mr = waveInMessage((HWAVEIN)IntToPtr(uDeviceID),
+ DRV_QUERYFUNCTIONINSTANCEID,
+ (DWORD_PTR)*epid,
+ cbEndpointId);
+ }
+#else
+ PJ_UNUSED_ARG(cbEndpointId);
+#endif
+
+ ++wf->dev_count;
+ }
+ }
+
+ if( outputDeviceCount > 0 )
+ {
+ /* -1 is the WAVE_MAPPER */
+ for (i = (waveMapperAdded? 0 : -1); i < outputDeviceCount; ++i) {
+ UINT uDeviceID = (UINT)((i==-1) ? WAVE_MAPPER : i);
+ WAVEOUTCAPS woc;
+ MMRESULT mr;
+ DWORD cbEndpointId;
+
+ pj_bzero(&woc, sizeof(WAVEOUTCAPS));
+
+ mr = waveOutGetDevCaps(uDeviceID, &woc, sizeof(WAVEOUTCAPS));
+
+ if (mr == MMSYSERR_NOMEM)
+ return PJ_ENOMEM;
+
+ if (mr != MMSYSERR_NOERROR)
+ continue;
+
+ build_dev_info(uDeviceID, &wf->dev_info[wf->dev_count],
+ NULL, &woc);
+
+#if PJMEDIA_WMME_DEV_USE_MMDEVICE_API != 0
+ /* Try to get the endpoint id of the audio device */
+ wf->dev_info[wf->dev_count].endpointId = L"";
+
+ mr = waveOutMessage((HWAVEOUT)IntToPtr(uDeviceID),
+ DRV_QUERYFUNCTIONINSTANCEIDSIZE,
+ (DWORD_PTR)&cbEndpointId, (DWORD_PTR)NULL);
+ if (mr == MMSYSERR_NOERROR) {
+ const wchar_t **epid = &wf->dev_info[wf->dev_count].endpointId;
+ *epid = (const wchar_t*)pj_pool_calloc(wf->pool,
+ cbEndpointId, 1);
+ mr = waveOutMessage((HWAVEOUT)IntToPtr(uDeviceID),
+ DRV_QUERYFUNCTIONINSTANCEID,
+ (DWORD_PTR)*epid, cbEndpointId);
+ }
+#else
+ PJ_UNUSED_ARG(cbEndpointId);
+#endif
+
+ ++wf->dev_count;
+ }
+ }
+
+ /* On Windows Vista and Windows 7 get the full device names */
+ get_dev_names(f);
+
+ PJ_LOG(4, (THIS_FILE, "WMME found %d devices:",
+ wf->dev_count));
+ for (c = 0; c < wf->dev_count; ++c) {
+ PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (in=%d, out=%d)",
+ c,
+ wf->dev_info[c].info.name,
+ wf->dev_info[c].info.input_count,
+ wf->dev_info[c].info.output_count));
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
+{
+ struct wmme_factory *wf = (struct wmme_factory*)f;
+ pj_pool_t *pool = wf->base_pool;
+
+ pj_pool_release(wf->pool);
+ wf->base_pool = NULL;
+ pj_pool_release(pool);
+
+ return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+ struct wmme_factory *wf = (struct wmme_factory*)f;
+ return wf->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info)
+{
+ struct wmme_factory *wf = (struct wmme_factory*)f;
+
+ PJ_ASSERT_RETURN(index < wf->dev_count, PJMEDIA_EAUD_INVDEV);
+
+ pj_memcpy(info, &wf->dev_info[index].info, sizeof(*info));
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param)
+{
+ struct wmme_factory *wf = (struct wmme_factory*)f;
+ struct wmme_dev_info *di = &wf->dev_info[index];
+
+ PJ_ASSERT_RETURN(index < wf->dev_count, PJMEDIA_EAUD_INVDEV);
+
+ pj_bzero(param, sizeof(*param));
+ if (di->info.input_count && di->info.output_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param->rec_id = index;
+ param->play_id = index;
+ } else if (di->info.input_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE;
+ param->rec_id = index;
+ param->play_id = PJMEDIA_AUD_INVALID_DEV;
+ } else if (di->info.output_count) {
+ param->dir = PJMEDIA_DIR_PLAYBACK;
+ param->play_id = index;
+ param->rec_id = PJMEDIA_AUD_INVALID_DEV;
+ } else {
+ return PJMEDIA_EAUD_INVDEV;
+ }
+
+ param->clock_rate = di->info.default_samples_per_sec;
+ param->channel_count = 1;
+ param->samples_per_frame = di->info.default_samples_per_sec * 20 / 1000;
+ param->bits_per_sample = 16;
+ param->flags = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+ param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+ param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+
+ return PJ_SUCCESS;
+}
+
+/* Internal: init WAVEFORMATEX */
+static pj_status_t init_waveformatex(LPWAVEFORMATEX wfx,
+ const pjmedia_aud_param *prm)
+{
+
+ pj_bzero(wfx, sizeof(WAVEFORMATEX));
+ if (prm->ext_fmt.id == PJMEDIA_FORMAT_L16) {
+ enum { BYTES_PER_SAMPLE = 2 };
+ wfx->wFormatTag = WAVE_FORMAT_PCM;
+ wfx->nChannels = (pj_uint16_t)prm->channel_count;
+ wfx->nSamplesPerSec = prm->clock_rate;
+ wfx->nBlockAlign = (pj_uint16_t)(prm->channel_count *
+ BYTES_PER_SAMPLE);
+ wfx->nAvgBytesPerSec = prm->clock_rate * prm->channel_count *
+ BYTES_PER_SAMPLE;
+ wfx->wBitsPerSample = 16;
+
+ return PJ_SUCCESS;
+
+ } else if ((prm->flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) &&
+ (prm->ext_fmt.id == PJMEDIA_FORMAT_PCMA ||
+ prm->ext_fmt.id == PJMEDIA_FORMAT_PCMU))
+ {
+ unsigned ptime;
+
+ ptime = prm->samples_per_frame * 1000 /
+ (prm->clock_rate * prm->channel_count);
+ wfx->wFormatTag = (pj_uint16_t)
+ ((prm->ext_fmt.id==PJMEDIA_FORMAT_PCMA) ?
+ WAVE_FORMAT_ALAW : WAVE_FORMAT_MULAW);
+ wfx->nChannels = (pj_uint16_t)prm->channel_count;
+ wfx->nSamplesPerSec = prm->clock_rate;
+ wfx->nAvgBytesPerSec = prm->clock_rate * prm->channel_count;
+ wfx->nBlockAlign = (pj_uint16_t)(wfx->nAvgBytesPerSec * ptime /
+ 1000);
+ wfx->wBitsPerSample = 8;
+ wfx->cbSize = 0;
+
+ return PJ_SUCCESS;
+
+ } else {
+
+ return PJMEDIA_EAUD_BADFORMAT;
+
+ }
+}
+
+/* Get format name */
+static const char *get_fmt_name(pj_uint32_t id)
+{
+ static char name[8];
+
+ if (id == PJMEDIA_FORMAT_L16)
+ return "PCM";
+ pj_memcpy(name, &id, 4);
+ name[4] = '\0';
+ return name;
+}
+
+/* Internal: create WMME player device. */
+static pj_status_t init_player_stream( struct wmme_factory *wf,
+ pj_pool_t *pool,
+ struct wmme_stream *parent,
+ struct wmme_channel *wmme_strm,
+ const pjmedia_aud_param *prm,
+ unsigned buffer_count)
+{
+ MMRESULT mr;
+ WAVEFORMATEX wfx;
+ unsigned i, ptime;
+ DWORD flag;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(prm->play_id < (int)wf->dev_count, PJ_EINVAL);
+
+ /*
+ * Create a wait event.
+ */
+ wmme_strm->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (NULL == wmme_strm->hEvent)
+ return pj_get_os_error();
+
+ /*
+ * Set up wave format structure for opening the device.
+ */
+ status = init_waveformatex(&wfx, prm);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ ptime = prm->samples_per_frame * 1000 /
+ (prm->clock_rate * prm->channel_count);
+ parent->bytes_per_frame = wfx.nAvgBytesPerSec * ptime / 1000;
+
+ flag = CALLBACK_EVENT;
+ if (prm->ext_fmt.id == PJMEDIA_FORMAT_L16)
+ flag |= WAVE_FORMAT_DIRECT;
+
+ /*
+ * Open wave device.
+ */
+ mr = waveOutOpen(&wmme_strm->hWave.Out,
+ wf->dev_info[prm->play_id].deviceId,
+ &wfx, (DWORD)wmme_strm->hEvent, 0, flag);
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+ }
+
+ /* Pause the wave out device */
+ mr = waveOutPause(wmme_strm->hWave.Out);
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+ }
+
+ /*
+ * Create the buffers.
+ */
+ wmme_strm->WaveHdr = (WAVEHDR*)
+ pj_pool_zalloc(pool, sizeof(WAVEHDR) * buffer_count);
+ for (i = 0; i < buffer_count; ++i) {
+ wmme_strm->WaveHdr[i].lpData = pj_pool_zalloc(pool,
+ parent->bytes_per_frame);
+ wmme_strm->WaveHdr[i].dwBufferLength = parent->bytes_per_frame;
+ mr = waveOutPrepareHeader(wmme_strm->hWave.Out,
+ &(wmme_strm->WaveHdr[i]),
+ sizeof(WAVEHDR));
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+ }
+ mr = waveOutWrite(wmme_strm->hWave.Out, &(wmme_strm->WaveHdr[i]),
+ sizeof(WAVEHDR));
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+ }
+ }
+
+ wmme_strm->dwBufIdx = 0;
+ wmme_strm->dwMaxBufIdx = buffer_count;
+ wmme_strm->timestamp.u64 = 0;
+
+ /* Done setting up play device. */
+ PJ_LOG(4, (THIS_FILE,
+ " WaveAPI Sound player \"%s\" initialized ("
+ "format=%s, clock_rate=%d, "
+ "channel_count=%d, samples_per_frame=%d (%dms))",
+ wf->dev_info[prm->play_id].info.name,
+ get_fmt_name(prm->ext_fmt.id),
+ prm->clock_rate, prm->channel_count, prm->samples_per_frame,
+ prm->samples_per_frame * 1000 / prm->clock_rate));
+
+ return PJ_SUCCESS;
+}
+
+
+/* Internal: create Windows Multimedia recorder device */
+static pj_status_t init_capture_stream( struct wmme_factory *wf,
+ pj_pool_t *pool,
+ struct wmme_stream *parent,
+ struct wmme_channel *wmme_strm,
+ const pjmedia_aud_param *prm,
+ unsigned buffer_count)
+{
+ MMRESULT mr;
+ WAVEFORMATEX wfx;
+ DWORD flag;
+ unsigned i, ptime;
+
+ PJ_ASSERT_RETURN(prm->rec_id < (int)wf->dev_count, PJ_EINVAL);
+
+ /*
+ * Create a wait event.
+ */
+ wmme_strm->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (NULL == wmme_strm->hEvent) {
+ return pj_get_os_error();
+ }
+
+ /*
+ * Set up wave format structure for opening the device.
+ */
+ init_waveformatex(&wfx, prm);
+ ptime = prm->samples_per_frame * 1000 /
+ (prm->clock_rate * prm->channel_count);
+ parent->bytes_per_frame = wfx.nAvgBytesPerSec * ptime / 1000;
+
+ flag = CALLBACK_EVENT;
+ if (prm->ext_fmt.id == PJMEDIA_FORMAT_L16)
+ flag |= WAVE_FORMAT_DIRECT;
+
+ /*
+ * Open wave device.
+ */
+ mr = waveInOpen(&wmme_strm->hWave.In,
+ wf->dev_info[prm->rec_id].deviceId,
+ &wfx, (DWORD)wmme_strm->hEvent, 0, flag);
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
+ }
+
+ /*
+ * Create the buffers.
+ */
+ wmme_strm->WaveHdr = (WAVEHDR*)
+ pj_pool_zalloc(pool, sizeof(WAVEHDR) * buffer_count);
+ for (i = 0; i < buffer_count; ++i) {
+ wmme_strm->WaveHdr[i].lpData = pj_pool_zalloc(pool,
+ parent->bytes_per_frame);
+ wmme_strm->WaveHdr[i].dwBufferLength = parent->bytes_per_frame;
+ mr = waveInPrepareHeader(wmme_strm->hWave.In,
+ &(wmme_strm->WaveHdr[i]),
+ sizeof(WAVEHDR));
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
+ }
+ mr = waveInAddBuffer(wmme_strm->hWave.In, &(wmme_strm->WaveHdr[i]),
+ sizeof(WAVEHDR));
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
+ }
+ }
+
+ wmme_strm->dwBufIdx = 0;
+ wmme_strm->dwMaxBufIdx = buffer_count;
+ wmme_strm->timestamp.u64 = 0;
+
+ /* Done setting up play device. */
+ PJ_LOG(4,(THIS_FILE,
+ " WaveAPI Sound recorder \"%s\" initialized "
+ "(format=%s, clock_rate=%d, "
+ "channel_count=%d, samples_per_frame=%d (%dms))",
+ wf->dev_info[prm->rec_id].info.name,
+ get_fmt_name(prm->ext_fmt.id),
+ prm->clock_rate, prm->channel_count, prm->samples_per_frame,
+ prm->samples_per_frame * 1000 / prm->clock_rate));
+
+ return PJ_SUCCESS;
+}
+
+
+/* WMME capture and playback thread. */
+static int PJ_THREAD_FUNC wmme_dev_thread(void *arg)
+{
+ struct wmme_stream *strm = (struct wmme_stream*)arg;
+ HANDLE events[3];
+ unsigned eventCount;
+ pj_status_t status = PJ_SUCCESS;
+ static unsigned rec_cnt, play_cnt;
+ enum { MAX_BURST = 1000 };
+
+ rec_cnt = play_cnt = 0;
+
+ eventCount = 0;
+ events[eventCount++] = strm->thread_quit_event;
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK)
+ events[eventCount++] = strm->play_strm.hEvent;
+ if (strm->param.dir & PJMEDIA_DIR_CAPTURE)
+ events[eventCount++] = strm->rec_strm.hEvent;
+
+
+ /* Raise self priority. We don't want the audio to be distorted by
+ * system activity.
+ */
+#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0
+ if (strm->param.dir & PJMEDIA_DIR_PLAYBACK)
+ CeSetThreadPriority(GetCurrentThread(), 153);
+ else
+ CeSetThreadPriority(GetCurrentThread(), 247);
+#else
+ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
+#endif
+
+ /*
+ * Loop while not signalled to quit, wait for event objects to be
+ * signalled by WMME capture and play buffer.
+ */
+ while (status == PJ_SUCCESS)
+ {
+
+ DWORD rc;
+ pjmedia_dir signalled_dir;
+
+ /* Swap hWaveIn and hWaveOut to get equal opportunity for both */
+ if (eventCount==3) {
+ HANDLE hTemp = events[2];
+ events[2] = events[1];
+ events[1] = hTemp;
+ }
+
+ rc = WaitForMultipleObjects(eventCount, events, FALSE, INFINITE);
+ if (rc < WAIT_OBJECT_0 || rc >= WAIT_OBJECT_0 + eventCount)
+ continue;
+
+ if (rc == WAIT_OBJECT_0)
+ break;
+
+ if (rc == (WAIT_OBJECT_0 + 1))
+ {
+ if (events[1] == strm->play_strm.hEvent)
+ signalled_dir = PJMEDIA_DIR_PLAYBACK;
+ else
+ signalled_dir = PJMEDIA_DIR_CAPTURE;
+ }
+ else
+ {
+ if (events[2] == strm->play_strm.hEvent)
+ signalled_dir = PJMEDIA_DIR_PLAYBACK;
+ else
+ signalled_dir = PJMEDIA_DIR_CAPTURE;
+ }
+
+
+ if (signalled_dir == PJMEDIA_DIR_PLAYBACK)
+ {
+ struct wmme_channel *wmme_strm = &strm->play_strm;
+ unsigned burst;
+
+ status = PJ_SUCCESS;
+
+ /*
+ * Windows Multimedia has requested us to feed some frames to
+ * playback buffer.
+ */
+
+ for (burst=0; burst<MAX_BURST &&
+ (wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwFlags & WHDR_DONE);
+ ++burst)
+ {
+ void *buffer = wmme_strm->WaveHdr[wmme_strm->dwBufIdx].lpData;
+ pjmedia_frame pcm_frame, *frame;
+ MMRESULT mr = MMSYSERR_NOERROR;
+
+ //PJ_LOG(5,(THIS_FILE, "Finished writing buffer %d",
+ // wmme_strm->dwBufIdx));
+
+ if (strm->fmt_id == PJMEDIA_FORMAT_L16) {
+ /* PCM mode */
+ frame = &pcm_frame;
+
+ frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame->size = strm->bytes_per_frame;
+ frame->buf = buffer;
+ frame->timestamp.u64 = wmme_strm->timestamp.u64;
+ frame->bit_info = 0;
+ } else {
+ /* Codec mode */
+ frame = &strm->xfrm->base;
+
+ strm->xfrm->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ strm->xfrm->base.size = strm->bytes_per_frame;
+ strm->xfrm->base.buf = NULL;
+ strm->xfrm->base.timestamp.u64 = wmme_strm->timestamp.u64;
+ strm->xfrm->base.bit_info = 0;
+ }
+
+ /* Get frame from application. */
+ //PJ_LOG(5,(THIS_FILE, "xxx %u play_cb", play_cnt++));
+ status = (*strm->play_cb)(strm->user_data, frame);
+
+ if (status != PJ_SUCCESS)
+ break;
+
+ if (strm->fmt_id == PJMEDIA_FORMAT_L16) {
+ /* PCM mode */
+ if (frame->type == PJMEDIA_FRAME_TYPE_NONE) {
+ pj_bzero(buffer, strm->bytes_per_frame);
+ } else if (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ pj_assert(!"Frame type not supported");
+ } else if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+ /* Nothing to do */
+ } else {
+ pj_assert(!"Frame type not supported");
+ }
+ } else {
+ /* Codec mode */
+ if (frame->type == PJMEDIA_FRAME_TYPE_NONE) {
+ pj_memset(buffer, strm->silence_char,
+ strm->bytes_per_frame);
+ } else if (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ unsigned sz;
+ sz = pjmedia_frame_ext_copy_payload(strm->xfrm,
+ buffer,
+ strm->bytes_per_frame);
+ if (sz < strm->bytes_per_frame) {
+ pj_memset((char*)buffer+sz,
+ strm->silence_char,
+ strm->bytes_per_frame - sz);
+ }
+ } else {
+ pj_assert(!"Frame type not supported");
+ }
+ }
+
+ /* Write to the device. */
+ mr = waveOutWrite(wmme_strm->hWave.Out,
+ &(wmme_strm->WaveHdr[wmme_strm->dwBufIdx]),
+ sizeof(WAVEHDR));
+ if (mr != MMSYSERR_NOERROR) {
+ status = PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+ break;
+ }
+
+ /* Increment position. */
+ if (++wmme_strm->dwBufIdx >= wmme_strm->dwMaxBufIdx)
+ wmme_strm->dwBufIdx = 0;
+ wmme_strm->timestamp.u64 += strm->param.samples_per_frame /
+ strm->param.channel_count;
+ } /* for */
+ }
+ else
+ {
+ struct wmme_channel *wmme_strm = &strm->rec_strm;
+ unsigned burst;
+ MMRESULT mr = MMSYSERR_NOERROR;
+ status = PJ_SUCCESS;
+
+ /*
+ * Windows Multimedia has indicated that it has some frames ready
+ * in the capture buffer. Get as much frames as possible to
+ * prevent overflows.
+ */
+#if 0
+ {
+ static DWORD tc = 0;
+ DWORD now = GetTickCount();
+ DWORD i = 0;
+ DWORD bits = 0;
+
+ if (tc == 0) tc = now;
+
+ for (i = 0; i < wmme_strm->dwMaxBufIdx; ++i)
+ {
+ bits = bits << 4;
+ bits |= wmme_strm->WaveHdr[i].dwFlags & WHDR_DONE;
+ }
+ PJ_LOG(5,(THIS_FILE, "Record Signal> Index: %d, Delta: %4.4d, "
+ "Flags: %6.6x\n",
+ wmme_strm->dwBufIdx,
+ now - tc,
+ bits));
+ tc = now;
+ }
+#endif
+
+ for (burst=0; burst<MAX_BURST &&
+ (wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwFlags & WHDR_DONE);
+ ++burst)
+ {
+ char* buffer = (char*)
+ wmme_strm->WaveHdr[wmme_strm->dwBufIdx].lpData;
+ unsigned cap_len =
+ wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwBytesRecorded;
+ pjmedia_frame pcm_frame, *frame;
+
+ /*
+ PJ_LOG(5,(THIS_FILE, "Read %d bytes from buffer %d", cap_len,
+ wmme_strm->dwBufIdx));
+ */
+
+ if (strm->fmt_id == PJMEDIA_FORMAT_L16) {
+ /* PCM mode */
+ if (cap_len < strm->bytes_per_frame)
+ pj_bzero(buffer + cap_len,
+ strm->bytes_per_frame - cap_len);
+
+ /* Copy the audio data out of the wave buffer. */
+ pj_memcpy(strm->buffer, buffer, strm->bytes_per_frame);
+
+ /* Prepare frame */
+ frame = &pcm_frame;
+ frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame->buf = strm->buffer;
+ frame->size = strm->bytes_per_frame;
+ frame->timestamp.u64 = wmme_strm->timestamp.u64;
+ frame->bit_info = 0;
+
+ } else {
+ /* Codec mode */
+ frame = &strm->xfrm->base;
+
+ frame->type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ frame->buf = NULL;
+ frame->size = strm->bytes_per_frame;
+ frame->timestamp.u64 = wmme_strm->timestamp.u64;
+ frame->bit_info = 0;
+
+ strm->xfrm->samples_cnt = 0;
+ strm->xfrm->subframe_cnt = 0;
+ pjmedia_frame_ext_append_subframe(
+ strm->xfrm, buffer,
+ strm->bytes_per_frame *8,
+ strm->param.samples_per_frame
+ );
+ }
+
+ /* Re-add the buffer to the device. */
+ mr = waveInAddBuffer(wmme_strm->hWave.In,
+ &(wmme_strm->WaveHdr[wmme_strm->dwBufIdx]),
+ sizeof(WAVEHDR));
+ if (mr != MMSYSERR_NOERROR) {
+ status = PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
+ break;
+ }
+
+
+ /* Call callback */
+ //PJ_LOG(5,(THIS_FILE, "xxx %u rec_cb", rec_cnt++));
+ status = (*strm->rec_cb)(strm->user_data, frame);
+ if (status != PJ_SUCCESS)
+ break;
+
+ /* Increment position. */
+ if (++wmme_strm->dwBufIdx >= wmme_strm->dwMaxBufIdx)
+ wmme_strm->dwBufIdx = 0;
+ wmme_strm->timestamp.u64 += strm->param.samples_per_frame /
+ strm->param.channel_count;
+ } /* for */
+ }
+ }
+
+ PJ_LOG(5,(THIS_FILE, "WMME: thread stopping.."));
+ return 0;
+}
+
+
+/* API: create stream */
+static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
+ const pjmedia_aud_param *param,
+ pjmedia_aud_rec_cb rec_cb,
+ pjmedia_aud_play_cb play_cb,
+ void *user_data,
+ pjmedia_aud_stream **p_aud_strm)
+{
+ struct wmme_factory *wf = (struct wmme_factory*)f;
+ pj_pool_t *pool;
+ struct wmme_stream *strm;
+ pj_uint8_t silence_char;
+ pj_status_t status;
+
+ switch (param->ext_fmt.id) {
+ case PJMEDIA_FORMAT_L16:
+ silence_char = '\0';
+ break;
+ case PJMEDIA_FORMAT_ALAW:
+ silence_char = (pj_uint8_t)'\xd5';
+ break;
+ case PJMEDIA_FORMAT_ULAW:
+ silence_char = (pj_uint8_t)'\xff';
+ break;
+ default:
+ return PJMEDIA_EAUD_BADFORMAT;
+ }
+
+ /* Create and Initialize stream descriptor */
+ pool = pj_pool_create(wf->pf, "wmme-dev", 1000, 1000, NULL);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ strm = PJ_POOL_ZALLOC_T(pool, struct wmme_stream);
+ pj_memcpy(&strm->param, param, sizeof(*param));
+ strm->pool = pool;
+ strm->rec_cb = rec_cb;
+ strm->play_cb = play_cb;
+ strm->user_data = user_data;
+ strm->fmt_id = param->ext_fmt.id;
+ strm->silence_char = silence_char;
+
+ /* Create player stream */
+ if (param->dir & PJMEDIA_DIR_PLAYBACK) {
+ unsigned buf_count;
+
+ if ((param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)==0) {
+ strm->param.flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+ strm->param.output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+ }
+
+ buf_count = strm->param.output_latency_ms * param->clock_rate *
+ param->channel_count / param->samples_per_frame / 1000;
+
+ status = init_player_stream(wf, strm->pool,
+ strm,
+ &strm->play_strm,
+ param,
+ buf_count);
+
+ if (status != PJ_SUCCESS) {
+ stream_destroy(&strm->base);
+ return status;
+ }
+ }
+
+ /* Create capture stream */
+ if (param->dir & PJMEDIA_DIR_CAPTURE) {
+ unsigned buf_count;
+
+ if ((param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)==0) {
+ strm->param.flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+ strm->param.input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+ }
+
+ buf_count = strm->param.input_latency_ms * param->clock_rate *
+ param->channel_count / param->samples_per_frame / 1000;
+
+ status = init_capture_stream(wf, strm->pool,
+ strm,
+ &strm->rec_strm,
+ param,
+ buf_count);
+
+ if (status != PJ_SUCCESS) {
+ stream_destroy(&strm->base);
+ return status;
+ }
+ }
+
+ strm->buffer = pj_pool_alloc(pool, strm->bytes_per_frame);
+ if (!strm->buffer) {
+ pj_pool_release(pool);
+ return PJ_ENOMEM;
+ }
+
+ /* If format is extended, must create buffer for the extended frame. */
+ if (strm->fmt_id != PJMEDIA_FORMAT_L16) {
+ strm->xfrm_size = sizeof(pjmedia_frame_ext) +
+ 32 * sizeof(pjmedia_frame_ext_subframe) +
+ strm->bytes_per_frame + 4;
+ strm->xfrm = (pjmedia_frame_ext*)
+ pj_pool_alloc(pool, strm->xfrm_size);
+ }
+
+ /* Create the stop event */
+ strm->thread_quit_event = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (strm->thread_quit_event == NULL) {
+ status = pj_get_os_error();
+ stream_destroy(&strm->base);
+ return status;
+ }
+
+ /* Create and start the thread */
+ status = pj_thread_create(pool, "wmme", &wmme_dev_thread, strm, 0, 0,
+ &strm->thread);
+ if (status != PJ_SUCCESS) {
+ stream_destroy(&strm->base);
+ return status;
+ }
+
+ /* Apply the remaining settings */
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
+ stream_set_cap(&strm->base, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &param->output_vol);
+ }
+
+
+ /* Done */
+ strm->base.op = &stream_op;
+ *p_aud_strm = &strm->base;
+
+ return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t stream_get_param(pjmedia_aud_stream *s,
+ pjmedia_aud_param *pi)
+{
+ struct wmme_stream *strm = (struct wmme_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+ /* Update the volume setting */
+ if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+ &pi->output_vol) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ void *pval)
+{
+ struct wmme_stream *strm = (struct wmme_stream*)s;
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY &&
+ (strm->param.dir & PJMEDIA_DIR_CAPTURE))
+ {
+ /* Recording latency */
+ *(unsigned*)pval = strm->param.input_latency_ms;
+ return PJ_SUCCESS;
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY &&
+ (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
+ {
+ /* Playback latency */
+ *(unsigned*)pval = strm->param.output_latency_ms;
+ return PJ_SUCCESS;
+ } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
+ strm->play_strm.hWave.Out)
+ {
+ /* Output volume setting */
+ DWORD waveVol;
+ MMRESULT mr;
+
+ mr = waveOutGetVolume(strm->play_strm.hWave.Out, &waveVol);
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+ }
+
+ waveVol &= 0xFFFF;
+ *(unsigned*)pval = (waveVol * 100) / 0xFFFF;
+ return PJ_SUCCESS;
+ } else {
+ return PJMEDIA_EAUD_INVCAP;
+ }
+}
+
+/* API: set capability */
+static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
+ pjmedia_aud_dev_cap cap,
+ const void *pval)
+{
+ struct wmme_stream *strm = (struct wmme_stream*)s;
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
+ strm->play_strm.hWave.Out)
+ {
+ /* Output volume setting */
+ unsigned vol = *(unsigned*)pval;
+ DWORD waveVol;
+ MMRESULT mr;
+ pj_status_t status;
+
+ if (vol > 100)
+ vol = 100;
+
+ waveVol = (vol * 0xFFFF) / 100;
+ waveVol |= (waveVol << 16);
+
+ mr = waveOutSetVolume(strm->play_strm.hWave.Out, waveVol);
+ status = (mr==MMSYSERR_NOERROR)? PJ_SUCCESS :
+ PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+ if (status == PJ_SUCCESS) {
+ strm->param.output_vol = *(unsigned*)pval;
+ }
+ return status;
+ }
+
+ return PJMEDIA_EAUD_INVCAP;
+}
+
+/* API: Start stream. */
+static pj_status_t stream_start(pjmedia_aud_stream *strm)
+{
+ struct wmme_stream *stream = (struct wmme_stream*)strm;
+ MMRESULT mr;
+
+ if (stream->play_strm.hWave.Out != NULL)
+ {
+ mr = waveOutRestart(stream->play_strm.hWave.Out);
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+ }
+ PJ_LOG(4,(THIS_FILE, "WMME playback stream started"));
+ }
+
+ if (stream->rec_strm.hWave.In != NULL)
+ {
+ mr = waveInStart(stream->rec_strm.hWave.In);
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
+ }
+ PJ_LOG(4,(THIS_FILE, "WMME capture stream started"));
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t stream_stop(pjmedia_aud_stream *strm)
+{
+ struct wmme_stream *stream = (struct wmme_stream*)strm;
+ MMRESULT mr;
+
+ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+ if (stream->play_strm.hWave.Out != NULL)
+ {
+ mr = waveOutPause(stream->play_strm.hWave.Out);
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr);
+ }
+ PJ_LOG(4,(THIS_FILE, "Stopped WMME playback stream"));
+ }
+
+ if (stream->rec_strm.hWave.In != NULL)
+ {
+ mr = waveInStop(stream->rec_strm.hWave.In);
+ if (mr != MMSYSERR_NOERROR) {
+ return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr);
+ }
+ PJ_LOG(4,(THIS_FILE, "Stopped WMME capture stream"));
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t stream_destroy(pjmedia_aud_stream *strm)
+{
+ struct wmme_stream *stream = (struct wmme_stream*)strm;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+ stream_stop(strm);
+
+ /* Stop the stream thread */
+ if (stream->thread)
+ {
+ SetEvent(stream->thread_quit_event);
+ pj_thread_join(stream->thread);
+ pj_thread_destroy(stream->thread);
+ stream->thread = NULL;
+ }
+
+ /* Close the thread quit event */
+ if (stream->thread_quit_event)
+ {
+ CloseHandle(stream->thread_quit_event);
+ stream->thread_quit_event = NULL;
+ }
+
+ /* Unprepare the headers and close the play device */
+ if (stream->play_strm.hWave.Out)
+ {
+ waveOutReset(stream->play_strm.hWave.Out);
+ for (i = 0; i < stream->play_strm.dwMaxBufIdx; ++i)
+ waveOutUnprepareHeader(stream->play_strm.hWave.Out,
+ &(stream->play_strm.WaveHdr[i]),
+ sizeof(WAVEHDR));
+ waveOutClose(stream->play_strm.hWave.Out);
+ stream->play_strm.hWave.Out = NULL;
+ }
+
+ /* Close the play event */
+ if (stream->play_strm.hEvent)
+ {
+ CloseHandle(stream->play_strm.hEvent);
+ stream->play_strm.hEvent = NULL;
+ }
+
+ /* Unprepare the headers and close the record device */
+ if (stream->rec_strm.hWave.In)
+ {
+ waveInReset(stream->rec_strm.hWave.In);
+ for (i = 0; i < stream->play_strm.dwMaxBufIdx; ++i)
+ waveInUnprepareHeader(stream->rec_strm.hWave.In,
+ &(stream->rec_strm.WaveHdr[i]),
+ sizeof(WAVEHDR));
+ waveInClose(stream->rec_strm.hWave.In);
+ stream->rec_strm.hWave.In = NULL;
+ }
+
+ /* Close the record event */
+ if (stream->rec_strm.hEvent)
+ {
+ CloseHandle(stream->rec_strm.hEvent);
+ stream->rec_strm.hEvent = NULL;
+ }
+
+ pj_pool_release(stream->pool);
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_AUDIO_DEV_HAS_WMME */
+
diff --git a/pjmedia/src/pjmedia-codec/amr_sdp_match.c b/pjmedia/src/pjmedia-codec/amr_sdp_match.c
new file mode 100644
index 0000000..44b104b
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/amr_sdp_match.c
@@ -0,0 +1,176 @@
+/* $Id: amr_sdp_match.c 3911 2011-12-15 06:45:23Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia-codec/amr_sdp_match.h>
+#include <pjmedia/errno.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#define GET_FMTP_IVAL_BASE(ival, base, fmtp, param, default_val) \
+ do { \
+ pj_str_t s; \
+ char *p; \
+ p = pj_stristr(&fmtp.fmt_param, &param); \
+ if (!p) { \
+ ival = default_val; \
+ break; \
+ } \
+ pj_strset(&s, p + param.slen, fmtp.fmt_param.slen - \
+ (p - fmtp.fmt_param.ptr) - param.slen); \
+ ival = pj_strtoul2(&s, NULL, base); \
+ } while (0)
+
+#define GET_FMTP_IVAL(ival, fmtp, param, default_val) \
+ GET_FMTP_IVAL_BASE(ival, 10, fmtp, param, default_val)
+
+
+/* Toggle AMR octet-align setting in the fmtp. */
+static pj_status_t amr_toggle_octet_align(pj_pool_t *pool,
+ pjmedia_sdp_media *media,
+ unsigned fmt_idx)
+{
+ pjmedia_sdp_attr *attr;
+ pjmedia_sdp_fmtp fmtp;
+ const pj_str_t STR_OCTET_ALIGN = {"octet-align=", 12};
+
+ enum { MAX_FMTP_STR_LEN = 160 };
+
+ attr = pjmedia_sdp_media_find_attr2(media, "fmtp",
+ &media->desc.fmt[fmt_idx]);
+ /* Check if the AMR media format has FMTP attribute */
+ if (attr) {
+ char *p;
+ pj_status_t status;
+
+ status = pjmedia_sdp_attr_get_fmtp(attr, &fmtp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Check if the fmtp has octet-align field. */
+ p = pj_stristr(&fmtp.fmt_param, &STR_OCTET_ALIGN);
+ if (p) {
+ /* It has, just toggle the value */
+ unsigned octet_align;
+ pj_str_t s;
+
+ pj_strset(&s, p + STR_OCTET_ALIGN.slen, fmtp.fmt_param.slen -
+ (p - fmtp.fmt_param.ptr) - STR_OCTET_ALIGN.slen);
+ octet_align = pj_strtoul(&s);
+ *(p + STR_OCTET_ALIGN.slen) = (char)(octet_align? '0' : '1');
+ } else {
+ /* It doesn't, append octet-align field */
+ char buf[MAX_FMTP_STR_LEN];
+
+ pj_ansi_snprintf(buf, MAX_FMTP_STR_LEN, "%.*s;octet-align=1",
+ (int)fmtp.fmt_param.slen, fmtp.fmt_param.ptr);
+ attr->value = pj_strdup3(pool, buf);
+ }
+ } else {
+ /* Add new attribute for the AMR media format with octet-align
+ * field set.
+ */
+ char buf[MAX_FMTP_STR_LEN];
+
+ attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr);
+ attr->name = pj_str("fmtp");
+ pj_ansi_snprintf(buf, MAX_FMTP_STR_LEN, "%.*s octet-align=1",
+ (int)media->desc.fmt[fmt_idx].slen,
+ media->desc.fmt[fmt_idx].ptr);
+ attr->value = pj_strdup3(pool, buf);
+ media->attr[media->attr_count++] = attr;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_codec_amr_match_sdp( pj_pool_t *pool,
+ pjmedia_sdp_media *offer,
+ unsigned o_fmt_idx,
+ pjmedia_sdp_media *answer,
+ unsigned a_fmt_idx,
+ unsigned option)
+{
+ /* Negotiated format-param field-names constants. */
+ const pj_str_t STR_OCTET_ALIGN = {"octet-align=", 12};
+ const pj_str_t STR_CRC = {"crc=", 4};
+ const pj_str_t STR_ROBUST_SORTING = {"robust-sorting=", 15};
+ const pj_str_t STR_INTERLEAVING = {"interleaving=", 13};
+
+ /* Negotiated params and their default values. */
+ unsigned a_octet_align = 0, o_octet_align = 0;
+ unsigned a_crc = 0, o_crc = 0;
+ unsigned a_robust_sorting = 0, o_robust_sorting = 0;
+ unsigned a_interleaving = 0, o_interleaving = 0;
+
+ const pjmedia_sdp_attr *attr_ans;
+ const pjmedia_sdp_attr *attr_ofr;
+ pjmedia_sdp_fmtp fmtp;
+ pj_status_t status;
+
+ /* Parse offerer FMTP */
+ attr_ofr = pjmedia_sdp_media_find_attr2(offer, "fmtp",
+ &offer->desc.fmt[o_fmt_idx]);
+ if (attr_ofr) {
+ status = pjmedia_sdp_attr_get_fmtp(attr_ofr, &fmtp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ GET_FMTP_IVAL(o_octet_align, fmtp, STR_OCTET_ALIGN, 0);
+ GET_FMTP_IVAL(o_crc, fmtp, STR_CRC, 0);
+ GET_FMTP_IVAL(o_robust_sorting, fmtp, STR_ROBUST_SORTING, 0);
+ GET_FMTP_IVAL(o_interleaving, fmtp, STR_INTERLEAVING, 0);
+ }
+
+ /* Parse answerer FMTP */
+ attr_ans = pjmedia_sdp_media_find_attr2(answer, "fmtp",
+ &answer->desc.fmt[a_fmt_idx]);
+ if (attr_ans) {
+ status = pjmedia_sdp_attr_get_fmtp(attr_ans, &fmtp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ GET_FMTP_IVAL(a_octet_align, fmtp, STR_OCTET_ALIGN, 0);
+ GET_FMTP_IVAL(a_crc, fmtp, STR_CRC, 0);
+ GET_FMTP_IVAL(a_robust_sorting, fmtp, STR_ROBUST_SORTING, 0);
+ GET_FMTP_IVAL(a_interleaving, fmtp, STR_INTERLEAVING, 0);
+ }
+
+ /* First, match crc, robust-sorting, interleaving settings */
+ if (a_crc != o_crc ||
+ a_robust_sorting != o_robust_sorting ||
+ a_interleaving != o_interleaving)
+ {
+ return PJMEDIA_SDP_EFORMATNOTEQUAL;
+ }
+
+ /* Match octet-align setting */
+ if (a_octet_align != o_octet_align) {
+ /* Check if answer can be modified to match to the offer */
+ if (option & PJMEDIA_SDP_NEG_FMT_MATCH_ALLOW_MODIFY_ANSWER) {
+ status = amr_toggle_octet_align(pool, answer, a_fmt_idx);
+ return status;
+ } else {
+ return PJMEDIA_SDP_EFORMATNOTEQUAL;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
diff --git a/pjmedia/src/pjmedia-codec/audio_codecs.c b/pjmedia/src/pjmedia-codec/audio_codecs.c
new file mode 100644
index 0000000..caf63e5
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/audio_codecs.c
@@ -0,0 +1,119 @@
+/* $Id: audio_codecs.c 3910 2011-12-15 06:34:25Z nanang $ */
+/*
+ * Copyright (C) 2011-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia-codec.h>
+#include <pjmedia/g711.h>
+
+PJ_DEF(void) pjmedia_audio_codec_config_default(pjmedia_audio_codec_config*cfg)
+{
+ pj_bzero(cfg, sizeof(*cfg));
+ cfg->speex.option = 0;
+ cfg->speex.quality = PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY;
+ cfg->speex.complexity = PJMEDIA_CODEC_SPEEX_DEFAULT_COMPLEXITY;
+ cfg->ilbc.mode = 30;
+ cfg->passthrough.setting.ilbc_mode = cfg->ilbc.mode;
+}
+
+PJ_DEF(pj_status_t)
+pjmedia_codec_register_audio_codecs(pjmedia_endpt *endpt,
+ const pjmedia_audio_codec_config *c)
+{
+ pjmedia_audio_codec_config default_cfg;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(endpt, PJ_EINVAL);
+ if (!c) {
+ pjmedia_audio_codec_config_default(&default_cfg);
+ c = &default_cfg;
+ }
+
+ PJ_ASSERT_RETURN(c->ilbc.mode==20 || c->ilbc.mode==30, PJ_EINVAL);
+
+#if PJMEDIA_HAS_PASSTHROUGH_CODECS
+ status = pjmedia_codec_passthrough_init2(endpt, &c->passthrough.setting);
+ if (status != PJ_SUCCESS)
+ return status;
+#endif
+
+#if PJMEDIA_HAS_SPEEX_CODEC
+ /* Register speex. */
+ status = pjmedia_codec_speex_init(endpt, c->speex.option,
+ c->speex.quality,
+ c->speex.complexity);
+ if (status != PJ_SUCCESS)
+ return status;
+#endif
+
+#if PJMEDIA_HAS_ILBC_CODEC
+ /* Register iLBC. */
+ status = pjmedia_codec_ilbc_init( endpt, c->ilbc.mode);
+ if (status != PJ_SUCCESS)
+ return status;
+#endif /* PJMEDIA_HAS_ILBC_CODEC */
+
+#if PJMEDIA_HAS_GSM_CODEC
+ /* Register GSM */
+ status = pjmedia_codec_gsm_init(endpt);
+ if (status != PJ_SUCCESS)
+ return status;
+#endif /* PJMEDIA_HAS_GSM_CODEC */
+
+#if PJMEDIA_HAS_G711_CODEC
+ /* Register PCMA and PCMU */
+ status = pjmedia_codec_g711_init(endpt);
+ if (status != PJ_SUCCESS)
+ return status;
+#endif /* PJMEDIA_HAS_G711_CODEC */
+
+#if PJMEDIA_HAS_G722_CODEC
+ status = pjmedia_codec_g722_init(endpt );
+ if (status != PJ_SUCCESS)
+ return status;
+#endif /* PJMEDIA_HAS_G722_CODEC */
+
+#if PJMEDIA_HAS_INTEL_IPP
+ /* Register IPP codecs */
+ status = pjmedia_codec_ipp_init(endpt);
+ if (status != PJ_SUCCESS)
+ return status;
+#endif /* PJMEDIA_HAS_INTEL_IPP */
+
+#if PJMEDIA_HAS_G7221_CODEC
+ /* Register G722.1 codecs */
+ status = pjmedia_codec_g7221_init(endpt);
+ if (status != PJ_SUCCESS)
+ return status;
+#endif /* PJMEDIA_HAS_G7221_CODEC */
+
+#if PJMEDIA_HAS_L16_CODEC
+ /* Register L16 family codecs */
+ status = pjmedia_codec_l16_init(endpt, 0);
+ if (status != PJ_SUCCESS)
+ return status;
+#endif /* PJMEDIA_HAS_L16_CODEC */
+
+#if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC
+ /* Register OpenCORE AMR-NB */
+ status = pjmedia_codec_opencore_amrnb_init(endpt);
+ if (status != PJ_SUCCESS)
+ return status;
+#endif
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c b/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c
new file mode 100644
index 0000000..d0e87ef
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c
@@ -0,0 +1,1818 @@
+/* $Id: ffmpeg_vid_codecs.c 4089 2012-04-26 07:27:06Z nanang $ */
+/*
+ * Copyright (C) 2010-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia-codec/ffmpeg_vid_codecs.h>
+#include <pjmedia-codec/h263_packetizer.h>
+#include <pjmedia-codec/h264_packetizer.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/vid_codec_util.h>
+#include <pj/assert.h>
+#include <pj/list.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/os.h>
+
+
+/*
+ * Only build this file if PJMEDIA_HAS_FFMPEG_VID_CODEC != 0 and
+ * PJMEDIA_HAS_VIDEO != 0
+ */
+#if defined(PJMEDIA_HAS_FFMPEG_VID_CODEC) && \
+ PJMEDIA_HAS_FFMPEG_VID_CODEC != 0 && \
+ defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+#define THIS_FILE "ffmpeg_vid_codecs.c"
+
+#define LIBAVCODEC_VER_AT_LEAST(major,minor) (LIBAVCODEC_VERSION_MAJOR > major || \
+ (LIBAVCODEC_VERSION_MAJOR == major && \
+ LIBAVCODEC_VERSION_MINOR >= minor))
+
+#include "../pjmedia/ffmpeg_util.h"
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#if LIBAVCODEC_VER_AT_LEAST(53,20)
+ /* Needed by 264 so far, on libavcodec 53.20 */
+# include <libavutil/opt.h>
+#endif
+
+
+/* Various compatibility */
+
+#if LIBAVCODEC_VER_AT_LEAST(53,20)
+# define AVCODEC_OPEN(ctx,c) avcodec_open2(ctx,c,NULL)
+#else
+# define AVCODEC_OPEN(ctx,c) avcodec_open(ctx,c)
+#endif
+
+#if LIBAVCODEC_VER_AT_LEAST(53,61)
+/* Not sure when AVCodec::encode2 is introduced. It appears in
+ * libavcodec 53.61 where some codecs actually still use AVCodec::encode
+ * (e.g: H263, H264).
+ */
+# define AVCODEC_HAS_ENCODE(c) (c->encode || c->encode2)
+# define AV_OPT_SET(obj,name,val,opt) (av_opt_set(obj,name,val,opt)==0)
+# define AV_OPT_SET_INT(obj,name,val) (av_opt_set_int(obj,name,val,0)==0)
+#else
+# define AVCODEC_HAS_ENCODE(c) (c->encode)
+# define AV_OPT_SET(obj,name,val,opt) (av_set_string3(obj,name,val,opt,NULL)==0)
+# define AV_OPT_SET_INT(obj,name,val) (av_set_int(obj,name,val)!=NULL)
+#endif
+#define AVCODEC_HAS_DECODE(c) (c->decode)
+
+
+/* Prototypes for FFMPEG codecs factory */
+static pj_status_t ffmpeg_test_alloc( pjmedia_vid_codec_factory *factory,
+ const pjmedia_vid_codec_info *id );
+static pj_status_t ffmpeg_default_attr( pjmedia_vid_codec_factory *factory,
+ const pjmedia_vid_codec_info *info,
+ pjmedia_vid_codec_param *attr );
+static pj_status_t ffmpeg_enum_codecs( pjmedia_vid_codec_factory *factory,
+ unsigned *count,
+ pjmedia_vid_codec_info codecs[]);
+static pj_status_t ffmpeg_alloc_codec( pjmedia_vid_codec_factory *factory,
+ const pjmedia_vid_codec_info *info,
+ pjmedia_vid_codec **p_codec);
+static pj_status_t ffmpeg_dealloc_codec( pjmedia_vid_codec_factory *factory,
+ pjmedia_vid_codec *codec );
+
+/* Prototypes for FFMPEG codecs implementation. */
+static pj_status_t ffmpeg_codec_init( pjmedia_vid_codec *codec,
+ pj_pool_t *pool );
+static pj_status_t ffmpeg_codec_open( pjmedia_vid_codec *codec,
+ pjmedia_vid_codec_param *attr );
+static pj_status_t ffmpeg_codec_close( pjmedia_vid_codec *codec );
+static pj_status_t ffmpeg_codec_modify(pjmedia_vid_codec *codec,
+ const pjmedia_vid_codec_param *attr );
+static pj_status_t ffmpeg_codec_get_param(pjmedia_vid_codec *codec,
+ pjmedia_vid_codec_param *param);
+static pj_status_t ffmpeg_codec_encode_begin(pjmedia_vid_codec *codec,
+ const pjmedia_vid_encode_opt *opt,
+ const pjmedia_frame *input,
+ unsigned out_size,
+ pjmedia_frame *output,
+ pj_bool_t *has_more);
+static pj_status_t ffmpeg_codec_encode_more(pjmedia_vid_codec *codec,
+ unsigned out_size,
+ pjmedia_frame *output,
+ pj_bool_t *has_more);
+static pj_status_t ffmpeg_codec_decode( pjmedia_vid_codec *codec,
+ pj_size_t pkt_count,
+ pjmedia_frame packets[],
+ unsigned out_size,
+ pjmedia_frame *output);
+
+/* Definition for FFMPEG codecs operations. */
+static pjmedia_vid_codec_op ffmpeg_op =
+{
+ &ffmpeg_codec_init,
+ &ffmpeg_codec_open,
+ &ffmpeg_codec_close,
+ &ffmpeg_codec_modify,
+ &ffmpeg_codec_get_param,
+ &ffmpeg_codec_encode_begin,
+ &ffmpeg_codec_encode_more,
+ &ffmpeg_codec_decode,
+ NULL
+};
+
+/* Definition for FFMPEG codecs factory operations. */
+static pjmedia_vid_codec_factory_op ffmpeg_factory_op =
+{
+ &ffmpeg_test_alloc,
+ &ffmpeg_default_attr,
+ &ffmpeg_enum_codecs,
+ &ffmpeg_alloc_codec,
+ &ffmpeg_dealloc_codec
+};
+
+
+/* FFMPEG codecs factory */
+static struct ffmpeg_factory {
+ pjmedia_vid_codec_factory base;
+ pjmedia_vid_codec_mgr *mgr;
+ pj_pool_factory *pf;
+ pj_pool_t *pool;
+ pj_mutex_t *mutex;
+} ffmpeg_factory;
+
+
+typedef struct ffmpeg_codec_desc ffmpeg_codec_desc;
+
+
+/* FFMPEG codecs private data. */
+typedef struct ffmpeg_private
+{
+ const ffmpeg_codec_desc *desc;
+ pjmedia_vid_codec_param param; /**< Codec param */
+ pj_pool_t *pool; /**< Pool for each instance */
+
+ /* Format info and apply format param */
+ const pjmedia_video_format_info *enc_vfi;
+ pjmedia_video_apply_fmt_param enc_vafp;
+ const pjmedia_video_format_info *dec_vfi;
+ pjmedia_video_apply_fmt_param dec_vafp;
+
+ /* Buffers, only needed for multi-packets */
+ pj_bool_t whole;
+ void *enc_buf;
+ unsigned enc_buf_size;
+ pj_bool_t enc_buf_is_keyframe;
+ unsigned enc_frame_len;
+ unsigned enc_processed;
+ void *dec_buf;
+ unsigned dec_buf_size;
+ pj_timestamp last_dec_keyframe_ts;
+
+ /* The ffmpeg codec states. */
+ AVCodec *enc;
+ AVCodec *dec;
+ AVCodecContext *enc_ctx;
+ AVCodecContext *dec_ctx;
+
+ /* The ffmpeg decoder cannot set the output format, so format conversion
+ * may be needed for post-decoding.
+ */
+ enum PixelFormat expected_dec_fmt;
+ /**< Expected output format of
+ ffmpeg decoder */
+
+ void *data; /**< Codec specific data */
+} ffmpeg_private;
+
+
+/* Shortcuts for packetize & unpacketize function declaration,
+ * as it has long params and is reused many times!
+ */
+#define FUNC_PACKETIZE(name) \
+ pj_status_t(name)(ffmpeg_private *ff, pj_uint8_t *bits, \
+ pj_size_t bits_len, unsigned *bits_pos, \
+ const pj_uint8_t **payload, pj_size_t *payload_len)
+
+#define FUNC_UNPACKETIZE(name) \
+ pj_status_t(name)(ffmpeg_private *ff, const pj_uint8_t *payload, \
+ pj_size_t payload_len, pj_uint8_t *bits, \
+ pj_size_t bits_len, unsigned *bits_pos)
+
+#define FUNC_FMT_MATCH(name) \
+ pj_status_t(name)(pj_pool_t *pool, \
+ pjmedia_sdp_media *offer, unsigned o_fmt_idx, \
+ pjmedia_sdp_media *answer, unsigned a_fmt_idx, \
+ unsigned option)
+
+
+/* Type definition of codec specific functions */
+typedef FUNC_PACKETIZE(*func_packetize);
+typedef FUNC_UNPACKETIZE(*func_unpacketize);
+typedef pj_status_t (*func_preopen) (ffmpeg_private *ff);
+typedef pj_status_t (*func_postopen) (ffmpeg_private *ff);
+typedef FUNC_FMT_MATCH(*func_sdp_fmt_match);
+
+
+/* FFMPEG codec info */
+struct ffmpeg_codec_desc
+{
+ /* Predefined info */
+ pjmedia_vid_codec_info info;
+ pjmedia_format_id base_fmt_id; /**< Some codecs may be exactly
+ same or compatible with
+ another codec, base format
+ will tell the initializer
+ to copy this codec desc
+ from its base format */
+ pjmedia_rect_size size;
+ pjmedia_ratio fps;
+ pj_uint32_t avg_bps;
+ pj_uint32_t max_bps;
+ func_packetize packetize;
+ func_unpacketize unpacketize;
+ func_preopen preopen;
+ func_preopen postopen;
+ func_sdp_fmt_match sdp_fmt_match;
+ pjmedia_codec_fmtp dec_fmtp;
+
+ /* Init time defined info */
+ pj_bool_t enabled;
+ AVCodec *enc;
+ AVCodec *dec;
+};
+
+
+#if PJMEDIA_HAS_FFMPEG_CODEC_H264 && !LIBAVCODEC_VER_AT_LEAST(53,20)
+# error "Must use libavcodec version 53.20 or later to enable FFMPEG H264"
+#endif
+
+/* H264 constants */
+#define PROFILE_H264_BASELINE 66
+#define PROFILE_H264_MAIN 77
+
+/* Codec specific functions */
+#if PJMEDIA_HAS_FFMPEG_CODEC_H264
+static pj_status_t h264_preopen(ffmpeg_private *ff);
+static pj_status_t h264_postopen(ffmpeg_private *ff);
+static FUNC_PACKETIZE(h264_packetize);
+static FUNC_UNPACKETIZE(h264_unpacketize);
+#endif
+
+static pj_status_t h263_preopen(ffmpeg_private *ff);
+static FUNC_PACKETIZE(h263_packetize);
+static FUNC_UNPACKETIZE(h263_unpacketize);
+
+
+/* Internal codec info */
+static ffmpeg_codec_desc codec_desc[] =
+{
+#if PJMEDIA_HAS_FFMPEG_CODEC_H264
+ {
+ {PJMEDIA_FORMAT_H264, PJMEDIA_RTP_PT_H264, {"H264",4},
+ {"Constrained Baseline (level=30, pack=1)", 39}},
+ 0,
+ {720, 480}, {15, 1}, 256000, 256000,
+ &h264_packetize, &h264_unpacketize, &h264_preopen, &h264_postopen,
+ &pjmedia_vid_codec_h264_match_sdp,
+ /* Leading space for better compatibility (strange indeed!) */
+ {2, { {{"profile-level-id",16}, {"42e01e",6}},
+ {{" packetization-mode",19}, {"1",1}}, } },
+ },
+#endif
+
+#if PJMEDIA_HAS_FFMPEG_CODEC_H263P
+ {
+ {PJMEDIA_FORMAT_H263P, PJMEDIA_RTP_PT_H263P, {"H263-1998",9}},
+ PJMEDIA_FORMAT_H263,
+ {352, 288}, {15, 1}, 256000, 256000,
+ &h263_packetize, &h263_unpacketize, &h263_preopen, NULL, NULL,
+ {2, { {{"CIF",3}, {"1",1}},
+ {{"QCIF",4}, {"1",1}}, } },
+ },
+#endif
+
+ {
+ {PJMEDIA_FORMAT_H263, PJMEDIA_RTP_PT_H263, {"H263",4}},
+ },
+ {
+ {PJMEDIA_FORMAT_H261, PJMEDIA_RTP_PT_H261, {"H261",4}},
+ },
+ {
+ {PJMEDIA_FORMAT_MJPEG, PJMEDIA_RTP_PT_JPEG, {"JPEG",4}},
+ PJMEDIA_FORMAT_MJPEG, {640, 480}, {25, 1},
+ },
+ {
+ {PJMEDIA_FORMAT_MPEG4, 0, {"MP4V",4}},
+ PJMEDIA_FORMAT_MPEG4, {640, 480}, {25, 1},
+ },
+};
+
+#if PJMEDIA_HAS_FFMPEG_CODEC_H264
+
+typedef struct h264_data
+{
+ pjmedia_vid_codec_h264_fmtp fmtp;
+ pjmedia_h264_packetizer *pktz;
+} h264_data;
+
+
+static pj_status_t h264_preopen(ffmpeg_private *ff)
+{
+ h264_data *data;
+ pjmedia_h264_packetizer_cfg pktz_cfg;
+ pj_status_t status;
+
+ data = PJ_POOL_ZALLOC_T(ff->pool, h264_data);
+ ff->data = data;
+
+ /* Parse remote fmtp */
+ status = pjmedia_vid_codec_h264_parse_fmtp(&ff->param.enc_fmtp,
+ &data->fmtp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Create packetizer */
+ pktz_cfg.mtu = ff->param.enc_mtu;
+#if 0
+ if (data->fmtp.packetization_mode == 0)
+ pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL;
+ else if (data->fmtp.packetization_mode == 1)
+ pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED;
+ else
+ return PJ_ENOTSUP;
+#else
+ if (data->fmtp.packetization_mode!=
+ PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL &&
+ data->fmtp.packetization_mode!=
+ PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED)
+ {
+ return PJ_ENOTSUP;
+ }
+ /* Better always send in single NAL mode for better compatibility */
+ pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL;
+#endif
+
+ status = pjmedia_h264_packetizer_create(ff->pool, &pktz_cfg, &data->pktz);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Apply SDP fmtp to format in codec param */
+ if (!ff->param.ignore_fmtp) {
+ status = pjmedia_vid_codec_h264_apply_fmtp(&ff->param);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ if (ff->param.dir & PJMEDIA_DIR_ENCODING) {
+ pjmedia_video_format_detail *vfd;
+ AVCodecContext *ctx = ff->enc_ctx;
+ const char *profile = NULL;
+
+ vfd = pjmedia_format_get_video_format_detail(&ff->param.enc_fmt,
+ PJ_TRUE);
+
+ /* Override generic params after applying SDP fmtp */
+ ctx->width = vfd->size.w;
+ ctx->height = vfd->size.h;
+ ctx->time_base.num = vfd->fps.denum;
+ ctx->time_base.den = vfd->fps.num;
+
+ /* Apply profile. */
+ ctx->profile = data->fmtp.profile_idc;
+ switch (ctx->profile) {
+ case PROFILE_H264_BASELINE:
+ profile = "baseline";
+ break;
+ case PROFILE_H264_MAIN:
+ profile = "main";
+ break;
+ default:
+ break;
+ }
+ if (profile && !AV_OPT_SET(ctx->priv_data, "profile", profile, 0))
+ {
+ PJ_LOG(3, (THIS_FILE, "Failed to set H264 profile to '%s'",
+ profile));
+ }
+
+ /* Apply profile constraint bits. */
+ //PJ_TODO(set_h264_constraint_bits_properly_in_ffmpeg);
+ if (data->fmtp.profile_iop) {
+#if defined(FF_PROFILE_H264_CONSTRAINED)
+ ctx->profile |= FF_PROFILE_H264_CONSTRAINED;
+#endif
+ }
+
+ /* Apply profile level. */
+ ctx->level = data->fmtp.level;
+
+ /* Limit NAL unit size as we prefer single NAL unit packetization */
+ if (!AV_OPT_SET_INT(ctx->priv_data, "slice-max-size", ff->param.enc_mtu))
+ {
+ PJ_LOG(3, (THIS_FILE, "Failed to set H264 max NAL size to %d",
+ ff->param.enc_mtu));
+ }
+
+ /* Apply intra-refresh */
+ if (!AV_OPT_SET_INT(ctx->priv_data, "intra-refresh", 1))
+ {
+ PJ_LOG(3, (THIS_FILE, "Failed to set x264 intra-refresh"));
+ }
+
+ /* Misc x264 settings (performance, quality, latency, etc).
+ * Let's just use the x264 predefined preset & tune.
+ */
+ if (!AV_OPT_SET(ctx->priv_data, "preset", "veryfast", 0)) {
+ PJ_LOG(3, (THIS_FILE, "Failed to set x264 preset 'veryfast'"));
+ }
+ if (!AV_OPT_SET(ctx->priv_data, "tune", "animation+zerolatency", 0)) {
+ PJ_LOG(3, (THIS_FILE, "Failed to set x264 tune 'zerolatency'"));
+ }
+ }
+
+ if (ff->param.dir & PJMEDIA_DIR_DECODING) {
+ AVCodecContext *ctx = ff->dec_ctx;
+
+ /* Apply the "sprop-parameter-sets" fmtp from remote SDP to
+ * extradata of ffmpeg codec context.
+ */
+ if (data->fmtp.sprop_param_sets_len) {
+ ctx->extradata_size = data->fmtp.sprop_param_sets_len;
+ ctx->extradata = data->fmtp.sprop_param_sets;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t h264_postopen(ffmpeg_private *ff)
+{
+ h264_data *data = (h264_data*)ff->data;
+ PJ_UNUSED_ARG(data);
+ return PJ_SUCCESS;
+}
+
+static FUNC_PACKETIZE(h264_packetize)
+{
+ h264_data *data = (h264_data*)ff->data;
+ return pjmedia_h264_packetize(data->pktz, bits, bits_len, bits_pos,
+ payload, payload_len);
+}
+
+static FUNC_UNPACKETIZE(h264_unpacketize)
+{
+ h264_data *data = (h264_data*)ff->data;
+ return pjmedia_h264_unpacketize(data->pktz, payload, payload_len,
+ bits, bits_len, bits_pos);
+}
+
+#endif /* PJMEDIA_HAS_FFMPEG_CODEC_H264 */
+
+
+#if PJMEDIA_HAS_FFMPEG_CODEC_H263P
+
+typedef struct h263_data
+{
+ pjmedia_h263_packetizer *pktz;
+} h263_data;
+
+/* H263 pre-open */
+static pj_status_t h263_preopen(ffmpeg_private *ff)
+{
+ h263_data *data;
+ pjmedia_h263_packetizer_cfg pktz_cfg;
+ pj_status_t status;
+
+ data = PJ_POOL_ZALLOC_T(ff->pool, h263_data);
+ ff->data = data;
+
+ /* Create packetizer */
+ pktz_cfg.mtu = ff->param.enc_mtu;
+ pktz_cfg.mode = PJMEDIA_H263_PACKETIZER_MODE_RFC4629;
+ status = pjmedia_h263_packetizer_create(ff->pool, &pktz_cfg, &data->pktz);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Apply fmtp settings to codec param */
+ if (!ff->param.ignore_fmtp) {
+ status = pjmedia_vid_codec_h263_apply_fmtp(&ff->param);
+ }
+
+ /* Override generic params after applying SDP fmtp */
+ if (ff->param.dir & PJMEDIA_DIR_ENCODING) {
+ pjmedia_video_format_detail *vfd;
+ AVCodecContext *ctx = ff->enc_ctx;
+
+ vfd = pjmedia_format_get_video_format_detail(&ff->param.enc_fmt,
+ PJ_TRUE);
+
+ /* Override generic params after applying SDP fmtp */
+ ctx->width = vfd->size.w;
+ ctx->height = vfd->size.h;
+ ctx->time_base.num = vfd->fps.denum;
+ ctx->time_base.den = vfd->fps.num;
+ }
+
+ return status;
+}
+
+static FUNC_PACKETIZE(h263_packetize)
+{
+ h263_data *data = (h263_data*)ff->data;
+ return pjmedia_h263_packetize(data->pktz, bits, bits_len, bits_pos,
+ payload, payload_len);
+}
+
+static FUNC_UNPACKETIZE(h263_unpacketize)
+{
+ h263_data *data = (h263_data*)ff->data;
+ return pjmedia_h263_unpacketize(data->pktz, payload, payload_len,
+ bits, bits_len, bits_pos);
+}
+
+#endif /* PJMEDIA_HAS_FFMPEG_CODEC_H263P */
+
+
+static const ffmpeg_codec_desc* find_codec_desc_by_info(
+ const pjmedia_vid_codec_info *info)
+{
+ int i;
+
+ for (i=0; i<PJ_ARRAY_SIZE(codec_desc); ++i) {
+ ffmpeg_codec_desc *desc = &codec_desc[i];
+
+ if (desc->enabled &&
+ (desc->info.fmt_id == info->fmt_id) &&
+ ((desc->info.dir & info->dir) == info->dir) &&
+ (desc->info.pt == info->pt) &&
+ (desc->info.packings & info->packings))
+ {
+ return desc;
+ }
+ }
+
+ return NULL;
+}
+
+
+static int find_codec_idx_by_fmt_id(pjmedia_format_id fmt_id)
+{
+ int i;
+ for (i=0; i<PJ_ARRAY_SIZE(codec_desc); ++i) {
+ if (codec_desc[i].info.fmt_id == fmt_id)
+ return i;
+ }
+
+ return -1;
+}
+
+
+/*
+ * Initialize and register FFMPEG codec factory to pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_ffmpeg_vid_init(pjmedia_vid_codec_mgr *mgr,
+ pj_pool_factory *pf)
+{
+ pj_pool_t *pool;
+ AVCodec *c;
+ pj_status_t status;
+ unsigned i;
+
+ if (ffmpeg_factory.pool != NULL) {
+ /* Already initialized. */
+ return PJ_SUCCESS;
+ }
+
+ if (!mgr) mgr = pjmedia_vid_codec_mgr_instance();
+ PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+ /* Create FFMPEG codec factory. */
+ ffmpeg_factory.base.op = &ffmpeg_factory_op;
+ ffmpeg_factory.base.factory_data = NULL;
+ ffmpeg_factory.mgr = mgr;
+ ffmpeg_factory.pf = pf;
+
+ pool = pj_pool_create(pf, "ffmpeg codec factory", 256, 256, NULL);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ /* Create mutex. */
+ status = pj_mutex_create_simple(pool, "ffmpeg codec factory",
+ &ffmpeg_factory.mutex);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ pjmedia_ffmpeg_add_ref();
+#if !LIBAVCODEC_VER_AT_LEAST(53,20)
+ /* avcodec_init() dissappeared between version 53.20 and 54.15, not sure
+ * exactly when
+ */
+ avcodec_init();
+#endif
+ avcodec_register_all();
+
+ /* Enum FFMPEG codecs */
+ for (c=av_codec_next(NULL); c; c=av_codec_next(c)) {
+ ffmpeg_codec_desc *desc;
+ pjmedia_format_id fmt_id;
+ int codec_info_idx;
+
+#if LIBAVCODEC_VERSION_MAJOR <= 52
+# define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO
+#endif
+ if (c->type != AVMEDIA_TYPE_VIDEO)
+ continue;
+
+ /* Video encoder and decoder are usually implemented in separate
+ * AVCodec instances. While the codec attributes (e.g: raw formats,
+ * supported fps) are in the encoder.
+ */
+
+ //PJ_LOG(3, (THIS_FILE, "%s", c->name));
+ status = CodecID_to_pjmedia_format_id(c->id, &fmt_id);
+ /* Skip if format ID is unknown */
+ if (status != PJ_SUCCESS)
+ continue;
+
+ codec_info_idx = find_codec_idx_by_fmt_id(fmt_id);
+ /* Skip if codec is unwanted by this wrapper (not listed in
+ * the codec info array)
+ */
+ if (codec_info_idx < 0)
+ continue;
+
+ desc = &codec_desc[codec_info_idx];
+
+ /* Skip duplicated codec implementation */
+ if ((AVCODEC_HAS_ENCODE(c) && (desc->info.dir & PJMEDIA_DIR_ENCODING))
+ ||
+ (AVCODEC_HAS_DECODE(c) && (desc->info.dir & PJMEDIA_DIR_DECODING)))
+ {
+ continue;
+ }
+
+ /* Get raw/decoded format ids in the encoder */
+ if (c->pix_fmts && AVCODEC_HAS_ENCODE(c)) {
+ pjmedia_format_id raw_fmt[PJMEDIA_VID_CODEC_MAX_DEC_FMT_CNT];
+ unsigned raw_fmt_cnt = 0;
+ unsigned raw_fmt_cnt_should_be = 0;
+ const enum PixelFormat *p = c->pix_fmts;
+
+ for(;(p && *p != -1) &&
+ (raw_fmt_cnt < PJMEDIA_VID_CODEC_MAX_DEC_FMT_CNT);
+ ++p)
+ {
+ pjmedia_format_id fmt_id;
+
+ raw_fmt_cnt_should_be++;
+ status = PixelFormat_to_pjmedia_format_id(*p, &fmt_id);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(6, (THIS_FILE, "Unrecognized ffmpeg pixel "
+ "format %d", *p));
+ continue;
+ }
+
+ //raw_fmt[raw_fmt_cnt++] = fmt_id;
+ /* Disable some formats due to H.264 error:
+ * x264 [error]: baseline profile doesn't support 4:4:4
+ */
+ if (desc->info.pt != PJMEDIA_RTP_PT_H264 ||
+ fmt_id != PJMEDIA_FORMAT_RGB24)
+ {
+ raw_fmt[raw_fmt_cnt++] = fmt_id;
+ }
+ }
+
+ if (raw_fmt_cnt == 0) {
+ PJ_LOG(5, (THIS_FILE, "No recognized raw format "
+ "for codec [%s/%s], codec ignored",
+ c->name, c->long_name));
+ /* Skip this encoder */
+ continue;
+ }
+
+ if (raw_fmt_cnt < raw_fmt_cnt_should_be) {
+ PJ_LOG(6, (THIS_FILE, "Codec [%s/%s] have %d raw formats, "
+ "recognized only %d raw formats",
+ c->name, c->long_name,
+ raw_fmt_cnt_should_be, raw_fmt_cnt));
+ }
+
+ desc->info.dec_fmt_id_cnt = raw_fmt_cnt;
+ pj_memcpy(desc->info.dec_fmt_id, raw_fmt,
+ sizeof(raw_fmt[0])*raw_fmt_cnt);
+ }
+
+ /* Get supported framerates */
+ if (c->supported_framerates) {
+ const AVRational *fr = c->supported_framerates;
+ while ((fr->num != 0 || fr->den != 0) &&
+ desc->info.fps_cnt < PJMEDIA_VID_CODEC_MAX_FPS_CNT)
+ {
+ desc->info.fps[desc->info.fps_cnt].num = fr->num;
+ desc->info.fps[desc->info.fps_cnt].denum = fr->den;
+ ++desc->info.fps_cnt;
+ ++fr;
+ }
+ }
+
+ /* Get ffmpeg encoder instance */
+ if (AVCODEC_HAS_ENCODE(c) && !desc->enc) {
+ desc->info.dir |= PJMEDIA_DIR_ENCODING;
+ desc->enc = c;
+ }
+
+ /* Get ffmpeg decoder instance */
+ if (AVCODEC_HAS_DECODE(c) && !desc->dec) {
+ desc->info.dir |= PJMEDIA_DIR_DECODING;
+ desc->dec = c;
+ }
+
+ /* Enable this codec when any ffmpeg codec instance are recognized
+ * and the supported raw formats info has been collected.
+ */
+ if ((desc->dec || desc->enc) && desc->info.dec_fmt_id_cnt)
+ {
+ desc->enabled = PJ_TRUE;
+ }
+
+ /* Normalize default value of clock rate */
+ if (desc->info.clock_rate == 0)
+ desc->info.clock_rate = 90000;
+
+ /* Set supported packings */
+ desc->info.packings |= PJMEDIA_VID_PACKING_WHOLE;
+ if (desc->packetize && desc->unpacketize)
+ desc->info.packings |= PJMEDIA_VID_PACKING_PACKETS;
+
+ }
+
+ /* Review all codecs for applying base format, registering format match for
+ * SDP negotiation, etc.
+ */
+ for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) {
+ ffmpeg_codec_desc *desc = &codec_desc[i];
+
+ /* Init encoder/decoder description from base format */
+ if (desc->base_fmt_id && (!desc->dec || !desc->enc)) {
+ ffmpeg_codec_desc *base_desc = NULL;
+ int base_desc_idx;
+ pjmedia_dir copied_dir = PJMEDIA_DIR_NONE;
+
+ base_desc_idx = find_codec_idx_by_fmt_id(desc->base_fmt_id);
+ if (base_desc_idx != -1)
+ base_desc = &codec_desc[base_desc_idx];
+ if (!base_desc || !base_desc->enabled)
+ continue;
+
+ /* Copy description from base codec */
+ if (!desc->info.dec_fmt_id_cnt) {
+ desc->info.dec_fmt_id_cnt = base_desc->info.dec_fmt_id_cnt;
+ pj_memcpy(desc->info.dec_fmt_id, base_desc->info.dec_fmt_id,
+ sizeof(pjmedia_format_id)*desc->info.dec_fmt_id_cnt);
+ }
+ if (!desc->info.fps_cnt) {
+ desc->info.fps_cnt = base_desc->info.fps_cnt;
+ pj_memcpy(desc->info.fps, base_desc->info.fps,
+ sizeof(desc->info.fps[0])*desc->info.fps_cnt);
+ }
+ if (!desc->info.clock_rate) {
+ desc->info.clock_rate = base_desc->info.clock_rate;
+ }
+ if (!desc->dec && base_desc->dec) {
+ copied_dir |= PJMEDIA_DIR_DECODING;
+ desc->dec = base_desc->dec;
+ }
+ if (!desc->enc && base_desc->enc) {
+ copied_dir |= PJMEDIA_DIR_ENCODING;
+ desc->enc = base_desc->enc;
+ }
+
+ desc->info.dir |= copied_dir;
+ desc->enabled = (desc->info.dir != PJMEDIA_DIR_NONE);
+
+ /* Set supported packings */
+ desc->info.packings |= PJMEDIA_VID_PACKING_WHOLE;
+ if (desc->packetize && desc->unpacketize)
+ desc->info.packings |= PJMEDIA_VID_PACKING_PACKETS;
+
+ if (copied_dir != PJMEDIA_DIR_NONE) {
+ const char *dir_name[] = {NULL, "encoder", "decoder", "codec"};
+ PJ_LOG(5, (THIS_FILE, "The %.*s %s is using base codec (%.*s)",
+ desc->info.encoding_name.slen,
+ desc->info.encoding_name.ptr,
+ dir_name[copied_dir],
+ base_desc->info.encoding_name.slen,
+ base_desc->info.encoding_name.ptr));
+ }
+ }
+
+ /* Registering format match for SDP negotiation */
+ if (desc->sdp_fmt_match) {
+ status = pjmedia_sdp_neg_register_fmt_match_cb(
+ &desc->info.encoding_name,
+ desc->sdp_fmt_match);
+ pj_assert(status == PJ_SUCCESS);
+ }
+
+ /* Print warning about missing encoder/decoder */
+ if (!desc->enc) {
+ PJ_LOG(4, (THIS_FILE, "Cannot find %.*s encoder in ffmpeg library",
+ desc->info.encoding_name.slen,
+ desc->info.encoding_name.ptr));
+ }
+ if (!desc->dec) {
+ PJ_LOG(4, (THIS_FILE, "Cannot find %.*s decoder in ffmpeg library",
+ desc->info.encoding_name.slen,
+ desc->info.encoding_name.ptr));
+ }
+ }
+
+ /* Register codec factory to codec manager. */
+ status = pjmedia_vid_codec_mgr_register_factory(mgr,
+ &ffmpeg_factory.base);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ ffmpeg_factory.pool = pool;
+
+ /* Done. */
+ return PJ_SUCCESS;
+
+on_error:
+ pj_pool_release(pool);
+ return status;
+}
+
+/*
+ * Unregister FFMPEG codecs factory from pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_ffmpeg_vid_deinit(void)
+{
+ pj_status_t status = PJ_SUCCESS;
+
+ if (ffmpeg_factory.pool == NULL) {
+ /* Already deinitialized */
+ return PJ_SUCCESS;
+ }
+
+ pj_mutex_lock(ffmpeg_factory.mutex);
+
+ /* Unregister FFMPEG codecs factory. */
+ status = pjmedia_vid_codec_mgr_unregister_factory(ffmpeg_factory.mgr,
+ &ffmpeg_factory.base);
+
+ /* Destroy mutex. */
+ pj_mutex_destroy(ffmpeg_factory.mutex);
+
+ /* Destroy pool. */
+ pj_pool_release(ffmpeg_factory.pool);
+ ffmpeg_factory.pool = NULL;
+
+ pjmedia_ffmpeg_dec_ref();
+
+ return status;
+}
+
+
+/*
+ * Check if factory can allocate the specified codec.
+ */
+static pj_status_t ffmpeg_test_alloc( pjmedia_vid_codec_factory *factory,
+ const pjmedia_vid_codec_info *info )
+{
+ const ffmpeg_codec_desc *desc;
+
+ PJ_ASSERT_RETURN(factory==&ffmpeg_factory.base, PJ_EINVAL);
+ PJ_ASSERT_RETURN(info, PJ_EINVAL);
+
+ desc = find_codec_desc_by_info(info);
+ if (!desc) {
+ return PJMEDIA_CODEC_EUNSUP;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Generate default attribute.
+ */
+static pj_status_t ffmpeg_default_attr( pjmedia_vid_codec_factory *factory,
+ const pjmedia_vid_codec_info *info,
+ pjmedia_vid_codec_param *attr )
+{
+ const ffmpeg_codec_desc *desc;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(factory==&ffmpeg_factory.base, PJ_EINVAL);
+ PJ_ASSERT_RETURN(info && attr, PJ_EINVAL);
+
+ desc = find_codec_desc_by_info(info);
+ if (!desc) {
+ return PJMEDIA_CODEC_EUNSUP;
+ }
+
+ pj_bzero(attr, sizeof(pjmedia_vid_codec_param));
+
+ /* Scan the requested packings and use the lowest number */
+ attr->packing = 0;
+ for (i=0; i<15; ++i) {
+ unsigned packing = (1 << i);
+ if ((desc->info.packings & info->packings) & packing) {
+ attr->packing = (pjmedia_vid_packing)packing;
+ break;
+ }
+ }
+ if (attr->packing == 0) {
+ /* No supported packing in info */
+ return PJMEDIA_CODEC_EUNSUP;
+ }
+
+ /* Direction */
+ attr->dir = desc->info.dir;
+
+ /* Encoded format */
+ pjmedia_format_init_video(&attr->enc_fmt, desc->info.fmt_id,
+ desc->size.w, desc->size.h,
+ desc->fps.num, desc->fps.denum);
+
+ /* Decoded format */
+ pjmedia_format_init_video(&attr->dec_fmt, desc->info.dec_fmt_id[0],
+ desc->size.w, desc->size.h,
+ desc->fps.num, desc->fps.denum);
+
+ /* Decoding fmtp */
+ attr->dec_fmtp = desc->dec_fmtp;
+
+ /* Bitrate */
+ attr->enc_fmt.det.vid.avg_bps = desc->avg_bps;
+ attr->enc_fmt.det.vid.max_bps = desc->max_bps;
+
+ /* Encoding MTU */
+ attr->enc_mtu = PJMEDIA_MAX_VID_PAYLOAD_SIZE;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Enum codecs supported by this factory.
+ */
+static pj_status_t ffmpeg_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 == &ffmpeg_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;
+}
+
+/*
+ * Allocate a new codec instance.
+ */
+static pj_status_t ffmpeg_alloc_codec( pjmedia_vid_codec_factory *factory,
+ const pjmedia_vid_codec_info *info,
+ pjmedia_vid_codec **p_codec)
+{
+ ffmpeg_private *ff;
+ const ffmpeg_codec_desc *desc;
+ pjmedia_vid_codec *codec;
+ pj_pool_t *pool = NULL;
+ pj_status_t status = PJ_SUCCESS;
+
+ PJ_ASSERT_RETURN(factory && info && p_codec, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory == &ffmpeg_factory.base, PJ_EINVAL);
+
+ desc = find_codec_desc_by_info(info);
+ if (!desc) {
+ return PJMEDIA_CODEC_EUNSUP;
+ }
+
+ /* Create pool for codec instance */
+ pool = pj_pool_create(ffmpeg_factory.pf, "ffmpeg codec", 512, 512, NULL);
+ codec = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_codec);
+ if (!codec) {
+ status = PJ_ENOMEM;
+ goto on_error;
+ }
+ codec->op = &ffmpeg_op;
+ codec->factory = factory;
+ ff = PJ_POOL_ZALLOC_T(pool, ffmpeg_private);
+ if (!ff) {
+ status = PJ_ENOMEM;
+ goto on_error;
+ }
+ codec->codec_data = ff;
+ ff->pool = pool;
+ ff->enc = desc->enc;
+ ff->dec = desc->dec;
+ ff->desc = desc;
+
+ *p_codec = codec;
+ return PJ_SUCCESS;
+
+on_error:
+ if (pool)
+ pj_pool_release(pool);
+ return status;
+}
+
+/*
+ * Free codec.
+ */
+static pj_status_t ffmpeg_dealloc_codec( pjmedia_vid_codec_factory *factory,
+ pjmedia_vid_codec *codec )
+{
+ ffmpeg_private *ff;
+ pj_pool_t *pool;
+
+ PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory == &ffmpeg_factory.base, PJ_EINVAL);
+
+ /* Close codec, if it's not closed. */
+ ff = (ffmpeg_private*) codec->codec_data;
+ pool = ff->pool;
+ codec->codec_data = NULL;
+ pj_pool_release(pool);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Init codec.
+ */
+static pj_status_t ffmpeg_codec_init( pjmedia_vid_codec *codec,
+ pj_pool_t *pool )
+{
+ PJ_UNUSED_ARG(codec);
+ PJ_UNUSED_ARG(pool);
+ return PJ_SUCCESS;
+}
+
+static void print_ffmpeg_err(int err)
+{
+#if LIBAVCODEC_VER_AT_LEAST(52,72)
+ char errbuf[512];
+ if (av_strerror(err, errbuf, sizeof(errbuf)) >= 0)
+ PJ_LOG(5, (THIS_FILE, "ffmpeg err %d: %s", err, errbuf));
+#else
+ PJ_LOG(5, (THIS_FILE, "ffmpeg err %d", err));
+#endif
+
+}
+
+static pj_status_t open_ffmpeg_codec(ffmpeg_private *ff,
+ pj_mutex_t *ff_mutex)
+{
+ enum PixelFormat pix_fmt;
+ pjmedia_video_format_detail *vfd;
+ pj_bool_t enc_opened = PJ_FALSE, dec_opened = PJ_FALSE;
+ pj_status_t status;
+
+ /* Get decoded pixel format */
+ status = pjmedia_format_id_to_PixelFormat(ff->param.dec_fmt.id,
+ &pix_fmt);
+ if (status != PJ_SUCCESS)
+ return status;
+ ff->expected_dec_fmt = pix_fmt;
+
+ /* Get video format detail for shortcut access to encoded format */
+ vfd = pjmedia_format_get_video_format_detail(&ff->param.enc_fmt,
+ PJ_TRUE);
+
+ /* Allocate ffmpeg codec context */
+ if (ff->param.dir & PJMEDIA_DIR_ENCODING) {
+#if LIBAVCODEC_VER_AT_LEAST(53,20)
+ ff->enc_ctx = avcodec_alloc_context3(ff->enc);
+#else
+ ff->enc_ctx = avcodec_alloc_context();
+#endif
+ if (ff->enc_ctx == NULL)
+ goto on_error;
+ }
+ if (ff->param.dir & PJMEDIA_DIR_DECODING) {
+#if LIBAVCODEC_VER_AT_LEAST(53,20)
+ ff->dec_ctx = avcodec_alloc_context3(ff->dec);
+#else
+ ff->dec_ctx = avcodec_alloc_context();
+#endif
+ if (ff->dec_ctx == NULL)
+ goto on_error;
+ }
+
+ /* Init generic encoder params */
+ if (ff->param.dir & PJMEDIA_DIR_ENCODING) {
+ AVCodecContext *ctx = ff->enc_ctx;
+
+ ctx->pix_fmt = pix_fmt;
+ ctx->width = vfd->size.w;
+ ctx->height = vfd->size.h;
+ ctx->time_base.num = vfd->fps.denum;
+ ctx->time_base.den = vfd->fps.num;
+ if (vfd->avg_bps) {
+ ctx->bit_rate = vfd->avg_bps;
+ if (vfd->max_bps > vfd->avg_bps)
+ ctx->bit_rate_tolerance = vfd->max_bps - vfd->avg_bps;
+ }
+ ctx->strict_std_compliance = FF_COMPLIANCE_STRICT;
+ ctx->workaround_bugs = FF_BUG_AUTODETECT;
+ ctx->opaque = ff;
+
+ /* Set no delay, note that this may cause some codec functionals
+ * not working (e.g: rate control).
+ */
+#if LIBAVCODEC_VER_AT_LEAST(52,113) && !LIBAVCODEC_VER_AT_LEAST(53,20)
+ ctx->rc_lookahead = 0;
+#endif
+ }
+
+ /* Init generic decoder params */
+ if (ff->param.dir & PJMEDIA_DIR_DECODING) {
+ AVCodecContext *ctx = ff->dec_ctx;
+
+ /* Width/height may be overriden by ffmpeg after first decoding. */
+ ctx->width = ctx->coded_width = ff->param.dec_fmt.det.vid.size.w;
+ ctx->height = ctx->coded_height = ff->param.dec_fmt.det.vid.size.h;
+ ctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
+ ctx->workaround_bugs = FF_BUG_AUTODETECT;
+ ctx->opaque = ff;
+ }
+
+ /* Override generic params or apply specific params before opening
+ * the codec.
+ */
+ if (ff->desc->preopen) {
+ status = (*ff->desc->preopen)(ff);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ /* Open encoder */
+ if (ff->param.dir & PJMEDIA_DIR_ENCODING) {
+ int err;
+
+ pj_mutex_lock(ff_mutex);
+ err = AVCODEC_OPEN(ff->enc_ctx, ff->enc);
+ pj_mutex_unlock(ff_mutex);
+ if (err < 0) {
+ print_ffmpeg_err(err);
+ status = PJMEDIA_CODEC_EFAILED;
+ goto on_error;
+ }
+ enc_opened = PJ_TRUE;
+ }
+
+ /* Open decoder */
+ if (ff->param.dir & PJMEDIA_DIR_DECODING) {
+ int err;
+
+ pj_mutex_lock(ff_mutex);
+ err = AVCODEC_OPEN(ff->dec_ctx, ff->dec);
+ pj_mutex_unlock(ff_mutex);
+ if (err < 0) {
+ print_ffmpeg_err(err);
+ status = PJMEDIA_CODEC_EFAILED;
+ goto on_error;
+ }
+ dec_opened = PJ_TRUE;
+ }
+
+ /* Let the codec apply specific params after the codec opened */
+ if (ff->desc->postopen) {
+ status = (*ff->desc->postopen)(ff);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ return PJ_SUCCESS;
+
+on_error:
+ if (ff->enc_ctx) {
+ if (enc_opened)
+ avcodec_close(ff->enc_ctx);
+ av_free(ff->enc_ctx);
+ ff->enc_ctx = NULL;
+ }
+ if (ff->dec_ctx) {
+ if (dec_opened)
+ avcodec_close(ff->dec_ctx);
+ av_free(ff->dec_ctx);
+ ff->dec_ctx = NULL;
+ }
+ return status;
+}
+
+/*
+ * Open codec.
+ */
+static pj_status_t ffmpeg_codec_open( pjmedia_vid_codec *codec,
+ pjmedia_vid_codec_param *attr )
+{
+ ffmpeg_private *ff;
+ pj_status_t status;
+ pj_mutex_t *ff_mutex;
+
+ PJ_ASSERT_RETURN(codec && attr, PJ_EINVAL);
+ ff = (ffmpeg_private*)codec->codec_data;
+
+ pj_memcpy(&ff->param, attr, sizeof(*attr));
+
+ /* Normalize encoding MTU in codec param */
+ if (attr->enc_mtu > PJMEDIA_MAX_VID_PAYLOAD_SIZE)
+ attr->enc_mtu = PJMEDIA_MAX_VID_PAYLOAD_SIZE;
+
+ /* Open the codec */
+ ff_mutex = ((struct ffmpeg_factory*)codec->factory)->mutex;
+ status = open_ffmpeg_codec(ff, ff_mutex);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Init format info and apply-param of decoder */
+ ff->dec_vfi = pjmedia_get_video_format_info(NULL, ff->param.dec_fmt.id);
+ if (!ff->dec_vfi) {
+ status = PJ_EINVAL;
+ goto on_error;
+ }
+ pj_bzero(&ff->dec_vafp, sizeof(ff->dec_vafp));
+ ff->dec_vafp.size = ff->param.dec_fmt.det.vid.size;
+ ff->dec_vafp.buffer = NULL;
+ status = (*ff->dec_vfi->apply_fmt)(ff->dec_vfi, &ff->dec_vafp);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ /* Init format info and apply-param of encoder */
+ ff->enc_vfi = pjmedia_get_video_format_info(NULL, ff->param.dec_fmt.id);
+ if (!ff->enc_vfi) {
+ status = PJ_EINVAL;
+ goto on_error;
+ }
+ pj_bzero(&ff->enc_vafp, sizeof(ff->enc_vafp));
+ ff->enc_vafp.size = ff->param.enc_fmt.det.vid.size;
+ ff->enc_vafp.buffer = NULL;
+ status = (*ff->enc_vfi->apply_fmt)(ff->enc_vfi, &ff->enc_vafp);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ /* Alloc buffers if needed */
+ ff->whole = (ff->param.packing == PJMEDIA_VID_PACKING_WHOLE);
+ if (!ff->whole) {
+ ff->enc_buf_size = ff->enc_vafp.framebytes;
+ ff->enc_buf = pj_pool_alloc(ff->pool, ff->enc_buf_size);
+
+ ff->dec_buf_size = ff->dec_vafp.framebytes;
+ ff->dec_buf = pj_pool_alloc(ff->pool, ff->dec_buf_size);
+ }
+
+ /* Update codec attributes, e.g: encoding format may be changed by
+ * SDP fmtp negotiation.
+ */
+ pj_memcpy(attr, &ff->param, sizeof(*attr));
+
+ return PJ_SUCCESS;
+
+on_error:
+ ffmpeg_codec_close(codec);
+ return status;
+}
+
+/*
+ * Close codec.
+ */
+static pj_status_t ffmpeg_codec_close( pjmedia_vid_codec *codec )
+{
+ ffmpeg_private *ff;
+ pj_mutex_t *ff_mutex;
+
+ PJ_ASSERT_RETURN(codec, PJ_EINVAL);
+ ff = (ffmpeg_private*)codec->codec_data;
+ ff_mutex = ((struct ffmpeg_factory*)codec->factory)->mutex;
+
+ pj_mutex_lock(ff_mutex);
+ if (ff->enc_ctx) {
+ avcodec_close(ff->enc_ctx);
+ av_free(ff->enc_ctx);
+ }
+ if (ff->dec_ctx && ff->dec_ctx!=ff->enc_ctx) {
+ avcodec_close(ff->dec_ctx);
+ av_free(ff->dec_ctx);
+ }
+ ff->enc_ctx = NULL;
+ ff->dec_ctx = NULL;
+ pj_mutex_unlock(ff_mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Modify codec settings.
+ */
+static pj_status_t ffmpeg_codec_modify( pjmedia_vid_codec *codec,
+ const pjmedia_vid_codec_param *attr)
+{
+ ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+
+ PJ_UNUSED_ARG(attr);
+ PJ_UNUSED_ARG(ff);
+
+ return PJ_ENOTSUP;
+}
+
+static pj_status_t ffmpeg_codec_get_param(pjmedia_vid_codec *codec,
+ pjmedia_vid_codec_param *param)
+{
+ ffmpeg_private *ff;
+
+ PJ_ASSERT_RETURN(codec && param, PJ_EINVAL);
+
+ ff = (ffmpeg_private*)codec->codec_data;
+ pj_memcpy(param, &ff->param, sizeof(*param));
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t ffmpeg_packetize ( pjmedia_vid_codec *codec,
+ pj_uint8_t *bits,
+ pj_size_t bits_len,
+ unsigned *bits_pos,
+ const pj_uint8_t **payload,
+ pj_size_t *payload_len)
+{
+ ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+
+ if (ff->desc->packetize) {
+ return (*ff->desc->packetize)(ff, bits, bits_len, bits_pos,
+ payload, payload_len);
+ }
+
+ return PJ_ENOTSUP;
+}
+
+static pj_status_t ffmpeg_unpacketize(pjmedia_vid_codec *codec,
+ const pj_uint8_t *payload,
+ pj_size_t payload_len,
+ pj_uint8_t *bits,
+ pj_size_t bits_len,
+ unsigned *bits_pos)
+{
+ ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+
+ if (ff->desc->unpacketize) {
+ return (*ff->desc->unpacketize)(ff, payload, payload_len,
+ bits, bits_len, bits_pos);
+ }
+
+ return PJ_ENOTSUP;
+}
+
+
+/*
+ * Encode frames.
+ */
+static pj_status_t ffmpeg_codec_encode_whole(pjmedia_vid_codec *codec,
+ const pjmedia_vid_encode_opt *opt,
+ const pjmedia_frame *input,
+ unsigned output_buf_len,
+ pjmedia_frame *output)
+{
+ ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+ pj_uint8_t *p = (pj_uint8_t*)input->buf;
+ AVFrame avframe;
+ AVPacket avpacket;
+ int err, got_packet;
+ //AVRational src_timebase;
+ /* For some reasons (e.g: SSE/MMX usage), the avcodec_encode_video() must
+ * have stack aligned to 16 bytes. Let's try to be safe by preparing the
+ * 16-bytes aligned stack here, in case it's not managed by the ffmpeg.
+ */
+ PJ_ALIGN_DATA(pj_uint32_t i[4], 16);
+
+ if ((long)i & 0xF) {
+ PJ_LOG(2,(THIS_FILE, "Stack alignment fails"));
+ }
+
+ /* Check if encoder has been opened */
+ PJ_ASSERT_RETURN(ff->enc_ctx, PJ_EINVALIDOP);
+
+ avcodec_get_frame_defaults(&avframe);
+
+ // Let ffmpeg manage the timestamps
+ /*
+ src_timebase.num = 1;
+ src_timebase.den = ff->desc->info.clock_rate;
+ avframe.pts = av_rescale_q(input->timestamp.u64, src_timebase,
+ ff->enc_ctx->time_base);
+ */
+
+ for (i[0] = 0; i[0] < ff->enc_vfi->plane_cnt; ++i[0]) {
+ avframe.data[i[0]] = p;
+ avframe.linesize[i[0]] = ff->enc_vafp.strides[i[0]];
+ p += ff->enc_vafp.plane_bytes[i[0]];
+ }
+
+ /* Force keyframe */
+ if (opt && opt->force_keyframe) {
+#if LIBAVCODEC_VER_AT_LEAST(53,20)
+ avframe.pict_type = AV_PICTURE_TYPE_I;
+#else
+ avframe.pict_type = FF_I_TYPE;
+#endif
+ }
+
+ av_init_packet(&avpacket);
+ avpacket.data = (pj_uint8_t*)output->buf;
+ avpacket.size = output_buf_len;
+
+#if LIBAVCODEC_VER_AT_LEAST(54,15)
+ err = avcodec_encode_video2(ff->enc_ctx, &avpacket, &avframe, &got_packet);
+ if (!err && got_packet)
+ err = avpacket.size;
+#else
+ PJ_UNUSED_ARG(got_packet);
+ err = avcodec_encode_video(ff->enc_ctx, avpacket.data, avpacket.size, &avframe);
+#endif
+
+ if (err < 0) {
+ print_ffmpeg_err(err);
+ return PJMEDIA_CODEC_EFAILED;
+ } else {
+ output->size = err;
+ output->bit_info = 0;
+ if (ff->enc_ctx->coded_frame->key_frame)
+ output->bit_info |= PJMEDIA_VID_FRM_KEYFRAME;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t ffmpeg_codec_encode_begin(pjmedia_vid_codec *codec,
+ const pjmedia_vid_encode_opt *opt,
+ const pjmedia_frame *input,
+ unsigned out_size,
+ pjmedia_frame *output,
+ pj_bool_t *has_more)
+{
+ ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+ pj_status_t status;
+
+ *has_more = PJ_FALSE;
+
+ if (ff->whole) {
+ status = ffmpeg_codec_encode_whole(codec, opt, input, out_size,
+ output);
+ } else {
+ pjmedia_frame whole_frm;
+ const pj_uint8_t *payload;
+ pj_size_t payload_len;
+
+ pj_bzero(&whole_frm, sizeof(whole_frm));
+ whole_frm.buf = ff->enc_buf;
+ whole_frm.size = ff->enc_buf_size;
+ status = ffmpeg_codec_encode_whole(codec, opt, input,
+ whole_frm.size, &whole_frm);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ ff->enc_buf_is_keyframe = (whole_frm.bit_info &
+ PJMEDIA_VID_FRM_KEYFRAME);
+ ff->enc_frame_len = (unsigned)whole_frm.size;
+ ff->enc_processed = 0;
+ status = ffmpeg_packetize(codec, (pj_uint8_t*)whole_frm.buf,
+ whole_frm.size, &ff->enc_processed,
+ &payload, &payload_len);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (out_size < payload_len)
+ return PJMEDIA_CODEC_EFRMTOOSHORT;
+
+ output->type = PJMEDIA_FRAME_TYPE_VIDEO;
+ pj_memcpy(output->buf, payload, payload_len);
+ output->size = payload_len;
+
+ if (ff->enc_buf_is_keyframe)
+ output->bit_info |= PJMEDIA_VID_FRM_KEYFRAME;
+
+ *has_more = (ff->enc_processed < ff->enc_frame_len);
+ }
+
+ return status;
+}
+
+static pj_status_t ffmpeg_codec_encode_more(pjmedia_vid_codec *codec,
+ unsigned out_size,
+ pjmedia_frame *output,
+ pj_bool_t *has_more)
+{
+ ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+ const pj_uint8_t *payload;
+ pj_size_t payload_len;
+ pj_status_t status;
+
+ *has_more = PJ_FALSE;
+
+ if (ff->enc_processed >= ff->enc_frame_len) {
+ /* No more frame */
+ return PJ_EEOF;
+ }
+
+ status = ffmpeg_packetize(codec, (pj_uint8_t*)ff->enc_buf,
+ ff->enc_frame_len, &ff->enc_processed,
+ &payload, &payload_len);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (out_size < payload_len)
+ return PJMEDIA_CODEC_EFRMTOOSHORT;
+
+ output->type = PJMEDIA_FRAME_TYPE_VIDEO;
+ pj_memcpy(output->buf, payload, payload_len);
+ output->size = payload_len;
+
+ if (ff->enc_buf_is_keyframe)
+ output->bit_info |= PJMEDIA_VID_FRM_KEYFRAME;
+
+ *has_more = (ff->enc_processed < ff->enc_frame_len);
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t check_decode_result(pjmedia_vid_codec *codec,
+ const pj_timestamp *ts,
+ pj_bool_t got_keyframe)
+{
+ ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+ pjmedia_video_apply_fmt_param *vafp = &ff->dec_vafp;
+ pjmedia_event event;
+
+ /* Check for format change.
+ * Decoder output format is set by libavcodec, in case it is different
+ * to the configured param.
+ */
+ if (ff->dec_ctx->pix_fmt != ff->expected_dec_fmt ||
+ ff->dec_ctx->width != (int)vafp->size.w ||
+ ff->dec_ctx->height != (int)vafp->size.h)
+ {
+ pjmedia_format_id new_fmt_id;
+ pj_status_t status;
+
+ /* Get current raw format id from ffmpeg decoder context */
+ status = PixelFormat_to_pjmedia_format_id(ff->dec_ctx->pix_fmt,
+ &new_fmt_id);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Update decoder format in param */
+ ff->param.dec_fmt.id = new_fmt_id;
+ ff->param.dec_fmt.det.vid.size.w = ff->dec_ctx->width;
+ ff->param.dec_fmt.det.vid.size.h = ff->dec_ctx->height;
+ ff->expected_dec_fmt = ff->dec_ctx->pix_fmt;
+
+ /* Re-init format info and apply-param of decoder */
+ ff->dec_vfi = pjmedia_get_video_format_info(NULL, ff->param.dec_fmt.id);
+ if (!ff->dec_vfi)
+ return PJ_ENOTSUP;
+ pj_bzero(&ff->dec_vafp, sizeof(ff->dec_vafp));
+ ff->dec_vafp.size = ff->param.dec_fmt.det.vid.size;
+ ff->dec_vafp.buffer = NULL;
+ status = (*ff->dec_vfi->apply_fmt)(ff->dec_vfi, &ff->dec_vafp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Realloc buffer if necessary */
+ if (ff->dec_vafp.framebytes > ff->dec_buf_size) {
+ PJ_LOG(5,(THIS_FILE, "Reallocating decoding buffer %u --> %u",
+ (unsigned)ff->dec_buf_size,
+ (unsigned)ff->dec_vafp.framebytes));
+ ff->dec_buf_size = ff->dec_vafp.framebytes;
+ ff->dec_buf = pj_pool_alloc(ff->pool, ff->dec_buf_size);
+ }
+
+ /* Broadcast format changed event */
+ pjmedia_event_init(&event, PJMEDIA_EVENT_FMT_CHANGED, ts, codec);
+ event.data.fmt_changed.dir = PJMEDIA_DIR_DECODING;
+ pj_memcpy(&event.data.fmt_changed.new_fmt, &ff->param.dec_fmt,
+ sizeof(ff->param.dec_fmt));
+ pjmedia_event_publish(NULL, codec, &event, 0);
+ }
+
+ /* Check for missing/found keyframe */
+ if (got_keyframe) {
+ pj_get_timestamp(&ff->last_dec_keyframe_ts);
+
+ /* Broadcast keyframe event */
+ pjmedia_event_init(&event, PJMEDIA_EVENT_KEYFRAME_FOUND, ts, codec);
+ pjmedia_event_publish(NULL, codec, &event, 0);
+ } else if (ff->last_dec_keyframe_ts.u64 == 0) {
+ /* Broadcast missing keyframe event */
+ pjmedia_event_init(&event, PJMEDIA_EVENT_KEYFRAME_MISSING, ts, codec);
+ pjmedia_event_publish(NULL, codec, &event, 0);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Decode frame.
+ */
+static pj_status_t ffmpeg_codec_decode_whole(pjmedia_vid_codec *codec,
+ const pjmedia_frame *input,
+ unsigned output_buf_len,
+ pjmedia_frame *output)
+{
+ ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+ AVFrame avframe;
+ AVPacket avpacket;
+ int err, got_picture;
+
+ /* Check if decoder has been opened */
+ PJ_ASSERT_RETURN(ff->dec_ctx, PJ_EINVALIDOP);
+
+ /* Reset output frame bit info */
+ output->bit_info = 0;
+
+ /* Validate output buffer size */
+ // Do this validation later after getting decoding result, where the real
+ // decoded size will be assured.
+ //if (ff->dec_vafp.framebytes > output_buf_len)
+ //return PJ_ETOOSMALL;
+
+ /* Init frame to receive the decoded data, the ffmpeg codec context will
+ * automatically provide the decoded buffer (single buffer used for the
+ * whole decoding session, and seems to be freed when the codec context
+ * closed).
+ */
+ avcodec_get_frame_defaults(&avframe);
+
+ /* Init packet, the container of the encoded data */
+ av_init_packet(&avpacket);
+ avpacket.data = (pj_uint8_t*)input->buf;
+ avpacket.size = input->size;
+
+ /* ffmpeg warns:
+ * - input buffer padding, at least FF_INPUT_BUFFER_PADDING_SIZE
+ * - null terminated
+ * Normally, encoded buffer is allocated more than needed, so lets just
+ * bzero the input buffer end/pad, hope it will be just fine.
+ */
+ pj_bzero(avpacket.data+avpacket.size, FF_INPUT_BUFFER_PADDING_SIZE);
+
+ output->bit_info = 0;
+ output->timestamp = input->timestamp;
+
+#if LIBAVCODEC_VER_AT_LEAST(52,72)
+ //avpacket.flags = AV_PKT_FLAG_KEY;
+#else
+ avpacket.flags = 0;
+#endif
+
+#if LIBAVCODEC_VER_AT_LEAST(52,72)
+ err = avcodec_decode_video2(ff->dec_ctx, &avframe,
+ &got_picture, &avpacket);
+#else
+ err = avcodec_decode_video(ff->dec_ctx, &avframe,
+ &got_picture, avpacket.data, avpacket.size);
+#endif
+ if (err < 0) {
+ pjmedia_event event;
+
+ output->type = PJMEDIA_FRAME_TYPE_NONE;
+ output->size = 0;
+ print_ffmpeg_err(err);
+
+ /* Broadcast missing keyframe event */
+ pjmedia_event_init(&event, PJMEDIA_EVENT_KEYFRAME_MISSING,
+ &input->timestamp, codec);
+ pjmedia_event_publish(NULL, codec, &event, 0);
+
+ return PJMEDIA_CODEC_EBADBITSTREAM;
+ } else if (got_picture) {
+ pjmedia_video_apply_fmt_param *vafp = &ff->dec_vafp;
+ pj_uint8_t *q = (pj_uint8_t*)output->buf;
+ unsigned i;
+ pj_status_t status;
+
+ /* Check decoding result, e.g: see if the format got changed,
+ * keyframe found/missing.
+ */
+ status = check_decode_result(codec, &input->timestamp,
+ avframe.key_frame);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Check provided buffer size */
+ if (vafp->framebytes > output_buf_len)
+ return PJ_ETOOSMALL;
+
+ /* Get the decoded data */
+ for (i = 0; i < ff->dec_vfi->plane_cnt; ++i) {
+ pj_uint8_t *p = avframe.data[i];
+
+ /* The decoded data may contain padding */
+ if (avframe.linesize[i]!=vafp->strides[i]) {
+ /* Padding exists, copy line by line */
+ pj_uint8_t *q_end;
+
+ q_end = q+vafp->plane_bytes[i];
+ while(q < q_end) {
+ pj_memcpy(q, p, vafp->strides[i]);
+ q += vafp->strides[i];
+ p += avframe.linesize[i];
+ }
+ } else {
+ /* No padding, copy the whole plane */
+ pj_memcpy(q, p, vafp->plane_bytes[i]);
+ q += vafp->plane_bytes[i];
+ }
+ }
+
+ output->type = PJMEDIA_FRAME_TYPE_VIDEO;
+ output->size = vafp->framebytes;
+ } else {
+ output->type = PJMEDIA_FRAME_TYPE_NONE;
+ output->size = 0;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t ffmpeg_codec_decode( pjmedia_vid_codec *codec,
+ pj_size_t pkt_count,
+ pjmedia_frame packets[],
+ unsigned out_size,
+ pjmedia_frame *output)
+{
+ ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(codec && pkt_count > 0 && packets && output,
+ PJ_EINVAL);
+
+ if (ff->whole) {
+ pj_assert(pkt_count==1);
+ return ffmpeg_codec_decode_whole(codec, &packets[0], out_size, output);
+ } else {
+ pjmedia_frame whole_frm;
+ unsigned whole_len = 0;
+ unsigned i;
+
+ for (i=0; i<pkt_count; ++i) {
+ if (whole_len + packets[i].size > ff->dec_buf_size) {
+ PJ_LOG(5,(THIS_FILE, "Decoding buffer overflow"));
+ break;
+ }
+
+ status = ffmpeg_unpacketize(codec, packets[i].buf, packets[i].size,
+ ff->dec_buf, ff->dec_buf_size,
+ &whole_len);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(5,(THIS_FILE, status, "Unpacketize error"));
+ continue;
+ }
+ }
+
+ whole_frm.buf = ff->dec_buf;
+ whole_frm.size = whole_len;
+ whole_frm.timestamp = output->timestamp = packets[i].timestamp;
+ whole_frm.bit_info = 0;
+
+ return ffmpeg_codec_decode_whole(codec, &whole_frm, out_size, output);
+ }
+}
+
+
+#ifdef _MSC_VER
+# pragma comment( lib, "avcodec.lib")
+#endif
+
+#endif /* PJMEDIA_HAS_FFMPEG_VID_CODEC */
+
diff --git a/pjmedia/src/pjmedia-codec/g722.c b/pjmedia/src/pjmedia-codec/g722.c
new file mode 100644
index 0000000..7e0e4e4
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/g722.c
@@ -0,0 +1,714 @@
+/* $Id: g722.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia-codec/g722.h>
+#include <pjmedia/codec.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/endpoint.h>
+#include <pjmedia/plc.h>
+#include <pjmedia/port.h>
+#include <pjmedia/silencedet.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/os.h>
+
+#if defined(PJMEDIA_HAS_G722_CODEC) && (PJMEDIA_HAS_G722_CODEC != 0)
+
+#include "g722/g722_enc.h"
+#include "g722/g722_dec.h"
+
+#define THIS_FILE "g722.c"
+
+/* Defines */
+#define PTIME (10)
+#define SAMPLES_PER_FRAME (16000 * PTIME /1000)
+#define FRAME_LEN (80)
+#define PLC_DISABLED 0
+
+/* Tracing */
+#ifndef PJ_TRACE
+# define PJ_TRACE 0
+#endif
+
+#if PJ_TRACE
+# define TRACE_(expr) PJ_LOG(4,expr)
+#else
+# define TRACE_(expr)
+#endif
+
+
+/* Prototypes for G722 factory */
+static pj_status_t g722_test_alloc(pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id );
+static pj_status_t g722_default_attr(pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr );
+static pj_status_t g722_enum_codecs(pjmedia_codec_factory *factory,
+ unsigned *count,
+ pjmedia_codec_info codecs[]);
+static pj_status_t g722_alloc_codec(pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec);
+static pj_status_t g722_dealloc_codec(pjmedia_codec_factory *factory,
+ pjmedia_codec *codec );
+
+/* Prototypes for G722 implementation. */
+static pj_status_t g722_codec_init(pjmedia_codec *codec,
+ pj_pool_t *pool );
+static pj_status_t g722_codec_open(pjmedia_codec *codec,
+ pjmedia_codec_param *attr );
+static pj_status_t g722_codec_close(pjmedia_codec *codec );
+static pj_status_t g722_codec_modify(pjmedia_codec *codec,
+ const pjmedia_codec_param *attr );
+static pj_status_t g722_codec_parse(pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[]);
+static pj_status_t g722_codec_encode(pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+static pj_status_t g722_codec_decode(pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+#if !PLC_DISABLED
+static pj_status_t g722_codec_recover(pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+#endif
+
+/* Definition for G722 codec operations. */
+static pjmedia_codec_op g722_op =
+{
+ &g722_codec_init,
+ &g722_codec_open,
+ &g722_codec_close,
+ &g722_codec_modify,
+ &g722_codec_parse,
+ &g722_codec_encode,
+ &g722_codec_decode,
+#if !PLC_DISABLED
+ &g722_codec_recover
+#else
+ NULL
+#endif
+};
+
+/* Definition for G722 codec factory operations. */
+static pjmedia_codec_factory_op g722_factory_op =
+{
+ &g722_test_alloc,
+ &g722_default_attr,
+ &g722_enum_codecs,
+ &g722_alloc_codec,
+ &g722_dealloc_codec,
+ &pjmedia_codec_g722_deinit
+};
+
+/* G722 factory */
+static struct g722_codec_factory
+{
+ pjmedia_codec_factory base;
+ pjmedia_endpt *endpt;
+ pj_pool_t *pool;
+ pj_mutex_t *mutex;
+ pjmedia_codec codec_list;
+ unsigned pcm_shift;
+} g722_codec_factory;
+
+
+/* G722 codec private data. */
+struct g722_data
+{
+ g722_enc_t encoder;
+ g722_dec_t decoder;
+ unsigned pcm_shift;
+ pj_int16_t pcm_clip_mask;
+ pj_bool_t plc_enabled;
+ pj_bool_t vad_enabled;
+ pjmedia_silence_det *vad;
+ pj_timestamp last_tx;
+#if !PLC_DISABLED
+ pjmedia_plc *plc;
+#endif
+};
+
+
+
+/*
+ * Initialize and register G722 codec factory to pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_g722_init( pjmedia_endpt *endpt )
+{
+ pjmedia_codec_mgr *codec_mgr;
+ pj_status_t status;
+
+ if (g722_codec_factory.pool != NULL)
+ return PJ_SUCCESS;
+
+ /* Create G722 codec factory. */
+ g722_codec_factory.base.op = &g722_factory_op;
+ g722_codec_factory.base.factory_data = NULL;
+ g722_codec_factory.endpt = endpt;
+ g722_codec_factory.pcm_shift = PJMEDIA_G722_DEFAULT_PCM_SHIFT;
+
+ g722_codec_factory.pool = pjmedia_endpt_create_pool(endpt, "g722", 1000,
+ 1000);
+ if (!g722_codec_factory.pool)
+ return PJ_ENOMEM;
+
+ pj_list_init(&g722_codec_factory.codec_list);
+
+ /* Create mutex. */
+ status = pj_mutex_create_simple(g722_codec_factory.pool, "g722",
+ &g722_codec_factory.mutex);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
+ if (!codec_mgr) {
+ status = PJ_EINVALIDOP;
+ goto on_error;
+ }
+
+ /* Register codec factory to endpoint. */
+ status = pjmedia_codec_mgr_register_factory(codec_mgr,
+ &g722_codec_factory.base);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ TRACE_((THIS_FILE, "G722 codec factory initialized"));
+
+ /* Done. */
+ return PJ_SUCCESS;
+
+on_error:
+ pj_pool_release(g722_codec_factory.pool);
+ g722_codec_factory.pool = NULL;
+ return status;
+}
+
+/*
+ * Unregister G722 codec factory from pjmedia endpoint and deinitialize
+ * the G722 codec library.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_g722_deinit(void)
+{
+ pjmedia_codec_mgr *codec_mgr;
+ pj_status_t status;
+
+ if (g722_codec_factory.pool == NULL)
+ return PJ_SUCCESS;
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(g722_codec_factory.endpt);
+ if (!codec_mgr) {
+ pj_pool_release(g722_codec_factory.pool);
+ g722_codec_factory.pool = NULL;
+ return PJ_EINVALIDOP;
+ }
+
+ /* Unregister G722 codec factory. */
+ status = pjmedia_codec_mgr_unregister_factory(codec_mgr,
+ &g722_codec_factory.base);
+
+ /* Destroy mutex. */
+ pj_mutex_destroy(g722_codec_factory.mutex);
+
+ /* Destroy pool. */
+ pj_pool_release(g722_codec_factory.pool);
+ g722_codec_factory.pool = NULL;
+
+ TRACE_((THIS_FILE, "G722 codec factory shutdown"));
+ return status;
+}
+
+
+/*
+ * Set level adjustment.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_g722_set_pcm_shift(unsigned val)
+{
+ g722_codec_factory.pcm_shift = val;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Check if factory can allocate the specified codec.
+ */
+static pj_status_t g722_test_alloc(pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *info )
+{
+ PJ_UNUSED_ARG(factory);
+
+ /* Check payload type. */
+ if (info->pt != PJMEDIA_RTP_PT_G722)
+ return PJMEDIA_CODEC_EUNSUP;
+
+ /* Ignore the rest, since it's static payload type. */
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Generate default attribute.
+ */
+static pj_status_t g722_default_attr( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr )
+{
+ PJ_UNUSED_ARG(factory);
+ PJ_UNUSED_ARG(id);
+
+ pj_bzero(attr, sizeof(pjmedia_codec_param));
+ attr->info.clock_rate = 16000;
+ attr->info.channel_cnt = 1;
+ attr->info.avg_bps = 64000;
+ attr->info.max_bps = 64000;
+ attr->info.pcm_bits_per_sample = 16;
+ attr->info.frm_ptime = PTIME;
+ attr->info.pt = PJMEDIA_RTP_PT_G722;
+
+ attr->setting.frm_per_pkt = 2;
+ attr->setting.vad = 1;
+ attr->setting.plc = 1;
+
+ /* Default all other flag bits disabled. */
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Enum codecs supported by this factory (i.e. only G722!).
+ */
+static pj_status_t g722_enum_codecs(pjmedia_codec_factory *factory,
+ unsigned *count,
+ pjmedia_codec_info codecs[])
+{
+ PJ_UNUSED_ARG(factory);
+ PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL);
+
+ pj_bzero(&codecs[0], sizeof(pjmedia_codec_info));
+ codecs[0].encoding_name = pj_str("G722");
+ codecs[0].pt = PJMEDIA_RTP_PT_G722;
+ codecs[0].type = PJMEDIA_TYPE_AUDIO;
+ codecs[0].clock_rate = 16000;
+ codecs[0].channel_cnt = 1;
+
+ *count = 1;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Allocate a new G722 codec instance.
+ */
+static pj_status_t g722_alloc_codec(pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec)
+{
+ pjmedia_codec *codec;
+ struct g722_data *g722_data;
+
+ PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory == &g722_codec_factory.base, PJ_EINVAL);
+
+ pj_mutex_lock(g722_codec_factory.mutex);
+
+ /* Get free nodes, if any. */
+ if (!pj_list_empty(&g722_codec_factory.codec_list)) {
+ codec = g722_codec_factory.codec_list.next;
+ pj_list_erase(codec);
+ } else {
+ pj_status_t status;
+
+ codec = PJ_POOL_ZALLOC_T(g722_codec_factory.pool, pjmedia_codec);
+ PJ_ASSERT_RETURN(codec != NULL, PJ_ENOMEM);
+ codec->op = &g722_op;
+ codec->factory = factory;
+
+ g722_data = PJ_POOL_ZALLOC_T(g722_codec_factory.pool, struct g722_data);
+ codec->codec_data = g722_data;
+
+#if !PLC_DISABLED
+ /* Create PLC */
+ status = pjmedia_plc_create(g722_codec_factory.pool, 16000,
+ SAMPLES_PER_FRAME, 0, &g722_data->plc);
+ if (status != PJ_SUCCESS) {
+ pj_mutex_unlock(g722_codec_factory.mutex);
+ return status;
+ }
+#endif
+
+ /* Create silence detector */
+ status = pjmedia_silence_det_create(g722_codec_factory.pool,
+ 16000, SAMPLES_PER_FRAME,
+ &g722_data->vad);
+ if (status != PJ_SUCCESS) {
+ pj_mutex_unlock(g722_codec_factory.mutex);
+ TRACE_((THIS_FILE, "Create silence detector failed (status = %d)",
+ status));
+ return status;
+ }
+ }
+
+
+ pj_mutex_unlock(g722_codec_factory.mutex);
+
+ *p_codec = codec;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Free codec.
+ */
+static pj_status_t g722_dealloc_codec(pjmedia_codec_factory *factory,
+ pjmedia_codec *codec )
+{
+ struct g722_data *g722_data;
+ int i;
+
+ PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory == &g722_codec_factory.base, PJ_EINVAL);
+
+ g722_data = (struct g722_data*) codec->codec_data;
+
+ /* Close codec, if it's not closed. */
+ g722_codec_close(codec);
+
+#if !PLC_DISABLED
+ /* Clear left samples in the PLC, since codec+plc will be reused
+ * next time.
+ */
+ for (i=0; i<2; ++i) {
+ pj_int16_t frame[SAMPLES_PER_FRAME];
+ pjmedia_zero_samples(frame, PJ_ARRAY_SIZE(frame));
+ pjmedia_plc_save(g722_data->plc, frame);
+ }
+#else
+ PJ_UNUSED_ARG(i);
+#endif
+
+ /* Re-init silence_period */
+ pj_set_timestamp32(&g722_data->last_tx, 0, 0);
+
+ /* Put in the free list. */
+ pj_mutex_lock(g722_codec_factory.mutex);
+ pj_list_push_front(&g722_codec_factory.codec_list, codec);
+ pj_mutex_unlock(g722_codec_factory.mutex);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Init codec.
+ */
+static pj_status_t g722_codec_init(pjmedia_codec *codec,
+ pj_pool_t *pool )
+{
+ PJ_UNUSED_ARG(codec);
+ PJ_UNUSED_ARG(pool);
+ return PJ_SUCCESS;
+}
+
+/*
+ * Open codec.
+ */
+static pj_status_t g722_codec_open(pjmedia_codec *codec,
+ pjmedia_codec_param *attr )
+{
+ struct g722_data *g722_data = (struct g722_data*) codec->codec_data;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(codec && attr, PJ_EINVAL);
+ PJ_ASSERT_RETURN(g722_data != NULL, PJ_EINVALIDOP);
+
+ status = g722_enc_init(&g722_data->encoder);
+ if (status != PJ_SUCCESS) {
+ TRACE_((THIS_FILE, "g722_enc_init() failed, status=%d", status));
+ pj_mutex_unlock(g722_codec_factory.mutex);
+ return PJMEDIA_CODEC_EFAILED;
+ }
+
+ status = g722_dec_init(&g722_data->decoder);
+ if (status != PJ_SUCCESS) {
+ TRACE_((THIS_FILE, "g722_dec_init() failed, status=%d", status));
+ pj_mutex_unlock(g722_codec_factory.mutex);
+ return PJMEDIA_CODEC_EFAILED;
+ }
+
+ g722_data->vad_enabled = (attr->setting.vad != 0);
+ g722_data->plc_enabled = (attr->setting.plc != 0);
+ g722_data->pcm_shift = g722_codec_factory.pcm_shift;
+ g722_data->pcm_clip_mask = (pj_int16_t)(1<<g722_codec_factory.pcm_shift)-1;
+ g722_data->pcm_clip_mask <<= (16-g722_codec_factory.pcm_shift);
+
+ TRACE_((THIS_FILE, "G722 codec opened: vad=%d, plc=%d",
+ g722_data->vad_enabled, g722_data->plc_enabled));
+ return PJ_SUCCESS;
+}
+
+/*
+ * Close codec.
+ */
+static pj_status_t g722_codec_close( pjmedia_codec *codec )
+{
+ /* The codec, encoder, and decoder will be reused, so there's
+ * nothing to do here
+ */
+
+ PJ_UNUSED_ARG(codec);
+
+ TRACE_((THIS_FILE, "G722 codec closed"));
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Modify codec settings.
+ */
+static pj_status_t g722_codec_modify(pjmedia_codec *codec,
+ const pjmedia_codec_param *attr )
+{
+ struct g722_data *g722_data = (struct g722_data*) codec->codec_data;
+
+ pj_assert(g722_data != NULL);
+
+ g722_data->vad_enabled = (attr->setting.vad != 0);
+ g722_data->plc_enabled = (attr->setting.plc != 0);
+
+ TRACE_((THIS_FILE, "G722 codec modified: vad=%d, plc=%d",
+ g722_data->vad_enabled, g722_data->plc_enabled));
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get frames in the packet.
+ */
+static pj_status_t g722_codec_parse(pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[])
+{
+ unsigned count = 0;
+
+ PJ_UNUSED_ARG(codec);
+
+ PJ_ASSERT_RETURN(frame_cnt, PJ_EINVAL);
+
+ TRACE_((THIS_FILE, "G722 parse(): input len=%d", pkt_size));
+
+ while (pkt_size >= FRAME_LEN && count < *frame_cnt) {
+ frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frames[count].buf = pkt;
+ frames[count].size = FRAME_LEN;
+ frames[count].timestamp.u64 = ts->u64 + count * SAMPLES_PER_FRAME;
+
+ pkt = ((char*)pkt) + FRAME_LEN;
+ pkt_size -= FRAME_LEN;
+
+ ++count;
+ }
+
+ TRACE_((THIS_FILE, "G722 parse(): got %d frames", count));
+
+ *frame_cnt = count;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Encode frame.
+ */
+static pj_status_t g722_codec_encode(pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ struct g722_data *g722_data = (struct g722_data*) codec->codec_data;
+ pj_status_t status;
+
+ pj_assert(g722_data && input && output);
+
+ PJ_ASSERT_RETURN((input->size >> 2) <= output_buf_len,
+ PJMEDIA_CODEC_EFRMTOOSHORT);
+
+ /* Detect silence */
+ if (g722_data->vad_enabled) {
+ pj_bool_t is_silence;
+ pj_int32_t silence_duration;
+
+ silence_duration = pj_timestamp_diff32(&g722_data->last_tx,
+ &input->timestamp);
+
+ is_silence = pjmedia_silence_det_detect(g722_data->vad,
+ (const pj_int16_t*) input->buf,
+ (input->size >> 1),
+ NULL);
+ if (is_silence &&
+ (PJMEDIA_CODEC_MAX_SILENCE_PERIOD == -1 ||
+ silence_duration < PJMEDIA_CODEC_MAX_SILENCE_PERIOD*16000/1000))
+ {
+ output->type = PJMEDIA_FRAME_TYPE_NONE;
+ output->buf = NULL;
+ output->size = 0;
+ output->timestamp = input->timestamp;
+ return PJ_SUCCESS;
+ } else {
+ g722_data->last_tx = input->timestamp;
+ }
+ }
+
+ /* Adjust input signal level from 16-bit to 14-bit */
+ if (g722_data->pcm_shift) {
+ pj_int16_t *p, *end;
+
+ p = (pj_int16_t*)input->buf;
+ end = p + input->size/2;
+ while (p < end) {
+ *p++ >>= g722_data->pcm_shift;
+ }
+ }
+
+ /* Encode to temporary buffer */
+ output->size = output_buf_len;
+ status = g722_enc_encode(&g722_data->encoder, (pj_int16_t*)input->buf,
+ (input->size >> 1), output->buf, &output->size);
+ if (status != PJ_SUCCESS) {
+ output->size = 0;
+ output->buf = NULL;
+ output->type = PJMEDIA_FRAME_TYPE_NONE;
+ TRACE_((THIS_FILE, "G722 encode() status: %d", status));
+ return PJMEDIA_CODEC_EFAILED;
+ }
+
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->timestamp = input->timestamp;
+
+ TRACE_((THIS_FILE, "G722 encode(): size=%d", output->size));
+ return PJ_SUCCESS;
+}
+
+/*
+ * Decode frame.
+ */
+static pj_status_t g722_codec_decode(pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ struct g722_data *g722_data = (struct g722_data*) codec->codec_data;
+ pj_status_t status;
+
+ pj_assert(g722_data != NULL);
+ PJ_ASSERT_RETURN(input && output, PJ_EINVAL);
+
+ TRACE_((THIS_FILE, "G722 decode(): inbuf=%p, insize=%d, outbuf=%p,"
+ "outsize=%d",
+ input->buf, input->size, output->buf, output_buf_len));
+
+ if (output_buf_len < SAMPLES_PER_FRAME * 2) {
+ TRACE_((THIS_FILE, "G722 decode() ERROR: PJMEDIA_CODEC_EPCMTOOSHORT"));
+ return PJMEDIA_CODEC_EPCMTOOSHORT;
+ }
+
+ if (input->size != FRAME_LEN) {
+ TRACE_((THIS_FILE, "G722 decode() ERROR: PJMEDIA_CODEC_EFRMTOOSHORT"));
+ return PJMEDIA_CODEC_EFRMTOOSHORT;
+ }
+
+
+ /* Decode */
+ output->size = SAMPLES_PER_FRAME;
+ status = g722_dec_decode(&g722_data->decoder, input->buf, input->size,
+ (pj_int16_t*)output->buf, &output->size);
+ if (status != PJ_SUCCESS) {
+ TRACE_((THIS_FILE, "G722 decode() status: %d", status));
+ return PJMEDIA_CODEC_EFAILED;
+ }
+
+ pj_assert(output->size == SAMPLES_PER_FRAME);
+
+ /* Adjust input signal level from 14-bit to 16-bit */
+ if (g722_data->pcm_shift) {
+ pj_int16_t *p, *end;
+
+ p = (pj_int16_t*)output->buf;
+ end = p + output->size;
+ while (p < end) {
+#if PJMEDIA_G722_STOP_PCM_SHIFT_ON_CLIPPING
+ /* If there is clipping, stop the PCM shifting */
+ if (*p & g722_data->pcm_clip_mask) {
+ g722_data->pcm_shift = 0;
+ break;
+ }
+#endif
+ *p++ <<= g722_data->pcm_shift;
+ }
+ }
+
+ output->size = SAMPLES_PER_FRAME * 2;
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->timestamp = input->timestamp;
+
+#if !PLC_DISABLED
+ if (g722_data->plc_enabled)
+ pjmedia_plc_save(g722_data->plc, (pj_int16_t*)output->buf);
+#endif
+
+ TRACE_((THIS_FILE, "G722 decode done"));
+ return PJ_SUCCESS;
+}
+
+
+#if !PLC_DISABLED
+/*
+ * Recover lost frame.
+ */
+static pj_status_t g722_codec_recover(pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ struct g722_data *g722_data = (struct g722_data*)codec->codec_data;
+
+ PJ_ASSERT_RETURN(g722_data->plc_enabled, PJ_EINVALIDOP);
+
+ PJ_ASSERT_RETURN(output_buf_len >= SAMPLES_PER_FRAME * 2,
+ PJMEDIA_CODEC_EPCMTOOSHORT);
+
+ pjmedia_plc_generate(g722_data->plc, (pj_int16_t*)output->buf);
+
+ output->size = SAMPLES_PER_FRAME * 2;
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+
+ return PJ_SUCCESS;
+}
+#endif
+
+#endif // PJMEDIA_HAS_G722_CODEC
+
diff --git a/pjmedia/src/pjmedia-codec/g722/g722_dec.c b/pjmedia/src/pjmedia-codec/g722/g722_dec.c
new file mode 100644
index 0000000..c6edc36
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/g722/g722_dec.c
@@ -0,0 +1,549 @@
+/* $Id: g722_dec.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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
+ */
+/*
+ * Based on implementation found in Carnegie Mellon Speech Group Software
+ * depository (ftp://ftp.cs.cmu.edu/project/fgdata/index.html). No copyright
+ * was claimed in the original source codes.
+ */
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/pool.h>
+
+#include "g722_dec.h"
+
+#if defined(PJMEDIA_HAS_G722_CODEC) && (PJMEDIA_HAS_G722_CODEC != 0)
+
+#define MODE 1
+
+#define SATURATE(v, max, min) \
+ if (v>max) v = max; \
+ else if (v<min) v = min
+
+extern const int g722_qmf_coeff[24];
+
+static const int qm4[16] =
+{
+ 0, -20456, -12896, -8968,
+ -6288, -4240, -2584, -1200,
+ 20456, 12896, 8968, 6288,
+ 4240, 2584, 1200, 0
+};
+static const int ilb[32] = {
+ 2048, 2093, 2139, 2186, 2233, 2282, 2332,
+ 2383, 2435, 2489, 2543, 2599, 2656, 2714,
+ 2774, 2834, 2896, 2960, 3025, 3091, 3158,
+ 3228, 3298, 3371, 3444, 3520, 3597, 3676,
+ 3756, 3838, 3922, 4008
+};
+
+
+static int block2l (int il, int detl)
+{
+ int dlt ;
+ int ril, wd2 ;
+
+ /* INVQAL */
+ ril = il >> 2 ;
+ wd2 = qm4[ril] ;
+ dlt = (detl * wd2) >> 15 ;
+
+ return (dlt) ;
+}
+
+
+static int block3l (g722_dec_t *dec, int il)
+{
+ int detl ;
+ int ril, il4, wd, wd1, wd2, wd3, nbpl, depl ;
+ static const int wl[8] = {
+ -60, -30, 58, 172, 334, 538, 1198, 3042
+ };
+ static const int rl42[16] = {
+ 0, 7, 6, 5, 4, 3, 2, 1, 7, 6, 5, 4, 3, 2, 1, 0
+ };
+
+ /* LOGSCL */
+ ril = il >> 2 ;
+ il4 = rl42[ril] ;
+ wd = (dec->nbl * 32512) >> 15 ;
+ nbpl = wd + wl[il4] ;
+
+ if (nbpl < 0) nbpl = 0 ;
+ if (nbpl > 18432) nbpl = 18432 ;
+
+ /* SCALEL */
+ wd1 = (nbpl >> 6) & 31 ;
+ wd2 = nbpl >> 11 ;
+ if ((8 - wd2) < 0) wd3 = ilb[wd1] << (wd2 - 8) ;
+ else wd3 = ilb[wd1] >> (8 - wd2) ;
+ depl = wd3 << 2 ;
+
+ /* DELAYA */
+ dec->nbl = nbpl ;
+ /* DELAYL */
+ detl = depl ;
+
+ return (detl) ;
+}
+
+
+static int block4l (g722_dec_t *dec, int dl)
+{
+ int sl = dec->slow ;
+ int i ;
+ int wd, wd1, wd2, wd3, wd4, wd5/*, wd6 */;
+
+ dec->dlt[0] = dl;
+
+ /* RECONS */
+ dec->rlt[0] = sl + dec->dlt[0] ;
+ SATURATE(dec->rlt[0], 32767, -32768);
+
+ /* PARREC */
+ dec->plt[0] = dec->dlt[0] + dec->szl ;
+ SATURATE(dec->plt[0], 32767, -32768);
+
+ /* UPPOL2 */
+ dec->sgl[0] = dec->plt[0] >> 15 ;
+ dec->sgl[1] = dec->plt[1] >> 15 ;
+ dec->sgl[2] = dec->plt[2] >> 15 ;
+
+ wd1 = dec->al[1] << 2;
+ SATURATE(wd1, 32767, -32768);
+
+ if ( dec->sgl[0] == dec->sgl[1] ) wd2 = - wd1 ;
+ else wd2 = wd1 ;
+ if (wd2 > 32767) wd2 = 32767;
+ wd2 = wd2 >> 7 ;
+
+ if ( dec->sgl[0] == dec->sgl[2] ) wd3 = 128 ;
+ else wd3 = - 128 ;
+
+ wd4 = wd2 + wd3 ;
+ wd5 = (dec->al[2] * 32512) >> 15 ;
+
+ dec->apl[2] = wd4 + wd5 ;
+ SATURATE(dec->apl[2], 12288, -12288);
+
+ /* UPPOL1 */
+ dec->sgl[0] = dec->plt[0] >> 15 ;
+ dec->sgl[1] = dec->plt[1] >> 15 ;
+
+ if ( dec->sgl[0] == dec->sgl[1] ) wd1 = 192 ;
+ else wd1 = - 192 ;
+
+ wd2 = (dec->al[1] * 32640) >> 15 ;
+
+ dec->apl[1] = wd1 + wd2 ;
+ SATURATE(dec->apl[1], 32767, -32768);
+
+ wd3 = (15360 - dec->apl[2]) ;
+ SATURATE(wd3, 32767, -32768);
+ if ( dec->apl[1] > wd3) dec->apl[1] = wd3 ;
+ if ( dec->apl[1] < -wd3) dec->apl[1] = -wd3 ;
+
+ /* UPZERO */
+ if ( dec->dlt[0] == 0 ) wd1 = 0 ;
+ else wd1 = 128 ;
+
+ dec->sgl[0] = dec->dlt[0] >> 15 ;
+
+ for ( i = 1; i < 7; i++ ) {
+ dec->sgl[i] = dec->dlt[i] >> 15 ;
+ if ( dec->sgl[i] == dec->sgl[0] ) wd2 = wd1 ;
+ else wd2 = - wd1 ;
+ wd3 = (dec->bl[i] * 32640) >> 15 ;
+ dec->bpl[i] = wd2 + wd3 ;
+ SATURATE(dec->bpl[i], 32767, -32768);
+ }
+
+ /* DELAYA */
+ for ( i = 6; i > 0; i-- ) {
+ dec->dlt[i] = dec->dlt[i-1] ;
+ dec->bl[i] = dec->bpl[i] ;
+ }
+
+ for ( i = 2; i > 0; i-- ) {
+ dec->rlt[i] = dec->rlt[i-1] ;
+ dec->plt[i] = dec->plt[i-1] ;
+ dec->al[i] = dec->apl[i] ;
+ }
+
+ /* FILTEP */
+ wd1 = dec->rlt[1] << 1;
+ SATURATE(wd1, 32767, -32768);
+ wd1 = ( dec->al[1] * wd1 ) >> 15 ;
+
+ wd2 = dec->rlt[2] << 1;
+ SATURATE(wd2, 32767, -32768);
+ wd2 = ( dec->al[2] * wd2 ) >> 15 ;
+
+ dec->spl = wd1 + wd2 ;
+ SATURATE(dec->spl, 32767, -32768);
+
+ /* FILTEZ */
+ dec->szl = 0 ;
+ for (i=6; i>0; i--) {
+ wd = dec->dlt[i] << 1;
+ SATURATE(wd, 32767, -32768);
+ dec->szl += (dec->bl[i] * wd) >> 15 ;
+ SATURATE(dec->szl, 32767, -32768);
+ }
+
+ /* PREDIC */
+ sl = dec->spl + dec->szl ;
+ SATURATE(sl, 32767, -32768);
+
+ return (sl) ;
+}
+
+static int block5l (int ilr, int sl, int detl, int mode)
+{
+ int yl ;
+ int ril, dl, wd2 = 0;
+ static const int qm5[32] = {
+ -280, -280, -23352, -17560,
+ -14120, -11664, -9752, -8184,
+ -6864, -5712, -4696, -3784,
+ -2960, -2208, -1520, -880,
+ 23352, 17560, 14120, 11664,
+ 9752, 8184, 6864, 5712,
+ 4696, 3784, 2960, 2208,
+ 1520, 880, 280, -280
+ };
+ static const int qm6[64] = {
+ -136, -136, -136, -136,
+ -24808, -21904, -19008, -16704,
+ -14984, -13512, -12280, -11192,
+ -10232, -9360, -8576, -7856,
+ -7192, -6576, -6000, -5456,
+ -4944, -4464, -4008, -3576,
+ -3168, -2776, -2400, -2032,
+ -1688, -1360, -1040, -728,
+ 24808, 21904, 19008, 16704,
+ 14984, 13512, 12280, 11192,
+ 10232, 9360, 8576, 7856,
+ 7192, 6576, 6000, 5456,
+ 4944, 4464, 4008, 3576,
+ 3168, 2776, 2400, 2032,
+ 1688, 1360, 1040, 728,
+ 432, 136, -432, -136
+ };
+
+ /* INVQBL */
+ if (mode == 1) {
+ ril = ilr ;
+ wd2 = qm6[ril] ;
+ }
+
+ if (mode == 2) {
+ ril = ilr >> 1 ;
+ wd2 = qm5[ril] ;
+ }
+
+ if (mode == 3) {
+ ril = ilr >> 2 ;
+ wd2 = qm4[ril] ;
+ }
+
+ dl = (detl * wd2 ) >> 15 ;
+
+ /* RECONS */
+ yl = sl + dl ;
+ SATURATE(yl, 32767, -32768);
+
+ return (yl) ;
+}
+
+static int block6l (int yl)
+{
+ int rl ;
+
+ rl = yl ;
+ SATURATE(rl, 16383, -16384);
+
+ return (rl) ;
+}
+
+static int block2h (int ih, int deth)
+{
+ int dh ;
+ int wd2 ;
+ static const int qm2[4] = {-7408, -1616, 7408, 1616} ;
+
+ /* INVQAH */
+ wd2 = qm2[ih] ;
+ dh = (deth * wd2) >> 15 ;
+
+ return (dh) ;
+}
+
+static int block3h (g722_dec_t *dec, int ih)
+{
+ int deth ;
+ int ih2, wd, wd1, wd2, wd3, nbph, deph ;
+ static const int wh[3] = {0, -214, 798} ;
+ static const int rh2[4] = {2, 1, 2, 1} ;
+
+ /* LOGSCH */
+ ih2 = rh2[ih] ;
+ wd = (dec->nbh * 32512) >> 15 ;
+ nbph = wd + wh[ih2] ;
+
+ if (nbph < 0) nbph = 0 ;
+ if (nbph > 22528) nbph = 22528 ;
+
+
+ /* SCALEH */
+ wd1 = (nbph >> 6) & 31 ;
+ wd2 = nbph >> 11 ;
+ if ((10 - wd2) < 0) wd3 = ilb[wd1] << (wd2 - 10) ;
+ else wd3 = ilb[wd1] >> (10 - wd2) ;
+ deph = wd3 << 2 ;
+
+ /* DELAYA */
+ dec->nbh = nbph ;
+
+ /* DELAYH */
+ deth = deph ;
+
+ return (deth) ;
+}
+
+static int block4h (g722_dec_t *dec, int d)
+{
+ int sh = dec->shigh;
+ int i ;
+ int wd, wd1, wd2, wd3, wd4, wd5/*, wd6 */;
+
+ dec->dh[0] = d;
+
+ /* RECONS */
+ dec->rh[0] = sh + dec->dh[0] ;
+ SATURATE(dec->rh[0], 32767, -32768);
+
+ /* PARREC */
+ dec->ph[0] = dec->dh[0] + dec->szh ;
+ SATURATE(dec->ph[0], 32767, -32768);
+
+ /* UPPOL2 */
+ dec->sgh[0] = dec->ph[0] >> 15 ;
+ dec->sgh[1] = dec->ph[1] >> 15 ;
+ dec->sgh[2] = dec->ph[2] >> 15 ;
+
+ wd1 = dec->ah[1] << 2;
+ SATURATE(wd1, 32767, -32768);
+
+ if ( dec->sgh[0] == dec->sgh[1] ) wd2 = - wd1 ;
+ else wd2 = wd1 ;
+ if (wd2 > 32767) wd2 = 32767;
+
+ wd2 = wd2 >> 7 ;
+
+ if ( dec->sgh[0] == dec->sgh[2] ) wd3 = 128 ;
+ else wd3 = - 128 ;
+
+ wd4 = wd2 + wd3 ;
+ wd5 = (dec->ah[2] * 32512) >> 15 ;
+
+ dec->aph[2] = wd4 + wd5 ;
+ SATURATE(dec->aph[2], 12288, -12288);
+
+ /* UPPOL1 */
+ dec->sgh[0] = dec->ph[0] >> 15 ;
+ dec->sgh[1] = dec->ph[1] >> 15 ;
+
+ if ( dec->sgh[0] == dec->sgh[1] ) wd1 = 192 ;
+ else wd1 = - 192 ;
+
+ wd2 = (dec->ah[1] * 32640) >> 15 ;
+
+ dec->aph[1] = wd1 + wd2 ;
+ SATURATE(dec->aph[1], 32767, -32768);
+ //dec->aph[2]?
+ //if (aph[2] > 32767) aph[2] = 32767;
+ //if (aph[2] < -32768) aph[2] = -32768;
+
+ wd3 = (15360 - dec->aph[2]) ;
+ SATURATE(wd3, 32767, -32768);
+ if ( dec->aph[1] > wd3) dec->aph[1] = wd3 ;
+ if ( dec->aph[1] < -wd3) dec->aph[1] = -wd3 ;
+
+ /* UPZERO */
+ if ( dec->dh[0] == 0 ) wd1 = 0 ;
+ if ( dec->dh[0] != 0 ) wd1 = 128 ;
+
+ dec->sgh[0] = dec->dh[0] >> 15 ;
+
+ for ( i = 1; i < 7; i++ ) {
+ dec->sgh[i] = dec->dh[i] >> 15 ;
+ if ( dec->sgh[i] == dec->sgh[0] ) wd2 = wd1 ;
+ else wd2 = - wd1 ;
+ wd3 = (dec->bh[i] * 32640) >> 15 ;
+ dec->bph[i] = wd2 + wd3 ;
+ }
+
+ /* DELAYA */
+ for ( i = 6; i > 0; i-- ) {
+ dec->dh[i] = dec->dh[i-1] ;
+ dec->bh[i] = dec->bph[i] ;
+ }
+
+ for ( i = 2; i > 0; i-- ) {
+ dec->rh[i] = dec->rh[i-1] ;
+ dec->ph[i] = dec->ph[i-1] ;
+ dec->ah[i] = dec->aph[i] ;
+ }
+
+ /* FILTEP */
+ wd1 = dec->rh[1] << 1 ;
+ SATURATE(wd1, 32767, -32768);
+ wd1 = ( dec->ah[1] * wd1 ) >> 15 ;
+
+ wd2 = dec->rh[2] << 1;
+ SATURATE(wd2, 32767, -32768);
+ wd2 = ( dec->ah[2] * wd2 ) >> 15 ;
+
+ dec->sph = wd1 + wd2 ;
+ SATURATE(dec->sph, 32767, -32768);
+
+ /* FILTEZ */
+ dec->szh = 0 ;
+ for (i=6; i>0; i--) {
+ wd = dec->dh[i] << 1;
+ SATURATE(wd, 32767, -32768);
+ dec->szh += (dec->bh[i] * wd) >> 15 ;
+ SATURATE(dec->szh, 32767, -32768);
+ }
+
+ /* PREDIC */
+ sh = dec->sph + dec->szh ;
+ SATURATE(sh, 32767, -32768);
+
+ return (sh) ;
+}
+
+static int block5h (int dh, int sh)
+{
+ int rh ;
+
+ rh = dh + sh;
+ SATURATE(rh, 16383, -16384);
+
+ return (rh) ;
+}
+
+void rx_qmf(g722_dec_t *dec, int rl, int rh, int *xout1, int *xout2)
+{
+ int i;
+
+ pj_memmove(&dec->xd[1], dec->xd, 11*sizeof(dec->xd[0]));
+ pj_memmove(&dec->xs[1], dec->xs, 11*sizeof(dec->xs[0]));
+
+ /* RECA */
+ dec->xd[0] = rl - rh ;
+ if (dec->xd[0] > 16383) dec->xd[0] = 16383;
+ else if (dec->xd[0] < -16384) dec->xd[0] = -16384;
+
+ /* RECB */
+ dec->xs[0] = rl + rh ;
+ if (dec->xs[0] > 16383) dec->xs[0] = 16383;
+ else if (dec->xs[0] < -16384) dec->xs[0] = -16384;
+
+ /* ACCUMC */
+ *xout1 = 0;
+ for (i=0; i<12; ++i) *xout1 += dec->xd[i] * g722_qmf_coeff[2*i];
+ *xout1 = *xout1 >> 12 ;
+ if (*xout1 > 16383) *xout1 = 16383 ;
+ else if (*xout1 < -16384) *xout1 = -16384 ;
+
+ /* ACCUMD */
+ *xout2 = 0;
+ for (i=0; i<12; ++i) *xout2 += dec->xs[i] * g722_qmf_coeff[2*i+1];
+ *xout2 = *xout2 >> 12 ;
+ if (*xout2 > 16383) *xout2 = 16383 ;
+ else if (*xout2 < -16384) *xout2 = -16384 ;
+}
+
+
+PJ_DEF(pj_status_t) g722_dec_init(g722_dec_t *dec)
+{
+ PJ_ASSERT_RETURN(dec, PJ_EINVAL);
+
+ pj_bzero(dec, sizeof(g722_dec_t));
+
+ dec->detlow = 32;
+ dec->dethigh = 8;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) g722_dec_decode( g722_dec_t *dec,
+ void *in,
+ pj_size_t in_size,
+ pj_int16_t out[],
+ pj_size_t *nsamples)
+{
+ unsigned i;
+ int ilowr, ylow, rlow, dlowt;
+ int ihigh, rhigh, dhigh;
+ int pcm1, pcm2;
+ pj_uint8_t *in_ = (pj_uint8_t*) in;
+
+ PJ_ASSERT_RETURN(dec && in && in_size && out && nsamples, PJ_EINVAL);
+ PJ_ASSERT_RETURN(*nsamples >= (in_size << 1), PJ_ETOOSMALL);
+
+ for(i = 0; i < in_size; ++i) {
+ ilowr = in_[i] & 63;
+ ihigh = (in_[i] >> 6) & 3;
+
+ /* low band decoder */
+ ylow = block5l (ilowr, dec->slow, dec->detlow, MODE) ;
+ rlow = block6l (ylow) ;
+ dlowt = block2l (ilowr, dec->detlow) ;
+ dec->detlow = block3l (dec, ilowr) ;
+ dec->slow = block4l (dec, dlowt) ;
+ /* rlow <= output low band pcm */
+
+ /* high band decoder */
+ dhigh = block2h (ihigh, dec->dethigh) ;
+ rhigh = block5h (dhigh, dec->shigh) ;
+ dec->dethigh = block3h (dec, ihigh) ;
+ dec->shigh = block4h (dec, dhigh) ;
+ /* rhigh <= output high band pcm */
+
+ rx_qmf(dec, rlow, rhigh, &pcm1, &pcm2);
+ out[i*2] = (pj_int16_t)pcm1;
+ out[i*2+1] = (pj_int16_t)pcm2;
+ }
+
+ *nsamples = in_size << 1;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) g722_dec_deinit(g722_dec_t *dec)
+{
+ pj_bzero(dec, sizeof(g722_dec_t));
+
+ return PJ_SUCCESS;
+}
+
+#endif
diff --git a/pjmedia/src/pjmedia-codec/g722/g722_dec.h b/pjmedia/src/pjmedia-codec/g722/g722_dec.h
new file mode 100644
index 0000000..3cf841e
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/g722/g722_dec.h
@@ -0,0 +1,79 @@
+/* $Id: g722_dec.h 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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
+ */
+/*
+ * Based on implementation found in Carnegie Mellon Speech Group Software
+ * depository (ftp://ftp.cs.cmu.edu/project/fgdata/index.html). No copyright
+ * was claimed in the original source codes.
+ */
+#ifndef __PJMEDIA_CODEC_G722_DEC_H__
+#define __PJMEDIA_CODEC_G722_DEC_H__
+
+#include <pjmedia-codec/types.h>
+
+/* Decoder state */
+typedef struct g722_dec_t {
+ /* PCM low band */
+ int slow;
+ int detlow;
+ int spl;
+ int szl;
+ int rlt [3];
+ int al [3];
+ int apl [3];
+ int plt [3];
+ int dlt [7];
+ int bl [7];
+ int bpl [7];
+ int sgl [7];
+ int nbl;
+
+ /* PCM high band*/
+ int shigh;
+ int dethigh;
+ int sph;
+ int szh;
+ int rh [3];
+ int ah [3];
+ int aph [3];
+ int ph [3];
+ int dh [7];
+ int bh [7];
+ int bph [7];
+ int sgh [7];
+ int nbh;
+
+ /* QMF signal history */
+ int xd[12];
+ int xs[12];
+} g722_dec_t;
+
+
+PJ_DECL(pj_status_t) g722_dec_init(g722_dec_t *dec);
+
+PJ_DECL(pj_status_t) g722_dec_decode(g722_dec_t *dec,
+ void *in,
+ pj_size_t in_size,
+ pj_int16_t out[],
+ pj_size_t *nsamples);
+
+PJ_DECL(pj_status_t) g722_dec_deinit(g722_dec_t *dec);
+
+#endif /* __PJMEDIA_CODEC_G722_DEC_H__ */
+
diff --git a/pjmedia/src/pjmedia-codec/g722/g722_enc.c b/pjmedia/src/pjmedia-codec/g722/g722_enc.c
new file mode 100644
index 0000000..d0b2011
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/g722/g722_enc.c
@@ -0,0 +1,576 @@
+/* $Id: g722_enc.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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
+ */
+/*
+ * Based on implementation found in Carnegie Mellon Speech Group Software
+ * depository (ftp://ftp.cs.cmu.edu/project/fgdata/index.html). No copyright
+ * was claimed in the original source codes.
+ */
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/pool.h>
+
+#include "g722_enc.h"
+
+#if defined(PJMEDIA_HAS_G722_CODEC) && (PJMEDIA_HAS_G722_CODEC != 0)
+
+#define SATURATE(v, max, min) \
+ if (v>max) v = max; \
+ else if (v<min) v = min
+
+/* QMF tap coefficients */
+const int g722_qmf_coeff[24] = {
+ 3, -11, -11, 53, 12, -156,
+ 32, 362, -210, -805, 951, 3876,
+ 3876, 951, -805, -210, 362, 32,
+ -156, 12, 53, -11, -11, 3
+};
+
+
+static int block1l (int xl, int sl, int detl)
+{
+ int il ;
+
+ int i, el, sil, mil, wd, wd1, hdu ;
+
+ static const int q6[32] = {
+ 0, 35, 72, 110, 150, 190, 233, 276, 323,
+ 370, 422, 473, 530, 587, 650, 714, 786,
+ 858, 940, 1023, 1121, 1219, 1339, 1458,
+ 1612, 1765, 1980, 2195, 2557, 2919, 0, 0
+ };
+
+ static const int iln[32] = {
+ 0, 63, 62, 31, 30, 29, 28, 27, 26, 25,
+ 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14,
+ 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 0
+ };
+
+ static const int ilp[32] = {
+ 0, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52,
+ 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41,
+ 40, 39, 38, 37, 36, 35, 34, 33, 32, 0
+ };
+
+ /* SUBTRA */
+
+ el = xl - sl ;
+ SATURATE(el, 32767, -32768);
+
+ /* QUANTL */
+
+ sil = el >> 15 ;
+ if (sil == 0 ) wd = el ;
+ else wd = (32767 - el) & 32767 ;
+
+ mil = 1 ;
+
+ for (i = 1; i < 30; i++) {
+ hdu = (q6[i] << 3) * detl;
+ wd1 = (hdu >> 15) ;
+ if (wd >= wd1) mil = (i + 1) ;
+ else break ;
+ }
+
+ if (sil == -1 ) il = iln[mil] ;
+ else il = ilp[mil] ;
+
+ return (il) ;
+}
+
+static int block2l (int il, int detl)
+{
+ int dlt;
+ int ril, wd2 ;
+ static const int qm4[16] = {
+ 0, -20456, -12896, -8968,
+ -6288, -4240, -2584, -1200,
+ 20456, 12896, 8968, 6288,
+ 4240, 2584, 1200, 0
+ };
+
+ /* INVQAL */
+ ril = il >> 2 ;
+ wd2 = qm4[ril] ;
+ dlt = (detl * wd2) >> 15 ;
+
+ return (dlt) ;
+}
+
+static int block3l (g722_enc_t *enc, int il)
+{
+ int detl;
+ int ril, il4, wd, wd1, wd2, wd3, nbpl, depl ;
+ static int const wl[8] = {
+ -60, -30, 58, 172, 334, 538, 1198, 3042
+ } ;
+ static int const rl42[16] = {
+ 0, 7, 6, 5, 4, 3, 2, 1, 7, 6, 5, 4, 3, 2, 1, 0
+ };
+ static const int ilb[32] = {
+ 2048, 2093, 2139, 2186, 2233, 2282, 2332,
+ 2383, 2435, 2489, 2543, 2599, 2656, 2714,
+ 2774, 2834, 2896, 2960, 3025, 3091, 3158,
+ 3228, 3298, 3371, 3444, 3520, 3597, 3676,
+ 3756, 3838, 3922, 4008
+ };
+
+ /* LOGSCL */
+
+ ril = il >> 2 ;
+ il4 = rl42[ril] ;
+
+ wd = (enc->nbl * 32512) >> 15 ;
+ nbpl = wd + wl[il4] ;
+
+ if (nbpl < 0) nbpl = 0 ;
+ if (nbpl > 18432) nbpl = 18432 ;
+
+ /* SCALEL */
+
+ wd1 = (nbpl >> 6) & 31 ;
+ wd2 = nbpl >> 11 ;
+ if ((8 - wd2) < 0) wd3 = ilb[wd1] << (wd2 - 8) ;
+ else wd3 = ilb[wd1] >> (8 - wd2) ;
+ depl = wd3 << 2 ;
+
+ /* DELAYA */
+ enc->nbl = nbpl ;
+
+ /* DELAYL */
+ detl = depl ;
+
+#ifdef DEBUG_VERBOSE
+ printf ("BLOCK3L il=%4d, ril=%4d, il4=%4d, nbl=%4d, wd=%4d, nbpl=%4d\n",
+ il, ril, il4, enc->nbl, wd, nbpl) ;
+ printf ("wd1=%4d, wd2=%4d, wd3=%4d, depl=%4d, detl=%4d\n",
+ wd1, wd2, wd3, depl, detl) ;
+#endif
+
+ return (detl) ;
+}
+
+static int block4l (g722_enc_t *enc, int dl)
+{
+ int sl = enc->slow;
+ int i ;
+ int wd, wd1, wd2, wd3, wd4, wd5 /*, wd6 */;
+
+ enc->dlt[0] = dl;
+
+ /* RECONS */
+
+ enc->rlt[0] = sl + enc->dlt[0] ;
+ SATURATE(enc->rlt[0], 32767, -32768);
+
+ /* PARREC */
+
+ enc->plt[0] = enc->dlt[0] + enc->szl ;
+ SATURATE(enc->plt[0], 32767, -32768);
+
+ /* UPPOL2 */
+
+ enc->sgl[0] = enc->plt[0] >> 15 ;
+ enc->sgl[1] = enc->plt[1] >> 15 ;
+ enc->sgl[2] = enc->plt[2] >> 15 ;
+
+ wd1 = enc->al[1] << 2;
+ SATURATE(wd1, 32767, -32768);
+
+ if ( enc->sgl[0] == enc->sgl[1] ) wd2 = - wd1 ;
+ else wd2 = wd1 ;
+ if ( wd2 > 32767 ) wd2 = 32767;
+
+ wd2 = wd2 >> 7 ;
+
+ if ( enc->sgl[0] == enc->sgl[2] ) wd3 = 128 ;
+ else wd3 = - 128 ;
+
+ wd4 = wd2 + wd3 ;
+ wd5 = (enc->al[2] * 32512) >> 15 ;
+
+ enc->apl[2] = wd4 + wd5 ;
+ SATURATE(enc->apl[2], 12288, -12288);
+
+ /* UPPOL1 */
+
+ enc->sgl[0] = enc->plt[0] >> 15 ;
+ enc->sgl[1] = enc->plt[1] >> 15 ;
+
+ if ( enc->sgl[0] == enc->sgl[1] ) wd1 = 192 ;
+ else wd1 = - 192 ;
+
+ wd2 = (enc->al[1] * 32640) >> 15 ;
+
+ enc->apl[1] = wd1 + wd2 ;
+ SATURATE(enc->apl[1], 32767, -32768);
+
+ wd3 = (15360 - enc->apl[2]) ;
+ SATURATE(wd3, 32767, -32768);
+
+ if ( enc->apl[1] > wd3) enc->apl[1] = wd3 ;
+ if ( enc->apl[1] < -wd3) enc->apl[1] = -wd3 ;
+
+ /* UPZERO */
+
+ if ( enc->dlt[0] == 0 ) wd1 = 0 ;
+ else wd1 = 128 ;
+
+ enc->sgl[0] = enc->dlt[0] >> 15 ;
+
+ for ( i = 1; i < 7; i++ ) {
+ enc->sgl[i] = enc->dlt[i] >> 15 ;
+ if ( enc->sgl[i] == enc->sgl[0] ) wd2 = wd1 ;
+ else wd2 = - wd1 ;
+ wd3 = (enc->bl[i] * 32640) >> 15 ;
+ enc->bpl[i] = wd2 + wd3 ;
+ SATURATE(enc->bpl[i], 32767, -32768);
+ }
+
+ /* DELAYA */
+
+ for ( i = 6; i > 0; i-- ) {
+ enc->dlt[i] = enc->dlt[i-1] ;
+ enc->bl[i] = enc->bpl[i] ;
+ }
+
+ for ( i = 2; i > 0; i-- ) {
+ enc->rlt[i] = enc->rlt[i-1] ;
+ enc->plt[i] = enc->plt[i-1] ;
+ enc->al[i] = enc->apl[i] ;
+ }
+
+ /* FILTEP */
+
+ wd1 = enc->rlt[1] + enc->rlt[1];
+ SATURATE(wd1, 32767, -32768);
+ wd1 = ( enc->al[1] * wd1 ) >> 15 ;
+
+ wd2 = enc->rlt[2] + enc->rlt[2];
+ SATURATE(wd2, 32767, -32768);
+ wd2 = ( enc->al[2] * wd2 ) >> 15 ;
+
+ enc->spl = wd1 + wd2 ;
+ SATURATE(enc->spl, 32767, -32768);
+
+ /* FILTEZ */
+
+ enc->szl = 0 ;
+ for (i=6; i>0; i--) {
+ wd = enc->dlt[i] + enc->dlt[i];
+ SATURATE(wd, 32767, -32768);
+ enc->szl += (enc->bl[i] * wd) >> 15 ;
+ SATURATE(enc->szl, 32767, -32768);
+ }
+
+ /* PREDIC */
+
+ sl = enc->spl + enc->szl ;
+ SATURATE(sl, 32767, -32768);
+
+ return (sl) ;
+}
+
+static int block1h (int xh, int sh, int deth)
+{
+ int ih ;
+
+ int eh, sih, mih, wd, wd1, hdu ;
+
+ static const int ihn[3] = { 0, 1, 0 } ;
+ static const int ihp[3] = { 0, 3, 2 } ;
+
+ /* SUBTRA */
+
+ eh = xh - sh ;
+ SATURATE(eh, 32767, -32768);
+
+ /* QUANTH */
+
+ sih = eh >> 15 ;
+ if (sih == 0 ) wd = eh ;
+ else wd = (32767 - eh) & 32767 ;
+
+ hdu = (564 << 3) * deth;
+ wd1 = (hdu >> 15) ;
+ if (wd >= wd1) mih = 2 ;
+ else mih = 1 ;
+
+ if (sih == -1 ) ih = ihn[mih] ;
+ else ih = ihp[mih] ;
+
+ return (ih) ;
+}
+
+static int block2h (int ih, int deth)
+{
+ int dh ;
+ int wd2 ;
+ static const int qm2[4] = {-7408, -1616, 7408, 1616};
+
+ /* INVQAH */
+
+ wd2 = qm2[ih] ;
+ dh = (deth * wd2) >> 15 ;
+
+ return (dh) ;
+}
+
+static int block3h (g722_enc_t *enc, int ih)
+{
+ int deth ;
+ int ih2, wd, wd1, wd2, wd3, nbph, deph ;
+ static const int wh[3] = {0, -214, 798} ;
+ static const int rh2[4] = {2, 1, 2, 1} ;
+ static const int ilb[32] = {
+ 2048, 2093, 2139, 2186, 2233, 2282, 2332,
+ 2383, 2435, 2489, 2543, 2599, 2656, 2714,
+ 2774, 2834, 2896, 2960, 3025, 3091, 3158,
+ 3228, 3298, 3371, 3444, 3520, 3597, 3676,
+ 3756, 3838, 3922, 4008
+ };
+
+ /* LOGSCH */
+
+ ih2 = rh2[ih] ;
+ wd = (enc->nbh * 32512) >> 15 ;
+ nbph = wd + wh[ih2] ;
+
+ if (nbph < 0) nbph = 0 ;
+ if (nbph > 22528) nbph = 22528 ;
+
+ /* SCALEH */
+
+ wd1 = (nbph >> 6) & 31 ;
+ wd2 = nbph >> 11 ;
+ if ((10-wd2) < 0) wd3 = ilb[wd1] << (wd2-10) ;
+ else wd3 = ilb[wd1] >> (10-wd2) ;
+ deph = wd3 << 2 ;
+
+ /* DELAYA */
+ enc->nbh = nbph ;
+ /* DELAYH */
+ deth = deph ;
+
+ return (deth) ;
+}
+
+static int block4h (g722_enc_t *enc, int d)
+{
+ int sh = enc->shigh;
+ int i ;
+ int wd, wd1, wd2, wd3, wd4, wd5 /*, wd6 */;
+
+ enc->dh[0] = d;
+
+ /* RECONS */
+
+ enc->rh[0] = sh + enc->dh[0] ;
+ SATURATE(enc->rh[0], 32767, -32768);
+
+ /* PARREC */
+
+ enc->ph[0] = enc->dh[0] + enc->szh ;
+ SATURATE(enc->ph[0], 32767, -32768);
+
+ /* UPPOL2 */
+
+ enc->sgh[0] = enc->ph[0] >> 15 ;
+ enc->sgh[1] = enc->ph[1] >> 15 ;
+ enc->sgh[2] = enc->ph[2] >> 15 ;
+
+ wd1 = enc->ah[1] << 2;
+ SATURATE(wd1, 32767, -32768);
+
+ if ( enc->sgh[0] == enc->sgh[1] ) wd2 = - wd1 ;
+ else wd2 = wd1 ;
+ if ( wd2 > 32767 ) wd2 = 32767;
+
+ wd2 = wd2 >> 7 ;
+
+ if ( enc->sgh[0] == enc->sgh[2] ) wd3 = 128 ;
+ else wd3 = - 128 ;
+
+ wd4 = wd2 + wd3 ;
+ wd5 = (enc->ah[2] * 32512) >> 15 ;
+
+ enc->aph[2] = wd4 + wd5 ;
+ SATURATE(enc->aph[2], 12288, -12288);
+
+ /* UPPOL1 */
+
+ enc->sgh[0] = enc->ph[0] >> 15 ;
+ enc->sgh[1] = enc->ph[1] >> 15 ;
+
+ if ( enc->sgh[0] == enc->sgh[1] ) wd1 = 192 ;
+ else wd1 = - 192 ;
+
+ wd2 = (enc->ah[1] * 32640) >> 15 ;
+
+ enc->aph[1] = wd1 + wd2 ;
+ SATURATE(enc->aph[1], 32767, -32768);
+
+ wd3 = (15360 - enc->aph[2]) ;
+ SATURATE(wd3, 32767, -32768);
+
+ if ( enc->aph[1] > wd3) enc->aph[1] = wd3 ;
+ else if ( enc->aph[1] < -wd3) enc->aph[1] = -wd3 ;
+
+ /* UPZERO */
+
+ if ( enc->dh[0] == 0 ) wd1 = 0 ;
+ else wd1 = 128 ;
+
+ enc->sgh[0] = enc->dh[0] >> 15 ;
+
+ for ( i = 1; i < 7; i++ ) {
+ enc->sgh[i] = enc->dh[i] >> 15 ;
+ if ( enc->sgh[i] == enc->sgh[0] ) wd2 = wd1 ;
+ else wd2 = - wd1 ;
+ wd3 = (enc->bh[i] * 32640) >> 15 ;
+ enc->bph[i] = wd2 + wd3 ;
+ SATURATE(enc->bph[i], 32767, -32768);
+ }
+
+ /* DELAYA */
+ for ( i = 6; i > 0; i-- ) {
+ enc->dh[i] = enc->dh[i-1] ;
+ enc->bh[i] = enc->bph[i] ;
+ }
+
+ for ( i = 2; i > 0; i-- ) {
+ enc->rh[i] = enc->rh[i-1] ;
+ enc->ph[i] = enc->ph[i-1] ;
+ enc->ah[i] = enc->aph[i] ;
+ }
+
+ /* FILTEP */
+
+ wd1 = enc->rh[1] + enc->rh[1];
+ SATURATE(wd1, 32767, -32768);
+ wd1 = ( enc->ah[1] * wd1 ) >> 15 ;
+
+ wd2 = enc->rh[2] + enc->rh[2];
+ SATURATE(wd2, 32767, -32768);
+ wd2 = ( enc->ah[2] * wd2 ) >> 15 ;
+
+ enc->sph = wd1 + wd2 ;
+ SATURATE(enc->sph, 32767, -32768);
+
+ /* FILTEZ */
+
+ enc->szh = 0 ;
+ for (i=6; i>0; i--) {
+ wd = enc->dh[i] + enc->dh[i];
+ SATURATE(wd, 32767, -32768);
+ enc->szh += (enc->bh[i] * wd) >> 15 ;
+ SATURATE(enc->szh, 32767, -32768);
+ }
+
+ /* PREDIC */
+
+ sh = enc->sph + enc->szh ;
+ SATURATE(sh, 32767, -32768);
+
+ return (sh) ;
+}
+
+/* PROCESS PCM THROUGH THE QMF FILTER */
+static void tx_qmf(g722_enc_t *enc, int pcm1, int pcm2, int *lo, int *hi)
+{
+ int sumodd, sumeven;
+ int i;
+
+ pj_memmove(&enc->x[2], enc->x, 22 * sizeof(enc->x[0]));
+ enc->x[1] = pcm1;
+ enc->x[0] = pcm2;
+
+ sumodd = 0;
+ for (i=1; i<24; i+=2) sumodd += enc->x[i] * g722_qmf_coeff[i];
+
+ sumeven = 0;
+ for (i=0; i<24; i+=2) sumeven += enc->x[i] * g722_qmf_coeff[i];
+
+ *lo = (sumeven + sumodd) >> 13 ;
+ *hi = (sumeven - sumodd) >> 13 ;
+
+ SATURATE(*lo, 16383, -16384);
+ SATURATE(*hi, 16383, -16383);
+}
+
+
+PJ_DEF(pj_status_t) g722_enc_init(g722_enc_t *enc)
+{
+ PJ_ASSERT_RETURN(enc, PJ_EINVAL);
+
+ pj_bzero(enc, sizeof(g722_enc_t));
+
+ enc->detlow = 32;
+ enc->dethigh = 8;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) g722_enc_encode( g722_enc_t *enc,
+ pj_int16_t in[],
+ pj_size_t nsamples,
+ void *out,
+ pj_size_t *out_size)
+{
+ unsigned i;
+ int xlow, ilow, dlowt;
+ int xhigh, ihigh, dhigh;
+ pj_uint8_t *out_ = (pj_uint8_t*) out;
+
+ PJ_ASSERT_RETURN(enc && in && nsamples && out && out_size, PJ_EINVAL);
+ PJ_ASSERT_RETURN(nsamples % 2 == 0, PJ_EINVAL);
+ PJ_ASSERT_RETURN(*out_size >= (nsamples >> 1), PJ_ETOOSMALL);
+
+ for(i = 0; i < nsamples; i += 2) {
+ tx_qmf(enc, in[i], in[i+1], &xlow, &xhigh);
+
+ /* low band encoder */
+ ilow = block1l (xlow, enc->slow, enc->detlow) ;
+ dlowt = block2l (ilow, enc->detlow) ;
+ enc->detlow = block3l (enc, ilow) ;
+ enc->slow = block4l (enc, dlowt) ;
+
+ /* high band encoder */
+ ihigh = block1h (xhigh, enc->shigh, enc->dethigh) ;
+ dhigh = block2h (ihigh, enc->dethigh) ;
+ enc->dethigh = block3h (enc, ihigh) ;
+ enc->shigh = block4h (enc, dhigh) ;
+
+ /* bits mix low & high adpcm */
+ out_[i/2] = (pj_uint8_t)((ihigh << 6) | ilow);
+ }
+
+ *out_size = nsamples >> 1;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) g722_enc_deinit(g722_enc_t *enc)
+{
+ pj_bzero(enc, sizeof(g722_enc_t));
+
+ return PJ_SUCCESS;
+}
+
+#endif
diff --git a/pjmedia/src/pjmedia-codec/g722/g722_enc.h b/pjmedia/src/pjmedia-codec/g722/g722_enc.h
new file mode 100644
index 0000000..96f9af6
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/g722/g722_enc.h
@@ -0,0 +1,78 @@
+/* $Id: g722_enc.h 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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
+ */
+/*
+ * Based on implementation found in Carnegie Mellon Speech Group Software
+ * depository (ftp://ftp.cs.cmu.edu/project/fgdata/index.html). No copyright
+ * was claimed in the original source codes.
+ */
+#ifndef __PJMEDIA_CODEC_G722_ENC_H__
+#define __PJMEDIA_CODEC_G722_ENC_H__
+
+#include <pjmedia-codec/types.h>
+
+/* Encoder state */
+typedef struct g722_enc_t {
+ /* PCM low band */
+ int slow;
+ int detlow;
+ int spl;
+ int szl;
+ int rlt [3];
+ int al [3];
+ int apl [3];
+ int plt [3];
+ int dlt [7];
+ int bl [7];
+ int bpl [7];
+ int sgl [7];
+ int nbl;
+
+ /* PCM high band*/
+ int shigh;
+ int dethigh;
+ int sph;
+ int szh;
+ int rh [3];
+ int ah [3];
+ int aph [3];
+ int ph [3];
+ int dh [7];
+ int bh [7];
+ int bph [7];
+ int sgh [7];
+ int nbh;
+
+ /* QMF signal history */
+ int x[24];
+} g722_enc_t;
+
+
+PJ_DECL(pj_status_t) g722_enc_init(g722_enc_t *enc);
+
+PJ_DECL(pj_status_t) g722_enc_encode(g722_enc_t *enc,
+ pj_int16_t in[],
+ pj_size_t nsamples,
+ void *out,
+ pj_size_t *out_size);
+
+PJ_DECL(pj_status_t) g722_enc_deinit(g722_enc_t *enc);
+
+#endif /* __PJMEDIA_CODEC_G722_ENC_H__ */
+
diff --git a/pjmedia/src/pjmedia-codec/g7221.c b/pjmedia/src/pjmedia-codec/g7221.c
new file mode 100644
index 0000000..2d38483
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/g7221.c
@@ -0,0 +1,950 @@
+/* $Id: g7221.c 4001 2012-03-30 07:53:36Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia-codec/g7221.h>
+#include <pjmedia-codec/g7221_sdp_match.h>
+#include <pjmedia/codec.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/endpoint.h>
+#include <pjmedia/port.h>
+#include <pjmedia/silencedet.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/os.h>
+
+/*
+ * Only build this file if PJMEDIA_HAS_G7221_CODEC != 0
+ */
+#if defined(PJMEDIA_HAS_G7221_CODEC) && PJMEDIA_HAS_G7221_CODEC!=0
+
+#include "../../../third_party/g7221/common/defs.h"
+
+#define THIS_FILE "g7221.c"
+
+/* Codec tag, it is the SDP encoding name and also MIME subtype name */
+#define CODEC_TAG "G7221"
+
+/* Sampling rates definition */
+#define WB_SAMPLE_RATE 16000
+#define UWB_SAMPLE_RATE 32000
+
+/* Maximum number of samples per frame. */
+#define MAX_SAMPLES_PER_FRAME (UWB_SAMPLE_RATE * 20 / 1000)
+
+/* Maximum number of codec params. */
+#define MAX_CODEC_MODES 8
+#define START_RSV_MODES_IDX 6
+
+
+/* Prototypes for G722.1 codec factory */
+static pj_status_t test_alloc( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id );
+static pj_status_t default_attr( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr );
+static pj_status_t enum_codecs( pjmedia_codec_factory *factory,
+ unsigned *count,
+ pjmedia_codec_info codecs[]);
+static pj_status_t alloc_codec( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec);
+static pj_status_t dealloc_codec( pjmedia_codec_factory *factory,
+ pjmedia_codec *codec );
+
+/* Prototypes for G722.1 codec implementation. */
+static pj_status_t codec_init( pjmedia_codec *codec,
+ pj_pool_t *pool );
+static pj_status_t codec_open( pjmedia_codec *codec,
+ pjmedia_codec_param *attr );
+static pj_status_t codec_close( pjmedia_codec *codec );
+static pj_status_t codec_modify(pjmedia_codec *codec,
+ const pjmedia_codec_param *attr );
+static pj_status_t codec_parse( pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[]);
+static pj_status_t codec_encode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+static pj_status_t codec_decode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+static pj_status_t codec_recover( pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+
+/* Definition for G722.1 codec operations. */
+static pjmedia_codec_op codec_op =
+{
+ &codec_init,
+ &codec_open,
+ &codec_close,
+ &codec_modify,
+ &codec_parse,
+ &codec_encode,
+ &codec_decode,
+ &codec_recover
+};
+
+/* Definition for G722.1 codec factory operations. */
+static pjmedia_codec_factory_op codec_factory_op =
+{
+ &test_alloc,
+ &default_attr,
+ &enum_codecs,
+ &alloc_codec,
+ &dealloc_codec,
+ &pjmedia_codec_g7221_deinit
+};
+
+
+/* Structure of G722.1 mode */
+typedef struct codec_mode
+{
+ pj_bool_t enabled; /* Is this mode enabled? */
+ pj_uint8_t pt; /* Payload type. */
+ unsigned sample_rate; /* Default sampling rate to be used.*/
+ unsigned bitrate; /* Bitrate. */
+ char bitrate_str[8]; /* Bitrate in string. */
+} codec_mode;
+
+
+/* G722.1 codec factory */
+static struct codec_factory {
+ pjmedia_codec_factory base; /**< Base class. */
+ pjmedia_endpt *endpt; /**< PJMEDIA endpoint instance. */
+ pj_pool_t *pool; /**< Codec factory pool. */
+ pj_mutex_t *mutex; /**< Codec factory mutex. */
+
+ int pcm_shift; /**< Level adjustment */
+ unsigned mode_count; /**< Number of G722.1 modes. */
+ codec_mode modes[MAX_CODEC_MODES]; /**< The G722.1 modes. */
+ unsigned mode_rsv_start;/**< Start index of G722.1 non-
+ standard modes, currently
+ there can only be up to two
+ non-standard modes enabled
+ at the same time. */
+} codec_factory;
+
+/* G722.1 codec private data. */
+typedef struct codec_private {
+ pj_pool_t *pool; /**< Pool for each instance. */
+ pj_bool_t plc_enabled; /**< PLC enabled? */
+ pj_bool_t vad_enabled; /**< VAD enabled? */
+ pjmedia_silence_det *vad; /**< PJMEDIA VAD instance. */
+ pj_timestamp last_tx; /**< Timestamp of last transmit.*/
+
+ /* ITU ref implementation seems to leave the codec engine states to be
+ * managed by the application, so here we go.
+ */
+
+ /* Common engine state */
+ pj_uint16_t samples_per_frame; /**< Samples per frame. */
+ pj_uint16_t bitrate; /**< Coded stream bitrate. */
+ pj_uint16_t frame_size; /**< Coded frame size. */
+ pj_uint16_t frame_size_bits; /**< Coded frame size in bits. */
+ pj_uint16_t number_of_regions; /**< Number of regions. */
+ int pcm_shift; /**< Adjustment for PCM in/out */
+
+ /* Encoder specific state */
+ Word16 *enc_frame; /**< 16bit to 14bit buffer */
+ Word16 *enc_old_frame;
+
+ /* Decoder specific state */
+ Word16 *dec_old_frame;
+ Rand_Obj dec_randobj;
+ Word16 dec_old_mag_shift;
+ Word16 *dec_old_mlt_coefs;
+} codec_private_t;
+
+/*
+ * Helper function for looking up mode based on payload type.
+ */
+static codec_mode* lookup_mode(unsigned pt)
+{
+ codec_mode* mode = NULL;
+ unsigned i;
+
+ for (i = 0; i < codec_factory.mode_count; ++i) {
+ mode = &codec_factory.modes[i];
+ if (mode->pt == pt)
+ break;
+ }
+
+ return mode;
+}
+
+/*
+ * Helper function to validate G722.1 mode. Valid modes are defined as:
+ * 1. sample rate must be 16kHz or 32kHz,
+ * 2. bitrate:
+ * - for sampling rate 16kHz: 16000 to 32000 bps, it must be a multiple
+ * of 400 (to keep RTP payload octed-aligned)
+ * - for sampling rate 32kHz: 24000 to 48000 bps, it must be a multiple
+ * of 400 (to keep RTP payload octed-aligned)
+ */
+static pj_bool_t validate_mode(unsigned sample_rate, unsigned bitrate)
+{
+ if (sample_rate == WB_SAMPLE_RATE) {
+ if (bitrate < 16000 || bitrate > 32000 ||
+ ((bitrate-16000) % 400 != 0))
+ {
+ return PJ_FALSE;
+ }
+ } else if (sample_rate == UWB_SAMPLE_RATE) {
+ if (bitrate < 24000 || bitrate > 48000 ||
+ ((bitrate-24000) % 400 != 0))
+ {
+ return PJ_FALSE;
+ }
+ } else {
+ return PJ_FALSE;
+ }
+
+ return PJ_TRUE;
+}
+
+#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN!=0
+PJ_INLINE(void) swap_bytes(pj_uint16_t *buf, unsigned count)
+{
+ pj_uint16_t *end = buf + count;
+ while (buf != end) {
+ *buf = (pj_uint16_t)((*buf << 8) | (*buf >> 8));
+ ++buf;
+ }
+}
+#else
+#define swap_bytes(buf, count)
+#endif
+
+/*
+ * Initialize and register G722.1 codec factory to pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_g7221_init( pjmedia_endpt *endpt )
+{
+ pjmedia_codec_mgr *codec_mgr;
+ codec_mode *mode;
+ pj_str_t codec_name;
+ pj_status_t status;
+
+ if (codec_factory.pool != NULL) {
+ /* Already initialized. */
+ return PJ_SUCCESS;
+ }
+
+ /* Initialize codec modes, by default all standard bitrates are enabled */
+ codec_factory.mode_count = 0;
+ codec_factory.pcm_shift = PJMEDIA_G7221_DEFAULT_PCM_SHIFT;
+
+ mode = &codec_factory.modes[codec_factory.mode_count++];
+ mode->enabled = PJ_TRUE;
+ mode->pt = PJMEDIA_RTP_PT_G722_1_24;
+ mode->sample_rate = WB_SAMPLE_RATE;
+ mode->bitrate = 24000;
+ pj_utoa(mode->bitrate, mode->bitrate_str);
+
+ mode = &codec_factory.modes[codec_factory.mode_count++];
+ mode->enabled = PJ_TRUE;
+ mode->pt = PJMEDIA_RTP_PT_G722_1_32;
+ mode->sample_rate = WB_SAMPLE_RATE;
+ mode->bitrate = 32000;
+ pj_utoa(mode->bitrate, mode->bitrate_str);
+
+ mode = &codec_factory.modes[codec_factory.mode_count++];
+ mode->enabled = PJ_TRUE;
+ mode->pt = PJMEDIA_RTP_PT_G7221C_24;
+ mode->sample_rate = UWB_SAMPLE_RATE;
+ mode->bitrate = 24000;
+ pj_utoa(mode->bitrate, mode->bitrate_str);
+
+ mode = &codec_factory.modes[codec_factory.mode_count++];
+ mode->enabled = PJ_TRUE;
+ mode->pt = PJMEDIA_RTP_PT_G7221C_32;
+ mode->sample_rate = UWB_SAMPLE_RATE;
+ mode->bitrate = 32000;
+ pj_utoa(mode->bitrate, mode->bitrate_str);
+
+ mode = &codec_factory.modes[codec_factory.mode_count++];
+ mode->enabled = PJ_TRUE;
+ mode->pt = PJMEDIA_RTP_PT_G7221C_48;
+ mode->sample_rate = UWB_SAMPLE_RATE;
+ mode->bitrate = 48000;
+ pj_utoa(mode->bitrate, mode->bitrate_str);
+
+ /* Non-standard bitrates */
+
+ /* Bitrate 16kbps is non-standard but rather commonly used. */
+ mode = &codec_factory.modes[codec_factory.mode_count++];
+ mode->enabled = PJ_FALSE;
+ mode->pt = PJMEDIA_RTP_PT_G722_1_16;
+ mode->sample_rate = WB_SAMPLE_RATE;
+ mode->bitrate = 16000;
+ pj_utoa(mode->bitrate, mode->bitrate_str);
+
+ /* Reserved two modes for non-standard bitrates */
+ codec_factory.mode_rsv_start = codec_factory.mode_count;
+ mode = &codec_factory.modes[codec_factory.mode_count++];
+ mode->enabled = PJ_FALSE;
+ mode->pt = PJMEDIA_RTP_PT_G7221_RSV1;
+
+ mode = &codec_factory.modes[codec_factory.mode_count++];
+ mode->enabled = PJ_FALSE;
+ mode->pt = PJMEDIA_RTP_PT_G7221_RSV2;
+
+ pj_assert(codec_factory.mode_count <= MAX_CODEC_MODES);
+
+ /* Create G722.1 codec factory. */
+ codec_factory.base.op = &codec_factory_op;
+ codec_factory.base.factory_data = NULL;
+ codec_factory.endpt = endpt;
+
+ codec_factory.pool = pjmedia_endpt_create_pool(endpt, "G722.1 codec",
+ 4000, 4000);
+ if (!codec_factory.pool)
+ return PJ_ENOMEM;
+
+ /* Create mutex. */
+ status = pj_mutex_create_simple(codec_factory.pool, "G722.1 codec",
+ &codec_factory.mutex);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
+ if (!codec_mgr) {
+ status = PJ_EINVALIDOP;
+ goto on_error;
+ }
+
+ /* Register format match callback. */
+ pj_cstr(&codec_name, CODEC_TAG);
+ status = pjmedia_sdp_neg_register_fmt_match_cb(
+ &codec_name,
+ &pjmedia_codec_g7221_match_sdp);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Register codec factory to endpoint. */
+ status = pjmedia_codec_mgr_register_factory(codec_mgr,
+ &codec_factory.base);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Done. */
+ return PJ_SUCCESS;
+
+on_error:
+ if (codec_factory.mutex) {
+ pj_mutex_destroy(codec_factory.mutex);
+ codec_factory.mutex = NULL;
+ }
+
+ pj_pool_release(codec_factory.pool);
+ codec_factory.pool = NULL;
+ return status;
+}
+
+
+/**
+ * Enable and disable G722.1 modes, including non-standard modes.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_g7221_set_mode(unsigned sample_rate,
+ unsigned bitrate,
+ pj_bool_t enabled)
+{
+ unsigned i;
+
+ /* Validate mode */
+ if (!validate_mode(sample_rate, bitrate))
+ return PJMEDIA_CODEC_EINMODE;
+
+ /* Look up in factory modes table */
+ for (i = 0; i < codec_factory.mode_count; ++i) {
+ if (codec_factory.modes[i].sample_rate == sample_rate &&
+ codec_factory.modes[i].bitrate == bitrate)
+ {
+ codec_factory.modes[i].enabled = enabled;
+ return PJ_SUCCESS;
+ }
+ }
+
+ /* Mode not found in modes table, this may be a request to enable
+ * a non-standard G722.1 mode.
+ */
+
+ /* Non-standard mode need to be initialized first before user
+ * can disable it.
+ */
+ if (!enabled)
+ return PJ_ENOTFOUND;
+
+ /* Initialize a non-standard mode, look for available space. */
+ for (i = codec_factory.mode_rsv_start;
+ i < codec_factory.mode_count; ++i)
+ {
+ if (!codec_factory.modes[i].enabled)
+ {
+ codec_mode *mode = &codec_factory.modes[i];
+ mode->enabled = PJ_TRUE;
+ mode->sample_rate = sample_rate;
+ mode->bitrate = bitrate;
+ pj_utoa(mode->bitrate, mode->bitrate_str);
+
+ return PJ_SUCCESS;
+ }
+ }
+
+ /* No space for non-standard mode. */
+ return PJ_ETOOMANY;
+}
+
+
+/*
+ * Set level adjustment.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_g7221_set_pcm_shift(int val)
+{
+ codec_factory.pcm_shift = val;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Unregister G722.1 codec factory from pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_g7221_deinit(void)
+{
+ pjmedia_codec_mgr *codec_mgr;
+ pj_status_t status;
+
+ if (codec_factory.pool == NULL) {
+ /* Already deinitialized */
+ return PJ_SUCCESS;
+ }
+
+ pj_mutex_lock(codec_factory.mutex);
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(codec_factory.endpt);
+ if (!codec_mgr) {
+ pj_pool_release(codec_factory.pool);
+ codec_factory.pool = NULL;
+ return PJ_EINVALIDOP;
+ }
+
+ /* Unregister G722.1 codec factory. */
+ status = pjmedia_codec_mgr_unregister_factory(codec_mgr,
+ &codec_factory.base);
+
+ /* Destroy mutex. */
+ pj_mutex_destroy(codec_factory.mutex);
+
+ /* Destroy pool. */
+ pj_pool_release(codec_factory.pool);
+ codec_factory.pool = NULL;
+
+ return status;
+}
+
+/*
+ * Check if factory can allocate the specified codec.
+ */
+static pj_status_t test_alloc( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *info )
+{
+ PJ_UNUSED_ARG(factory);
+
+ /* Type MUST be audio. */
+ if (info->type != PJMEDIA_TYPE_AUDIO)
+ return PJMEDIA_CODEC_EUNSUP;
+
+ /* Check encoding name. */
+ if (pj_stricmp2(&info->encoding_name, CODEC_TAG) != 0)
+ return PJMEDIA_CODEC_EUNSUP;
+
+ /* Check clock-rate */
+ if (info->clock_rate != WB_SAMPLE_RATE &&
+ info->clock_rate != UWB_SAMPLE_RATE)
+ {
+ return PJMEDIA_CODEC_EUNSUP;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Generate default attribute.
+ */
+static pj_status_t default_attr ( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr )
+{
+ codec_mode *mode;
+
+ PJ_ASSERT_RETURN(factory==&codec_factory.base, PJ_EINVAL);
+
+ pj_bzero(attr, sizeof(pjmedia_codec_param));
+
+ mode = lookup_mode(id->pt);
+ if (mode == NULL || !mode->enabled)
+ return PJMEDIA_CODEC_EUNSUP;
+
+ attr->info.pt = (pj_uint8_t)id->pt;
+ attr->info.channel_cnt = 1;
+ attr->info.clock_rate = mode->sample_rate;
+ attr->info.max_bps = mode->bitrate;
+ attr->info.avg_bps = mode->bitrate;
+ attr->info.pcm_bits_per_sample = 16;
+ attr->info.frm_ptime = 20;
+
+ /* Default flags. */
+ attr->setting.plc = 1;
+ attr->setting.vad = 0;
+ attr->setting.frm_per_pkt = 1;
+
+ /* Default FMTP setting */
+ attr->setting.dec_fmtp.cnt = 1;
+ attr->setting.dec_fmtp.param[0].name = pj_str("bitrate");
+ attr->setting.dec_fmtp.param[0].val = pj_str(mode->bitrate_str);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Enum codecs supported by this factory.
+ */
+static pj_status_t enum_codecs( pjmedia_codec_factory *factory,
+ unsigned *count,
+ pjmedia_codec_info codecs[])
+{
+ unsigned i, max_cnt;
+
+ PJ_ASSERT_RETURN(factory==&codec_factory.base, PJ_EINVAL);
+ PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL);
+
+ max_cnt = *count;
+ *count = 0;
+
+ for (i=0; (i < codec_factory.mode_count) && (*count < max_cnt); ++i)
+ {
+ if (!codec_factory.modes[i].enabled)
+ continue;
+
+ pj_bzero(&codecs[*count], sizeof(pjmedia_codec_info));
+ codecs[*count].encoding_name = pj_str((char*)CODEC_TAG);
+ codecs[*count].pt = codec_factory.modes[i].pt;
+ codecs[*count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[*count].clock_rate = codec_factory.modes[i].sample_rate;
+ codecs[*count].channel_cnt = 1;
+
+ ++ *count;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Allocate a new codec instance.
+ */
+static pj_status_t alloc_codec( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec)
+{
+ codec_private_t *codec_data;
+ pjmedia_codec *codec;
+ pj_pool_t *pool;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory == &codec_factory.base, PJ_EINVAL);
+
+ pj_mutex_lock(codec_factory.mutex);
+
+ /* Create pool for codec instance */
+ pool = pjmedia_endpt_create_pool(codec_factory.endpt, "G7221", 512, 512);
+ codec = PJ_POOL_ZALLOC_T(pool, pjmedia_codec);
+ codec->op = &codec_op;
+ codec->factory = factory;
+ codec->codec_data = PJ_POOL_ZALLOC_T(pool, codec_private_t);
+ codec_data = (codec_private_t*) codec->codec_data;
+ codec_data->pool = pool;
+
+ /* Create silence detector */
+ status = pjmedia_silence_det_create(pool, id->clock_rate,
+ id->clock_rate * 20 / 1000,
+ &codec_data->vad);
+ if (status != PJ_SUCCESS) {
+ pj_mutex_unlock(codec_factory.mutex);
+ return status;
+ }
+
+ pj_mutex_unlock(codec_factory.mutex);
+
+ *p_codec = codec;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Free codec.
+ */
+static pj_status_t dealloc_codec( pjmedia_codec_factory *factory,
+ pjmedia_codec *codec )
+{
+ codec_private_t *codec_data;
+ pj_pool_t *pool;
+
+ PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory == &codec_factory.base, PJ_EINVAL);
+
+ /* Close codec, if it's not closed. */
+ codec_data = (codec_private_t*) codec->codec_data;
+ pool = codec_data->pool;
+ codec_close(codec);
+
+ /* Release codec pool */
+ pj_pool_release(pool);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Init codec.
+ */
+static pj_status_t codec_init( pjmedia_codec *codec,
+ pj_pool_t *pool )
+{
+ PJ_UNUSED_ARG(codec);
+ PJ_UNUSED_ARG(pool);
+ return PJ_SUCCESS;
+}
+
+/*
+ * Open codec.
+ */
+static pj_status_t codec_open( pjmedia_codec *codec,
+ pjmedia_codec_param *attr )
+{
+ codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+ pj_pool_t *pool;
+ unsigned tmp;
+
+ /* Validation mode first! */
+ if (!validate_mode(attr->info.clock_rate, attr->info.avg_bps))
+ return PJMEDIA_CODEC_EINMODE;
+
+ pool = codec_data->pool;
+
+ /* Initialize common state */
+ codec_data->vad_enabled = (attr->setting.vad != 0);
+ codec_data->plc_enabled = (attr->setting.plc != 0);
+
+ codec_data->bitrate = (pj_uint16_t)attr->info.avg_bps;
+ codec_data->frame_size_bits = (pj_uint16_t)(attr->info.avg_bps*20/1000);
+ codec_data->frame_size = (pj_uint16_t)(codec_data->frame_size_bits>>3);
+ codec_data->samples_per_frame = (pj_uint16_t)
+ (attr->info.clock_rate*20/1000);
+ codec_data->number_of_regions = (pj_uint16_t)
+ (attr->info.clock_rate <= WB_SAMPLE_RATE?
+ NUMBER_OF_REGIONS:MAX_NUMBER_OF_REGIONS);
+ codec_data->pcm_shift = codec_factory.pcm_shift;
+
+ /* Initialize encoder state */
+ tmp = codec_data->samples_per_frame << 1;
+ codec_data->enc_old_frame = (Word16*)pj_pool_zalloc(pool, tmp);
+ codec_data->enc_frame = (Word16*)pj_pool_alloc(pool, tmp);
+
+ /* Initialize decoder state */
+ tmp = codec_data->samples_per_frame;
+ codec_data->dec_old_frame = (Word16*)pj_pool_zalloc(pool, tmp);
+
+ tmp = codec_data->samples_per_frame << 1;
+ codec_data->dec_old_mlt_coefs = (Word16*)pj_pool_zalloc(pool, tmp);
+
+ codec_data->dec_randobj.seed0 = 1;
+ codec_data->dec_randobj.seed1 = 1;
+ codec_data->dec_randobj.seed2 = 1;
+ codec_data->dec_randobj.seed3 = 1;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Close codec.
+ */
+static pj_status_t codec_close( pjmedia_codec *codec )
+{
+ PJ_UNUSED_ARG(codec);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Modify codec settings.
+ */
+static pj_status_t codec_modify( pjmedia_codec *codec,
+ const pjmedia_codec_param *attr )
+{
+ codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+
+ codec_data->vad_enabled = (attr->setting.vad != 0);
+ codec_data->plc_enabled = (attr->setting.plc != 0);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get frames in the packet.
+ */
+static pj_status_t codec_parse( pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[])
+{
+ codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+ unsigned count = 0;
+
+ PJ_ASSERT_RETURN(frame_cnt, PJ_EINVAL);
+
+ /* Parse based on fixed frame size. */
+ while (pkt_size >= codec_data->frame_size && count < *frame_cnt) {
+ frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frames[count].buf = pkt;
+ frames[count].size = codec_data->frame_size;
+ frames[count].timestamp.u64 = ts->u64 +
+ count * codec_data->samples_per_frame;
+
+ pkt = (pj_uint8_t*)pkt + codec_data->frame_size;
+ pkt_size -= codec_data->frame_size;
+
+ ++count;
+ }
+
+ pj_assert(pkt_size == 0);
+ *frame_cnt = count;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Encode frames.
+ */
+static pj_status_t codec_encode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+ unsigned nsamples, processed;
+
+ /* Check frame in & out size */
+ nsamples = input->size >> 1;
+ PJ_ASSERT_RETURN(nsamples % codec_data->samples_per_frame == 0,
+ PJMEDIA_CODEC_EPCMFRMINLEN);
+ PJ_ASSERT_RETURN(output_buf_len >= codec_data->frame_size * nsamples /
+ codec_data->samples_per_frame,
+ PJMEDIA_CODEC_EFRMTOOSHORT);
+
+ /* Apply silence detection if VAD is enabled */
+ if (codec_data->vad_enabled) {
+ pj_bool_t is_silence;
+ pj_int32_t silence_duration;
+
+ pj_assert(codec_data->vad);
+
+ silence_duration = pj_timestamp_diff32(&codec_data->last_tx,
+ &input->timestamp);
+
+ is_silence = pjmedia_silence_det_detect(codec_data->vad,
+ (const pj_int16_t*) input->buf,
+ (input->size >> 1),
+ NULL);
+ if (is_silence &&
+ (PJMEDIA_CODEC_MAX_SILENCE_PERIOD == -1 ||
+ silence_duration < (PJMEDIA_CODEC_MAX_SILENCE_PERIOD *
+ (int)codec_data->samples_per_frame / 20)))
+ {
+ output->type = PJMEDIA_FRAME_TYPE_NONE;
+ output->buf = NULL;
+ output->size = 0;
+ output->timestamp = input->timestamp;
+ return PJ_SUCCESS;
+ } else {
+ codec_data->last_tx = input->timestamp;
+ }
+ }
+
+ processed = 0;
+ output->size = 0;
+ while (processed < nsamples) {
+ Word16 mlt_coefs[MAX_SAMPLES_PER_FRAME];
+ Word16 mag_shift;
+ const Word16 *pcm_input;
+ pj_int8_t *out_bits;
+
+ pcm_input = (const Word16*)input->buf + processed;
+ out_bits = (pj_int8_t*)output->buf + output->size;
+
+ /* Encoder adjust the input signal level */
+ if (codec_data->pcm_shift) {
+ unsigned i;
+ for (i=0; i<codec_data->samples_per_frame; ++i) {
+ codec_data->enc_frame[i] =
+ (Word16)(pcm_input[i] >> codec_data->pcm_shift);
+ }
+ pcm_input = codec_data->enc_frame;
+ }
+
+ /* Convert input samples to rmlt coefs */
+ mag_shift = samples_to_rmlt_coefs(pcm_input,
+ codec_data->enc_old_frame,
+ mlt_coefs,
+ codec_data->samples_per_frame);
+
+ /* Encode the mlt coefs. Note that encoder output stream is
+ * 16 bit array, so we need to take care about endianness.
+ */
+ encoder(codec_data->frame_size_bits,
+ codec_data->number_of_regions,
+ mlt_coefs,
+ mag_shift,
+ (Word16*)out_bits);
+
+ /* Encoder output are in native host byte order, while ITU says
+ * it must be in network byte order (MSB first).
+ */
+ swap_bytes((pj_uint16_t*)out_bits, codec_data->frame_size/2);
+
+ processed += codec_data->samples_per_frame;
+ output->size += codec_data->frame_size;
+ }
+
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->timestamp = input->timestamp;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Decode frame.
+ */
+static pj_status_t codec_decode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+ Word16 mlt_coefs[MAX_SAMPLES_PER_FRAME];
+ Word16 mag_shift;
+ Bit_Obj bitobj;
+ Word16 frame_error_flag = 0;
+
+ /* Check frame out length size */
+ PJ_ASSERT_RETURN(output_buf_len >=
+ (unsigned)(codec_data->samples_per_frame<<1),
+ PJMEDIA_CODEC_EPCMTOOSHORT);
+
+ /* If input is NULL, perform PLC by settting frame_error_flag to 1 */
+ if (input) {
+ /* Check frame in length size */
+ PJ_ASSERT_RETURN((pj_uint16_t)input->size == codec_data->frame_size,
+ PJMEDIA_CODEC_EFRMINLEN);
+
+ /* Decoder requires input of 16-bits array in native host byte
+ * order, while the frame received from the network are in
+ * network byte order (MSB first).
+ */
+ swap_bytes((pj_uint16_t*)input->buf, codec_data->frame_size/2);
+
+ bitobj.code_word_ptr = (Word16*)input->buf;
+ bitobj.current_word = *bitobj.code_word_ptr;
+ bitobj.code_bit_count = 0;
+ bitobj.number_of_bits_left = codec_data->frame_size_bits;
+
+ output->timestamp = input->timestamp;
+ } else {
+ pj_bzero(&bitobj, sizeof(bitobj));
+ frame_error_flag = 1;
+ }
+
+ /* Process the input frame to get mlt coefs */
+ decoder(&bitobj,
+ &codec_data->dec_randobj,
+ codec_data->number_of_regions,
+ mlt_coefs,
+ &mag_shift,
+ &codec_data->dec_old_mag_shift,
+ codec_data->dec_old_mlt_coefs,
+ frame_error_flag);
+
+ /* Convert the mlt_coefs to PCM samples */
+ rmlt_coefs_to_samples(mlt_coefs,
+ codec_data->dec_old_frame,
+ (Word16*)output->buf,
+ codec_data->samples_per_frame,
+ mag_shift);
+
+ /* Decoder adjust PCM signal */
+ if (codec_data->pcm_shift) {
+ unsigned i;
+ pj_int16_t *buf = (Word16*)output->buf;
+
+ for (i=0; i<codec_data->samples_per_frame; ++i) {
+ buf[i] <<= codec_data->pcm_shift;
+ }
+ }
+
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->size = codec_data->samples_per_frame << 1;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Recover lost frame.
+ */
+static pj_status_t codec_recover( pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+
+ /* Use native PLC when PLC is enabled. */
+ if (codec_data->plc_enabled)
+ return codec_decode(codec, NULL, output_buf_len, output);
+
+ /* Otherwise just return zero-fill frame. */
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->size = codec_data->samples_per_frame << 1;
+
+ pjmedia_zero_samples((pj_int16_t*)output->buf,
+ codec_data->samples_per_frame);
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_HAS_G7221_CODEC */
diff --git a/pjmedia/src/pjmedia-codec/g7221_sdp_match.c b/pjmedia/src/pjmedia-codec/g7221_sdp_match.c
new file mode 100644
index 0000000..21e5b2c
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/g7221_sdp_match.c
@@ -0,0 +1,91 @@
+/* $Id: g7221_sdp_match.c 3911 2011-12-15 06:45:23Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia-codec/g7221_sdp_match.h>
+#include <pjmedia/errno.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#define GET_FMTP_IVAL_BASE(ival, base, fmtp, param, default_val) \
+ do { \
+ pj_str_t s; \
+ char *p; \
+ p = pj_stristr(&fmtp.fmt_param, &param); \
+ if (!p) { \
+ ival = default_val; \
+ break; \
+ } \
+ pj_strset(&s, p + param.slen, fmtp.fmt_param.slen - \
+ (p - fmtp.fmt_param.ptr) - param.slen); \
+ ival = pj_strtoul2(&s, NULL, base); \
+ } while (0)
+
+#define GET_FMTP_IVAL(ival, fmtp, param, default_val) \
+ GET_FMTP_IVAL_BASE(ival, 10, fmtp, param, default_val)
+
+
+
+PJ_DEF(pj_status_t) pjmedia_codec_g7221_match_sdp(pj_pool_t *pool,
+ pjmedia_sdp_media *offer,
+ unsigned o_fmt_idx,
+ pjmedia_sdp_media *answer,
+ unsigned a_fmt_idx,
+ unsigned option)
+{
+ const pjmedia_sdp_attr *attr_ans;
+ const pjmedia_sdp_attr *attr_ofr;
+ pjmedia_sdp_fmtp fmtp;
+ unsigned a_bitrate, o_bitrate;
+ const pj_str_t bitrate = {"bitrate=", 8};
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(pool);
+ PJ_UNUSED_ARG(option);
+
+ /* Parse offer */
+ attr_ofr = pjmedia_sdp_media_find_attr2(offer, "fmtp",
+ &offer->desc.fmt[o_fmt_idx]);
+ if (!attr_ofr)
+ return PJMEDIA_SDP_EINFMTP;
+
+ status = pjmedia_sdp_attr_get_fmtp(attr_ofr, &fmtp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ GET_FMTP_IVAL(o_bitrate, fmtp, bitrate, 0);
+
+ /* Parse answer */
+ attr_ans = pjmedia_sdp_media_find_attr2(answer, "fmtp",
+ &answer->desc.fmt[a_fmt_idx]);
+ if (!attr_ans)
+ return PJMEDIA_SDP_EINFMTP;
+
+ status = pjmedia_sdp_attr_get_fmtp(attr_ans, &fmtp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ GET_FMTP_IVAL(a_bitrate, fmtp, bitrate, 0);
+
+ /* Compare bitrate in answer and offer. */
+ if (a_bitrate != o_bitrate)
+ return PJMEDIA_SDP_EFORMATNOTEQUAL;
+
+ return PJ_SUCCESS;
+}
diff --git a/pjmedia/src/pjmedia-codec/gsm.c b/pjmedia/src/pjmedia-codec/gsm.c
new file mode 100644
index 0000000..c84f241
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/gsm.c
@@ -0,0 +1,645 @@
+/* $Id: gsm.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia-codec/gsm.h>
+#include <pjmedia/codec.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/endpoint.h>
+#include <pjmedia/plc.h>
+#include <pjmedia/port.h>
+#include <pjmedia/silencedet.h>
+#include <pj/assert.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/os.h>
+
+/*
+ * Only build this file if PJMEDIA_HAS_GSM_CODEC != 0
+ */
+#if defined(PJMEDIA_HAS_GSM_CODEC) && PJMEDIA_HAS_GSM_CODEC != 0
+
+#if defined(PJMEDIA_EXTERNAL_GSM_CODEC) && PJMEDIA_EXTERNAL_GSM_CODEC
+# if PJMEDIA_EXTERNAL_GSM_GSM_H
+# include <gsm/gsm.h>
+# elif PJMEDIA_EXTERNAL_GSM_H
+# include <gsm.h>
+# else
+# error Please set the location of gsm.h
+# endif
+#else
+# include "../../third_party/gsm/inc/gsm.h"
+#endif
+
+/* We removed PLC in 0.6 (and re-enabled it again in 0.9!) */
+#define PLC_DISABLED 0
+
+
+/* Prototypes for GSM factory */
+static pj_status_t gsm_test_alloc( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id );
+static pj_status_t gsm_default_attr( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr );
+static pj_status_t gsm_enum_codecs( pjmedia_codec_factory *factory,
+ unsigned *count,
+ pjmedia_codec_info codecs[]);
+static pj_status_t gsm_alloc_codec( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec);
+static pj_status_t gsm_dealloc_codec( pjmedia_codec_factory *factory,
+ pjmedia_codec *codec );
+
+/* Prototypes for GSM implementation. */
+static pj_status_t gsm_codec_init( pjmedia_codec *codec,
+ pj_pool_t *pool );
+static pj_status_t gsm_codec_open( pjmedia_codec *codec,
+ pjmedia_codec_param *attr );
+static pj_status_t gsm_codec_close( pjmedia_codec *codec );
+static pj_status_t gsm_codec_modify(pjmedia_codec *codec,
+ const pjmedia_codec_param *attr );
+static pj_status_t gsm_codec_parse( pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[]);
+static pj_status_t gsm_codec_encode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+static pj_status_t gsm_codec_decode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+#if !PLC_DISABLED
+static pj_status_t gsm_codec_recover(pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+#endif
+
+/* Definition for GSM codec operations. */
+static pjmedia_codec_op gsm_op =
+{
+ &gsm_codec_init,
+ &gsm_codec_open,
+ &gsm_codec_close,
+ &gsm_codec_modify,
+ &gsm_codec_parse,
+ &gsm_codec_encode,
+ &gsm_codec_decode,
+#if !PLC_DISABLED
+ &gsm_codec_recover
+#else
+ NULL
+#endif
+};
+
+/* Definition for GSM codec factory operations. */
+static pjmedia_codec_factory_op gsm_factory_op =
+{
+ &gsm_test_alloc,
+ &gsm_default_attr,
+ &gsm_enum_codecs,
+ &gsm_alloc_codec,
+ &gsm_dealloc_codec,
+ &pjmedia_codec_gsm_deinit
+};
+
+/* GSM factory */
+static struct gsm_codec_factory
+{
+ pjmedia_codec_factory base;
+ pjmedia_endpt *endpt;
+ pj_pool_t *pool;
+ pj_mutex_t *mutex;
+ pjmedia_codec codec_list;
+} gsm_codec_factory;
+
+
+/* GSM codec private data. */
+struct gsm_data
+{
+ struct gsm_state *encoder;
+ struct gsm_state *decoder;
+ pj_bool_t plc_enabled;
+#if !PLC_DISABLED
+ pjmedia_plc *plc;
+#endif
+ pj_bool_t vad_enabled;
+ pjmedia_silence_det *vad;
+ pj_timestamp last_tx;
+};
+
+
+
+/*
+ * Initialize and register GSM codec factory to pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_gsm_init( pjmedia_endpt *endpt )
+{
+ pjmedia_codec_mgr *codec_mgr;
+ pj_status_t status;
+
+ if (gsm_codec_factory.pool != NULL)
+ return PJ_SUCCESS;
+
+ /* Create GSM codec factory. */
+ gsm_codec_factory.base.op = &gsm_factory_op;
+ gsm_codec_factory.base.factory_data = NULL;
+ gsm_codec_factory.endpt = endpt;
+
+ gsm_codec_factory.pool = pjmedia_endpt_create_pool(endpt, "gsm", 4000,
+ 4000);
+ if (!gsm_codec_factory.pool)
+ return PJ_ENOMEM;
+
+ pj_list_init(&gsm_codec_factory.codec_list);
+
+ /* Create mutex. */
+ status = pj_mutex_create_simple(gsm_codec_factory.pool, "gsm",
+ &gsm_codec_factory.mutex);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
+ if (!codec_mgr) {
+ status = PJ_EINVALIDOP;
+ goto on_error;
+ }
+
+ /* Register codec factory to endpoint. */
+ status = pjmedia_codec_mgr_register_factory(codec_mgr,
+ &gsm_codec_factory.base);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Done. */
+ return PJ_SUCCESS;
+
+on_error:
+ pj_pool_release(gsm_codec_factory.pool);
+ gsm_codec_factory.pool = NULL;
+ return status;
+}
+
+
+
+/*
+ * Unregister GSM codec factory from pjmedia endpoint and deinitialize
+ * the GSM codec library.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_gsm_deinit(void)
+{
+ pjmedia_codec_mgr *codec_mgr;
+ pj_status_t status;
+
+ if (gsm_codec_factory.pool == NULL)
+ return PJ_SUCCESS;
+
+ /* We don't want to deinit if there's outstanding codec. */
+ /* This is silly, as we'll always have codec in the list if
+ we ever allocate a codec! A better behavior maybe is to
+ deallocate all codecs in the list.
+ pj_mutex_lock(gsm_codec_factory.mutex);
+ if (!pj_list_empty(&gsm_codec_factory.codec_list)) {
+ pj_mutex_unlock(gsm_codec_factory.mutex);
+ return PJ_EBUSY;
+ }
+ */
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(gsm_codec_factory.endpt);
+ if (!codec_mgr) {
+ pj_pool_release(gsm_codec_factory.pool);
+ gsm_codec_factory.pool = NULL;
+ return PJ_EINVALIDOP;
+ }
+
+ /* Unregister GSM codec factory. */
+ status = pjmedia_codec_mgr_unregister_factory(codec_mgr,
+ &gsm_codec_factory.base);
+
+ /* Destroy mutex. */
+ pj_mutex_destroy(gsm_codec_factory.mutex);
+
+ /* Destroy pool. */
+ pj_pool_release(gsm_codec_factory.pool);
+ gsm_codec_factory.pool = NULL;
+
+ return status;
+}
+
+/*
+ * Check if factory can allocate the specified codec.
+ */
+static pj_status_t gsm_test_alloc( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *info )
+{
+ PJ_UNUSED_ARG(factory);
+
+ /* Check payload type. */
+ if (info->pt != PJMEDIA_RTP_PT_GSM)
+ return PJMEDIA_CODEC_EUNSUP;
+
+ /* Ignore the rest, since it's static payload type. */
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Generate default attribute.
+ */
+static pj_status_t gsm_default_attr (pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr )
+{
+ PJ_UNUSED_ARG(factory);
+ PJ_UNUSED_ARG(id);
+
+ pj_bzero(attr, sizeof(pjmedia_codec_param));
+ attr->info.clock_rate = 8000;
+ attr->info.channel_cnt = 1;
+ attr->info.avg_bps = 13200;
+ attr->info.max_bps = 13200;
+ attr->info.pcm_bits_per_sample = 16;
+ attr->info.frm_ptime = 20;
+ attr->info.pt = PJMEDIA_RTP_PT_GSM;
+
+ attr->setting.frm_per_pkt = 1;
+ attr->setting.vad = 1;
+#if !PLC_DISABLED
+ attr->setting.plc = 1;
+#endif
+
+ /* Default all other flag bits disabled. */
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Enum codecs supported by this factory (i.e. only GSM!).
+ */
+static pj_status_t gsm_enum_codecs(pjmedia_codec_factory *factory,
+ unsigned *count,
+ pjmedia_codec_info codecs[])
+{
+ PJ_UNUSED_ARG(factory);
+ PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL);
+
+ pj_bzero(&codecs[0], sizeof(pjmedia_codec_info));
+ codecs[0].encoding_name = pj_str("GSM");
+ codecs[0].pt = PJMEDIA_RTP_PT_GSM;
+ codecs[0].type = PJMEDIA_TYPE_AUDIO;
+ codecs[0].clock_rate = 8000;
+ codecs[0].channel_cnt = 1;
+
+ *count = 1;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Allocate a new GSM codec instance.
+ */
+static pj_status_t gsm_alloc_codec( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec)
+{
+ pjmedia_codec *codec;
+ struct gsm_data *gsm_data;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory == &gsm_codec_factory.base, PJ_EINVAL);
+
+
+ pj_mutex_lock(gsm_codec_factory.mutex);
+
+ /* Get free nodes, if any. */
+ if (!pj_list_empty(&gsm_codec_factory.codec_list)) {
+ codec = gsm_codec_factory.codec_list.next;
+ pj_list_erase(codec);
+ } else {
+ codec = PJ_POOL_ZALLOC_T(gsm_codec_factory.pool, pjmedia_codec);
+ PJ_ASSERT_RETURN(codec != NULL, PJ_ENOMEM);
+ codec->op = &gsm_op;
+ codec->factory = factory;
+
+ gsm_data = PJ_POOL_ZALLOC_T(gsm_codec_factory.pool, struct gsm_data);
+ codec->codec_data = gsm_data;
+
+#if !PLC_DISABLED
+ /* Create PLC */
+ status = pjmedia_plc_create(gsm_codec_factory.pool, 8000,
+ 160, 0, &gsm_data->plc);
+ if (status != PJ_SUCCESS) {
+ pj_mutex_unlock(gsm_codec_factory.mutex);
+ return status;
+ }
+#endif
+
+ /* Create silence detector */
+ status = pjmedia_silence_det_create(gsm_codec_factory.pool,
+ 8000, 160,
+ &gsm_data->vad);
+ if (status != PJ_SUCCESS) {
+ pj_mutex_unlock(gsm_codec_factory.mutex);
+ return status;
+ }
+ }
+
+ pj_mutex_unlock(gsm_codec_factory.mutex);
+
+ *p_codec = codec;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Free codec.
+ */
+static pj_status_t gsm_dealloc_codec( pjmedia_codec_factory *factory,
+ pjmedia_codec *codec )
+{
+ struct gsm_data *gsm_data;
+ int i;
+
+ PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory == &gsm_codec_factory.base, PJ_EINVAL);
+
+ gsm_data = (struct gsm_data*) codec->codec_data;
+
+ /* Close codec, if it's not closed. */
+ gsm_codec_close(codec);
+
+#if !PLC_DISABLED
+ /* Clear left samples in the PLC, since codec+plc will be reused
+ * next time.
+ */
+ for (i=0; i<2; ++i) {
+ pj_int16_t frame[160];
+ pjmedia_zero_samples(frame, PJ_ARRAY_SIZE(frame));
+ pjmedia_plc_save(gsm_data->plc, frame);
+ }
+#else
+ PJ_UNUSED_ARG(i);
+#endif
+
+ /* Re-init silence_period */
+ pj_set_timestamp32(&gsm_data->last_tx, 0, 0);
+
+ /* Put in the free list. */
+ pj_mutex_lock(gsm_codec_factory.mutex);
+ pj_list_push_front(&gsm_codec_factory.codec_list, codec);
+ pj_mutex_unlock(gsm_codec_factory.mutex);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Init codec.
+ */
+static pj_status_t gsm_codec_init( pjmedia_codec *codec,
+ pj_pool_t *pool )
+{
+ PJ_UNUSED_ARG(codec);
+ PJ_UNUSED_ARG(pool);
+ return PJ_SUCCESS;
+}
+
+/*
+ * Open codec.
+ */
+static pj_status_t gsm_codec_open( pjmedia_codec *codec,
+ pjmedia_codec_param *attr )
+{
+ struct gsm_data *gsm_data = (struct gsm_data*) codec->codec_data;
+
+ pj_assert(gsm_data != NULL);
+ pj_assert(gsm_data->encoder == NULL && gsm_data->decoder == NULL);
+
+ gsm_data->encoder = gsm_create();
+ if (!gsm_data->encoder)
+ return PJMEDIA_CODEC_EFAILED;
+
+ gsm_data->decoder = gsm_create();
+ if (!gsm_data->decoder)
+ return PJMEDIA_CODEC_EFAILED;
+
+ gsm_data->vad_enabled = (attr->setting.vad != 0);
+ gsm_data->plc_enabled = (attr->setting.plc != 0);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Close codec.
+ */
+static pj_status_t gsm_codec_close( pjmedia_codec *codec )
+{
+ struct gsm_data *gsm_data = (struct gsm_data*) codec->codec_data;
+
+ pj_assert(gsm_data != NULL);
+
+ if (gsm_data->encoder) {
+ gsm_destroy(gsm_data->encoder);
+ gsm_data->encoder = NULL;
+ }
+ if (gsm_data->decoder) {
+ gsm_destroy(gsm_data->decoder);
+ gsm_data->decoder = NULL;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Modify codec settings.
+ */
+static pj_status_t gsm_codec_modify(pjmedia_codec *codec,
+ const pjmedia_codec_param *attr )
+{
+ struct gsm_data *gsm_data = (struct gsm_data*) codec->codec_data;
+
+ pj_assert(gsm_data != NULL);
+ pj_assert(gsm_data->encoder != NULL && gsm_data->decoder != NULL);
+
+ gsm_data->vad_enabled = (attr->setting.vad != 0);
+ gsm_data->plc_enabled = (attr->setting.plc != 0);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get frames in the packet.
+ */
+static pj_status_t gsm_codec_parse( pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[])
+{
+ unsigned count = 0;
+
+ PJ_UNUSED_ARG(codec);
+
+ PJ_ASSERT_RETURN(frame_cnt, PJ_EINVAL);
+
+ while (pkt_size >= 33 && count < *frame_cnt) {
+ frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frames[count].buf = pkt;
+ frames[count].size = 33;
+ frames[count].timestamp.u64 = ts->u64 + count * 160;
+
+ pkt = ((char*)pkt) + 33;
+ pkt_size -= 33;
+
+ ++count;
+ }
+
+ *frame_cnt = count;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Encode frame.
+ */
+static pj_status_t gsm_codec_encode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ struct gsm_data *gsm_data = (struct gsm_data*) codec->codec_data;
+ pj_int16_t *pcm_in;
+ unsigned in_size;
+
+ pj_assert(gsm_data && input && output);
+
+ pcm_in = (pj_int16_t*)input->buf;
+ in_size = input->size;
+
+ PJ_ASSERT_RETURN(in_size % 320 == 0, PJMEDIA_CODEC_EPCMFRMINLEN);
+ PJ_ASSERT_RETURN(output_buf_len >= 33 * in_size/320,
+ PJMEDIA_CODEC_EFRMTOOSHORT);
+
+ /* Detect silence */
+ if (gsm_data->vad_enabled) {
+ pj_bool_t is_silence;
+ pj_int32_t silence_duration;
+
+ silence_duration = pj_timestamp_diff32(&gsm_data->last_tx,
+ &input->timestamp);
+
+ is_silence = pjmedia_silence_det_detect(gsm_data->vad,
+ (const pj_int16_t*) input->buf,
+ (input->size >> 1),
+ NULL);
+ if (is_silence &&
+ (PJMEDIA_CODEC_MAX_SILENCE_PERIOD == -1 ||
+ silence_duration < PJMEDIA_CODEC_MAX_SILENCE_PERIOD*8000/1000))
+ {
+ output->type = PJMEDIA_FRAME_TYPE_NONE;
+ output->buf = NULL;
+ output->size = 0;
+ output->timestamp = input->timestamp;
+ return PJ_SUCCESS;
+ } else {
+ gsm_data->last_tx = input->timestamp;
+ }
+ }
+
+ /* Encode */
+ output->size = 0;
+ while (in_size >= 320) {
+ gsm_encode(gsm_data->encoder, pcm_in,
+ (unsigned char*)output->buf + output->size);
+ pcm_in += 160;
+ output->size += 33;
+ in_size -= 320;
+ }
+
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->timestamp = input->timestamp;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Decode frame.
+ */
+static pj_status_t gsm_codec_decode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ struct gsm_data *gsm_data = (struct gsm_data*) codec->codec_data;
+
+ pj_assert(gsm_data != NULL);
+ PJ_ASSERT_RETURN(input && output, PJ_EINVAL);
+
+ if (output_buf_len < 320)
+ return PJMEDIA_CODEC_EPCMTOOSHORT;
+
+ if (input->size < 33)
+ return PJMEDIA_CODEC_EFRMTOOSHORT;
+
+ gsm_decode(gsm_data->decoder,
+ (unsigned char*)input->buf,
+ (short*)output->buf);
+
+ output->size = 320;
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->timestamp = input->timestamp;
+
+#if !PLC_DISABLED
+ if (gsm_data->plc_enabled)
+ pjmedia_plc_save( gsm_data->plc, (pj_int16_t*)output->buf);
+#endif
+
+ return PJ_SUCCESS;
+}
+
+
+#if !PLC_DISABLED
+/*
+ * Recover lost frame.
+ */
+static pj_status_t gsm_codec_recover(pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ struct gsm_data *gsm_data = (struct gsm_data*) codec->codec_data;
+
+ PJ_ASSERT_RETURN(gsm_data->plc_enabled, PJ_EINVALIDOP);
+
+ PJ_ASSERT_RETURN(output_buf_len >= 320, PJMEDIA_CODEC_EPCMTOOSHORT);
+
+ pjmedia_plc_generate(gsm_data->plc, (pj_int16_t*)output->buf);
+ output->size = 320;
+
+ return PJ_SUCCESS;
+}
+#endif
+
+
+#endif /* PJMEDIA_HAS_GSM_CODEC */
+
diff --git a/pjmedia/src/pjmedia-codec/h263_packetizer.c b/pjmedia/src/pjmedia-codec/h263_packetizer.c
new file mode 100644
index 0000000..a953242
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/h263_packetizer.c
@@ -0,0 +1,294 @@
+/* $Id: h263_packetizer.c 4006 2012-04-02 08:40:54Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia-codec/h263_packetizer.h>
+#include <pjmedia/types.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/string.h>
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+
+#define THIS_FILE "h263_packetizer.c"
+
+
+/* H.263 packetizer definition */
+struct pjmedia_h263_packetizer {
+ /* Current settings */
+ pjmedia_h263_packetizer_cfg cfg;
+
+ /* Unpacketizer state */
+ unsigned unpack_last_sync_pos;
+ pj_bool_t unpack_prev_lost;
+};
+
+
+/*
+ * Find synchronization point (PSC, slice, GSBC, EOS, EOSBS) in H.263
+ * bitstream.
+ */
+static pj_uint8_t* find_sync_point(pj_uint8_t *data,
+ pj_size_t data_len)
+{
+ pj_uint8_t *p = data, *end = data+data_len-1;
+
+ while (p < end && (*p || *(p+1)))
+ ++p;
+
+ if (p == end)
+ return NULL;
+
+ return p;
+}
+
+
+/*
+ * Find synchronization point (PSC, slice, GSBC, EOS, EOSBS) in H.263
+ * bitstream, in reversed manner.
+ */
+static pj_uint8_t* find_sync_point_rev(pj_uint8_t *data,
+ pj_size_t data_len)
+{
+ pj_uint8_t *p = data+data_len-2;
+
+ while (p >= data && (*p || *(p+1)))
+ --p;
+
+ if (p < data)
+ return (data + data_len);
+
+ return p;
+}
+
+
+/*
+ * Create H263 packetizer.
+ */
+PJ_DEF(pj_status_t) pjmedia_h263_packetizer_create(
+ pj_pool_t *pool,
+ const pjmedia_h263_packetizer_cfg *cfg,
+ pjmedia_h263_packetizer **p)
+{
+ pjmedia_h263_packetizer *p_;
+
+ PJ_ASSERT_RETURN(pool && p, PJ_EINVAL);
+
+ if (cfg && cfg->mode != PJMEDIA_H263_PACKETIZER_MODE_RFC4629)
+ return PJ_ENOTSUP;
+
+ p_ = PJ_POOL_ZALLOC_T(pool, pjmedia_h263_packetizer);
+ if (cfg) {
+ pj_memcpy(&p_->cfg, cfg, sizeof(*cfg));
+ } else {
+ p_->cfg.mode = PJMEDIA_H263_PACKETIZER_MODE_RFC4629;
+ p_->cfg.mtu = PJMEDIA_MAX_VID_PAYLOAD_SIZE;
+ }
+
+ *p = p_;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Generate an RTP payload from H.263 frame bitstream, in-place processing.
+ */
+PJ_DEF(pj_status_t) pjmedia_h263_packetize(pjmedia_h263_packetizer *pktz,
+ pj_uint8_t *bits,
+ pj_size_t bits_len,
+ unsigned *pos,
+ const pj_uint8_t **payload,
+ pj_size_t *payload_len)
+{
+ pj_uint8_t *p, *end;
+
+ pj_assert(pktz && bits && pos && payload && payload_len);
+ pj_assert(*pos <= bits_len);
+
+ p = bits + *pos;
+ end = bits + bits_len;
+
+ /* Put two octets payload header */
+ if ((end-p > 2) && *p==0 && *(p+1)==0) {
+ /* The bitstream starts with synchronization point, just override
+ * the two zero octets (sync point mark) for payload header.
+ */
+ *p = 0x04;
+ } else {
+ /* Not started in synchronization point, we will use two octets
+ * preceeding the bitstream for payload header!
+ */
+
+ if (*pos < 2) {
+ /* Invalid H263 bitstream, it's not started with PSC */
+ return PJ_EINVAL;
+ }
+
+ p -= 2;
+ *p = 0;
+ }
+ *(p+1) = 0;
+
+ /* When bitstream truncation needed because of payload length/MTU
+ * limitation, try to use sync point for the payload boundary.
+ */
+ if (end-p > pktz->cfg.mtu) {
+ end = find_sync_point_rev(p+2, pktz->cfg.mtu-2);
+ }
+
+ *payload = p;
+ *payload_len = end-p;
+ *pos = end - bits;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Append an RTP payload to a H.263 picture bitstream.
+ */
+PJ_DEF(pj_status_t) pjmedia_h263_unpacketize (pjmedia_h263_packetizer *pktz,
+ const pj_uint8_t *payload,
+ pj_size_t payload_len,
+ pj_uint8_t *bits,
+ pj_size_t bits_size,
+ unsigned *pos)
+{
+ pj_uint8_t P, V, PLEN;
+ const pj_uint8_t *p = payload;
+ pj_uint8_t *q;
+
+ q = bits + *pos;
+
+ /* Check if this is a missing/lost packet */
+ if (payload == NULL) {
+ pktz->unpack_prev_lost = PJ_TRUE;
+ return PJ_SUCCESS;
+ }
+
+ /* H263 payload header size is two octets */
+ if (payload_len < 2) {
+ /* Invalid bitstream, discard this payload */
+ pktz->unpack_prev_lost = PJ_TRUE;
+ return PJ_EINVAL;
+ }
+
+ /* Reset last sync point for every new picture bitstream */
+ if (*pos == 0)
+ pktz->unpack_last_sync_pos = 0;
+
+ /* Get payload header info */
+ P = *p & 0x04;
+ V = *p & 0x02;
+ PLEN = ((*p & 0x01) << 5) + ((*(p+1) & 0xF8)>>3);
+
+ /* Get start bitstream pointer */
+ p += 2; /* Skip payload header */
+ if (V)
+ p += 1; /* Skip VRC data */
+ if (PLEN)
+ p += PLEN; /* Skip extra picture header data */
+
+ /* Get bitstream length */
+ if (payload_len > (pj_size_t)(p - payload)) {
+ payload_len -= (p - payload);
+ } else {
+ /* Invalid bitstream, discard this payload */
+ pktz->unpack_prev_lost = PJ_TRUE;
+ return PJ_EINVAL;
+ }
+
+ /* Validate bitstream length */
+ if (bits_size < *pos + payload_len + 2) {
+ /* Insufficient bistream buffer, discard this payload */
+ pj_assert(!"Insufficient H.263 bitstream buffer");
+ pktz->unpack_prev_lost = PJ_TRUE;
+ return PJ_ETOOSMALL;
+ }
+
+ /* Start writing bitstream */
+
+ /* No sync point flag */
+ if (!P) {
+ if (*pos == 0) {
+ /* Previous packet must be lost */
+ pktz->unpack_prev_lost = PJ_TRUE;
+
+ /* If there is extra picture header, let's use it. */
+ if (PLEN) {
+ /* Write two zero octets for PSC */
+ *q++ = 0;
+ *q++ = 0;
+ /* Copy the picture header */
+ p -= PLEN;
+ pj_memcpy(q, p, PLEN);
+ p += PLEN;
+ q += PLEN;
+ }
+ } else if (pktz->unpack_prev_lost) {
+ /* If prev packet was lost, revert the bitstream pointer to
+ * the last sync point.
+ */
+ pj_assert(pktz->unpack_last_sync_pos <= *pos);
+ q = bits + pktz->unpack_last_sync_pos;
+ }
+
+ /* There was packet lost, see if this payload contain sync point
+ * (usable data).
+ */
+ if (pktz->unpack_prev_lost) {
+ pj_uint8_t *sync;
+ sync = find_sync_point((pj_uint8_t*)p, payload_len);
+ if (sync) {
+ /* Got sync point, update P/sync-point flag */
+ P = 1;
+ /* Skip the two zero octets */
+ sync += 2;
+ /* Update payload length and start bitstream pointer */
+ payload_len -= (sync - p);
+ p = sync;
+ } else {
+ /* No sync point in it, just discard this payload */
+ return PJ_EIGNORED;
+ }
+ }
+ }
+
+ /* Write two zero octets when payload flagged with sync point */
+ if (P) {
+ pktz->unpack_last_sync_pos = q - bits;
+ *q++ = 0;
+ *q++ = 0;
+ }
+
+ /* Write the payload to the bitstream */
+ pj_memcpy(q, p, payload_len);
+ q += payload_len;
+
+ /* Update the bitstream writing offset */
+ *pos = q - bits;
+
+ pktz->unpack_prev_lost = PJ_FALSE;
+
+ return PJ_SUCCESS;
+}
+
+
+#endif /* PJMEDIA_HAS_VIDEO */
diff --git a/pjmedia/src/pjmedia-codec/h264_packetizer.c b/pjmedia/src/pjmedia-codec/h264_packetizer.c
new file mode 100644
index 0000000..59ca418
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/h264_packetizer.c
@@ -0,0 +1,535 @@
+/* $Id: h264_packetizer.c 4006 2012-04-02 08:40:54Z nanang $ */
+/*
+ * Copyright (C) 2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia-codec/h264_packetizer.h>
+#include <pjmedia/types.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+
+#define THIS_FILE "h264_packetizer.c"
+
+#define DBG_PACKETIZE 0
+#define DBG_UNPACKETIZE 0
+
+
+/* H.264 packetizer definition */
+struct pjmedia_h264_packetizer
+{
+ /* Current settings */
+ pjmedia_h264_packetizer_cfg cfg;
+
+ /* Unpacketizer state */
+ unsigned unpack_last_sync_pos;
+ pj_bool_t unpack_prev_lost;
+};
+
+
+/* Enumeration of H.264 NAL unit types */
+enum
+{
+ NAL_TYPE_SINGLE_NAL_MIN = 1,
+ NAL_TYPE_SINGLE_NAL_MAX = 23,
+ NAL_TYPE_STAP_A = 24,
+ NAL_TYPE_FU_A = 28,
+};
+
+
+/*
+ * Find next NAL unit from the specified H.264 bitstream data.
+ */
+static pj_uint8_t* find_next_nal_unit(pj_uint8_t *start,
+ pj_uint8_t *end)
+{
+ pj_uint8_t *p = start;
+
+ /* Simply lookup "0x000001" pattern */
+ while (p <= end-3 && (p[0] || p[1] || p[2]!=1))
+ ++p;
+
+ if (p > end-3)
+ /* No more NAL unit in this bitstream */
+ return NULL;
+
+ /* Include 8 bits leading zero */
+ if (p>start && *(p-1)==0)
+ return (p-1);
+
+ return p;
+}
+
+
+/*
+ * Create H264 packetizer.
+ */
+PJ_DEF(pj_status_t) pjmedia_h264_packetizer_create(
+ pj_pool_t *pool,
+ const pjmedia_h264_packetizer_cfg *cfg,
+ pjmedia_h264_packetizer **p)
+{
+ pjmedia_h264_packetizer *p_;
+
+ PJ_ASSERT_RETURN(pool && p, PJ_EINVAL);
+
+ if (cfg &&
+ cfg->mode != PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED &&
+ cfg->mode != PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL)
+ {
+ return PJ_ENOTSUP;
+ }
+
+ p_ = PJ_POOL_ZALLOC_T(pool, pjmedia_h264_packetizer);
+ if (cfg) {
+ pj_memcpy(&p_->cfg, cfg, sizeof(*cfg));
+ } else {
+ p_->cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED;
+ p_->cfg.mtu = PJMEDIA_MAX_VID_PAYLOAD_SIZE;
+ }
+
+ *p = p_;
+
+ return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Generate an RTP payload from H.264 frame bitstream, in-place processing.
+ */
+PJ_DEF(pj_status_t) pjmedia_h264_packetize(pjmedia_h264_packetizer *pktz,
+ pj_uint8_t *buf,
+ pj_size_t buf_len,
+ unsigned *pos,
+ const pj_uint8_t **payload,
+ pj_size_t *payload_len)
+{
+ pj_uint8_t *nal_start = NULL, *nal_end = NULL, *nal_octet = NULL;
+ pj_uint8_t *p, *end;
+ enum {
+ HEADER_SIZE_FU_A = 2,
+ HEADER_SIZE_STAP_A = 3,
+ };
+ enum { MAX_NALS_IN_AGGR = 32 };
+
+#if DBG_PACKETIZE
+ if (*pos == 0 && buf_len) {
+ PJ_LOG(3, ("h264pack", "<< Start packing new frame >>"));
+ }
+#endif
+
+ p = buf + *pos;
+ end = buf + buf_len;
+
+ /* Find NAL unit startcode */
+ if (end-p >= 4)
+ nal_start = find_next_nal_unit(p, p+4);
+ if (nal_start) {
+ /* Get NAL unit octet pointer */
+ while (*nal_start++ == 0);
+ nal_octet = nal_start;
+ } else {
+ /* This NAL unit is being fragmented */
+ nal_start = p;
+ }
+
+ /* Get end of NAL unit */
+ p = nal_start+pktz->cfg.mtu+1;
+ if (p > end || pktz->cfg.mode==PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL)
+ p = end;
+ nal_end = find_next_nal_unit(nal_start, p);
+ if (!nal_end)
+ nal_end = p;
+
+ /* Validate MTU vs NAL length on single NAL unit packetization */
+ if ((pktz->cfg.mode==PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL) &&
+ nal_end - nal_start > pktz->cfg.mtu)
+ {
+ //pj_assert(!"MTU too small for H.264 single NAL packetization mode");
+ PJ_LOG(2,("h264_packetizer.c",
+ "MTU too small for H.264 (required=%u, MTU=%u)",
+ nal_end - nal_start, pktz->cfg.mtu));
+ return PJ_ETOOSMALL;
+ }
+
+ /* Evaluate the proper payload format structure */
+
+ /* Fragmentation (FU-A) packet */
+ if ((pktz->cfg.mode != PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL) &&
+ (!nal_octet || nal_end-nal_start > pktz->cfg.mtu))
+ {
+ pj_uint8_t NRI, TYPE;
+
+ if (nal_octet) {
+ /* We have NAL unit octet, so this is the first fragment */
+ NRI = (*nal_octet & 0x60) >> 5;
+ TYPE = *nal_octet & 0x1F;
+
+ /* Skip nal_octet in nal_start to be overriden by FU header */
+ ++nal_start;
+ } else {
+ /* Not the first fragment, get NRI and NAL unit type
+ * from the previous fragment.
+ */
+ p = nal_start - pktz->cfg.mtu;
+ NRI = (*p & 0x60) >> 5;
+ TYPE = *(p+1) & 0x1F;
+ }
+
+ /* Init FU indicator (one octet: F+NRI+TYPE) */
+ p = nal_start - HEADER_SIZE_FU_A;
+ *p = (NRI << 5) | NAL_TYPE_FU_A;
+ ++p;
+
+ /* Init FU header (one octed: S+E+R+TYPE) */
+ *p = TYPE;
+ if (nal_octet)
+ *p |= (1 << 7); /* S bit flag = start of fragmentation */
+ if (nal_end-nal_start+HEADER_SIZE_FU_A <= pktz->cfg.mtu)
+ *p |= (1 << 6); /* E bit flag = end of fragmentation */
+
+ /* Set payload, payload length */
+ *payload = nal_start - HEADER_SIZE_FU_A;
+ if (nal_end-nal_start+HEADER_SIZE_FU_A > pktz->cfg.mtu)
+ *payload_len = pktz->cfg.mtu;
+ else
+ *payload_len = nal_end - nal_start + HEADER_SIZE_FU_A;
+ *pos = *payload + *payload_len - buf;
+
+#if DBG_PACKETIZE
+ PJ_LOG(3, ("h264pack", "Packetized fragmented H264 NAL unit "
+ "(pos=%d, type=%d, NRI=%d, S=%d, E=%d, len=%d/%d)",
+ *payload-buf, TYPE, NRI, *p>>7, (*p>>6)&1, *payload_len,
+ buf_len));
+#endif
+
+ return PJ_SUCCESS;
+ }
+
+ /* Aggregation (STAP-A) packet */
+ if ((pktz->cfg.mode != PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL) &&
+ (nal_end != end) &&
+ (nal_end - nal_start + HEADER_SIZE_STAP_A) < pktz->cfg.mtu)
+ {
+ int total_size;
+ unsigned nal_cnt = 1;
+ pj_uint8_t *nal[MAX_NALS_IN_AGGR];
+ pj_size_t nal_size[MAX_NALS_IN_AGGR];
+ pj_uint8_t NRI;
+
+ pj_assert(nal_octet);
+
+ /* Init the first NAL unit in the packet */
+ nal[0] = nal_start;
+ nal_size[0] = nal_end - nal_start;
+ total_size = nal_size[0] + HEADER_SIZE_STAP_A;
+ NRI = (*nal_octet & 0x60) >> 5;
+
+ /* Populate next NAL units */
+ while (nal_cnt < MAX_NALS_IN_AGGR) {
+ pj_uint8_t *tmp_end;
+
+ /* Find start address of the next NAL unit */
+ p = nal[nal_cnt-1] + nal_size[nal_cnt-1];
+ while (*p++ == 0);
+ nal[nal_cnt] = p;
+
+ /* Find end address of the next NAL unit */
+ tmp_end = p + (pktz->cfg.mtu - total_size);
+ if (tmp_end > end)
+ tmp_end = end;
+ p = find_next_nal_unit(p+1, tmp_end);
+ if (p) {
+ nal_size[nal_cnt] = p - nal[nal_cnt];
+ } else {
+ break;
+ }
+
+ /* Update total payload size (2 octet NAL size + NAL) */
+ total_size += (2 + nal_size[nal_cnt]);
+ if (total_size <= pktz->cfg.mtu) {
+ pj_uint8_t tmp_nri;
+
+ /* Get maximum NRI of the aggregated NAL units */
+ tmp_nri = (*(nal[nal_cnt]-1) & 0x60) >> 5;
+ if (tmp_nri > NRI)
+ NRI = tmp_nri;
+ } else {
+ break;
+ }
+
+ ++nal_cnt;
+ }
+
+ /* Only use STAP-A when we found more than one NAL units */
+ if (nal_cnt > 1) {
+ unsigned i;
+
+ /* Init STAP-A NAL header (F+NRI+TYPE) */
+ p = nal[0] - HEADER_SIZE_STAP_A;
+ *p++ = (NRI << 5) | NAL_TYPE_STAP_A;
+
+ /* Append all populated NAL units into payload (SIZE+NAL) */
+ for (i = 0; i < nal_cnt; ++i) {
+ /* Put size (2 octets in network order) */
+ pj_assert(nal_size[i] <= 0xFFFF);
+ *p++ = (pj_uint8_t)(nal_size[i] >> 8);
+ *p++ = (pj_uint8_t)(nal_size[i] & 0xFF);
+
+ /* Append NAL unit, watchout memmove()-ing bitstream! */
+ if (p != nal[i])
+ pj_memmove(p, nal[i], nal_size[i]);
+ p += nal_size[i];
+ }
+
+ /* Set payload, payload length, and pos */
+ *payload = nal[0] - HEADER_SIZE_STAP_A;
+ pj_assert(*payload >= buf+*pos);
+ *payload_len = p - *payload;
+ *pos = nal[nal_cnt-1] + nal_size[nal_cnt-1] - buf;
+
+#if DBG_PACKETIZE
+ PJ_LOG(3, ("h264pack", "Packetized aggregation of "
+ "%d H264 NAL units (pos=%d, NRI=%d len=%d/%d)",
+ nal_cnt, *payload-buf, NRI, *payload_len, buf_len));
+#endif
+
+ return PJ_SUCCESS;
+ }
+ }
+
+ /* Single NAL unit packet */
+ *payload = nal_start;
+ *payload_len = nal_end - nal_start;
+ *pos = nal_end - buf;
+
+#if DBG_PACKETIZE
+ PJ_LOG(3, ("h264pack", "Packetized single H264 NAL unit "
+ "(pos=%d, type=%d, NRI=%d, len=%d/%d)",
+ nal_start-buf, *nal_octet&0x1F, (*nal_octet&0x60)>>5,
+ *payload_len, buf_len));
+#endif
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Append RTP payload to a H.264 picture bitstream. Note that the only
+ * payload format that cares about packet lost is the NAL unit
+ * fragmentation format (FU-A/B), so we will only manage the "prev_lost"
+ * state for the FU-A/B packets.
+ */
+PJ_DEF(pj_status_t) pjmedia_h264_unpacketize(pjmedia_h264_packetizer *pktz,
+ const pj_uint8_t *payload,
+ pj_size_t payload_len,
+ pj_uint8_t *bits,
+ pj_size_t bits_len,
+ unsigned *bits_pos)
+{
+ const pj_uint8_t nal_start_code[3] = {0, 0, 1};
+ enum { MIN_PAYLOAD_SIZE = 2 };
+ pj_uint8_t nal_type;
+
+ PJ_UNUSED_ARG(pktz);
+
+#if DBG_UNPACKETIZE
+ if (*bits_pos == 0 && payload_len) {
+ PJ_LOG(3, ("h264unpack", ">> Start unpacking new frame <<"));
+ }
+#endif
+
+ /* Check if this is a missing/lost packet */
+ if (payload == NULL) {
+ pktz->unpack_prev_lost = PJ_TRUE;
+ return PJ_SUCCESS;
+ }
+
+ /* H264 payload size */
+ if (payload_len < MIN_PAYLOAD_SIZE) {
+ /* Invalid bitstream, discard this payload */
+ pktz->unpack_prev_lost = PJ_TRUE;
+ return PJ_EINVAL;
+ }
+
+ /* Reset last sync point for every new picture bitstream */
+ if (*bits_pos == 0)
+ pktz->unpack_last_sync_pos = 0;
+
+ nal_type = *payload & 0x1F;
+ if (nal_type >= NAL_TYPE_SINGLE_NAL_MIN &&
+ nal_type <= NAL_TYPE_SINGLE_NAL_MAX)
+ {
+ /* Single NAL unit packet */
+ pj_uint8_t *p = bits + *bits_pos;
+
+ /* Validate bitstream length */
+ if (bits_len-*bits_pos < payload_len+PJ_ARRAY_SIZE(nal_start_code)) {
+ /* Insufficient bistream buffer, discard this payload */
+ pj_assert(!"Insufficient H.263 bitstream buffer");
+ return PJ_ETOOSMALL;
+ }
+
+ /* Write NAL unit start code */
+ pj_memcpy(p, &nal_start_code, PJ_ARRAY_SIZE(nal_start_code));
+ p += PJ_ARRAY_SIZE(nal_start_code);
+
+ /* Write NAL unit */
+ pj_memcpy(p, payload, payload_len);
+ p += payload_len;
+
+ /* Update the bitstream writing offset */
+ *bits_pos = p - bits;
+ pktz->unpack_last_sync_pos = *bits_pos;
+
+#if DBG_UNPACKETIZE
+ PJ_LOG(3, ("h264unpack", "Unpacked single H264 NAL unit "
+ "(type=%d, NRI=%d, len=%d)",
+ nal_type, (*payload&0x60)>>5, payload_len));
+#endif
+
+ }
+ else if (nal_type == NAL_TYPE_STAP_A)
+ {
+ /* Aggregation packet */
+ pj_uint8_t *p, *p_end;
+ const pj_uint8_t *q, *q_end;
+ unsigned cnt = 0;
+
+ /* Validate bitstream length */
+ if (bits_len - *bits_pos < payload_len + 32) {
+ /* Insufficient bistream buffer, discard this payload */
+ pj_assert(!"Insufficient H.263 bitstream buffer");
+ return PJ_ETOOSMALL;
+ }
+
+ /* Fill bitstream */
+ p = bits + *bits_pos;
+ p_end = bits + bits_len;
+ q = payload + 1;
+ q_end = payload + payload_len;
+ while (q < q_end && p < p_end) {
+ pj_uint16_t tmp_nal_size;
+
+ /* Write NAL unit start code */
+ pj_memcpy(p, &nal_start_code, PJ_ARRAY_SIZE(nal_start_code));
+ p += PJ_ARRAY_SIZE(nal_start_code);
+
+ /* Get NAL unit size */
+ tmp_nal_size = (*q << 8) | *(q+1);
+ q += 2;
+ if (q + tmp_nal_size > q_end) {
+ /* Invalid bitstream, discard the rest of the payload */
+ return PJ_EINVAL;
+ }
+
+ /* Write NAL unit */
+ pj_memcpy(p, q, tmp_nal_size);
+ p += tmp_nal_size;
+ q += tmp_nal_size;
+ ++cnt;
+
+ /* Update the bitstream writing offset */
+ *bits_pos = p - bits;
+ pktz->unpack_last_sync_pos = *bits_pos;
+ }
+
+#if DBG_UNPACKETIZE
+ PJ_LOG(3, ("h264unpack", "Unpacked %d H264 NAL units (len=%d)",
+ cnt, payload_len));
+#endif
+
+ }
+ else if (nal_type == NAL_TYPE_FU_A)
+ {
+ /* Fragmentation packet */
+ pj_uint8_t *p;
+ const pj_uint8_t *q = payload;
+ pj_uint8_t NRI, TYPE, S, E;
+
+ p = bits + *bits_pos;
+
+ /* Validate bitstream length */
+ if (bits_len-*bits_pos < payload_len+PJ_ARRAY_SIZE(nal_start_code)) {
+ /* Insufficient bistream buffer, drop this packet */
+ pj_assert(!"Insufficient H.263 bitstream buffer");
+ pktz->unpack_prev_lost = PJ_TRUE;
+ return PJ_ETOOSMALL;
+ }
+
+ /* Get info */
+ S = *(q+1) & 0x80; /* Start bit flag */
+ E = *(q+1) & 0x40; /* End bit flag */
+ TYPE = *(q+1) & 0x1f;
+ NRI = (*q & 0x60) >> 5;
+
+ /* Fill bitstream */
+ if (S) {
+ /* This is the first part, write NAL unit start code */
+ pj_memcpy(p, &nal_start_code, PJ_ARRAY_SIZE(nal_start_code));
+ p += PJ_ARRAY_SIZE(nal_start_code);
+
+ /* Write NAL unit octet */
+ *p++ = (NRI << 5) | TYPE;
+ } else if (pktz->unpack_prev_lost) {
+ /* If prev packet was lost, revert the bitstream pointer to
+ * the last sync point.
+ */
+ pj_assert(pktz->unpack_last_sync_pos <= *bits_pos);
+ *bits_pos = pktz->unpack_last_sync_pos;
+ /* And discard this payload (and the following fragmentation
+ * payloads carrying this same NAL unit.
+ */
+ return PJ_EIGNORED;
+ }
+ q += 2;
+
+ /* Write NAL unit */
+ pj_memcpy(p, q, payload_len - 2);
+ p += (payload_len - 2);
+
+ /* Update the bitstream writing offset */
+ *bits_pos = p - bits;
+ if (E) {
+ /* Update the sync pos only if the end bit flag is set */
+ pktz->unpack_last_sync_pos = *bits_pos;
+ }
+
+#if DBG_UNPACKETIZE
+ PJ_LOG(3, ("h264unpack", "Unpacked fragmented H264 NAL unit "
+ "(type=%d, NRI=%d, len=%d)",
+ TYPE, NRI, payload_len));
+#endif
+
+ } else {
+ *bits_pos = 0;
+ return PJ_ENOTSUP;
+ }
+
+ pktz->unpack_prev_lost = PJ_FALSE;
+
+ return PJ_SUCCESS;
+}
+
+
+#endif /* PJMEDIA_HAS_VIDEO */
diff --git a/pjmedia/src/pjmedia-codec/ilbc.c b/pjmedia/src/pjmedia-codec/ilbc.c
new file mode 100644
index 0000000..bbebdfe
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/ilbc.c
@@ -0,0 +1,883 @@
+/* $Id: ilbc.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia-codec/ilbc.h>
+#include <pjmedia-codec/types.h>
+#include <pjmedia/codec.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/endpoint.h>
+#include <pjmedia/plc.h>
+#include <pjmedia/port.h>
+#include <pjmedia/silencedet.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/os.h>
+
+#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO
+ #include <AudioToolbox/AudioToolbox.h>
+ #define iLBC_Enc_Inst_t AudioConverterRef
+ #define iLBC_Dec_Inst_t AudioConverterRef
+ #define BLOCKL_MAX 1
+#else
+ #include "../../third_party/ilbc/iLBC_encode.h"
+ #include "../../third_party/ilbc/iLBC_decode.h"
+#endif
+
+/*
+ * Only build this file if PJMEDIA_HAS_ILBC_CODEC != 0
+ */
+#if defined(PJMEDIA_HAS_ILBC_CODEC) && PJMEDIA_HAS_ILBC_CODEC != 0
+
+
+#define THIS_FILE "ilbc.c"
+#define CLOCK_RATE 8000
+#define DEFAULT_MODE 30
+
+
+/* Prototypes for iLBC factory */
+static pj_status_t ilbc_test_alloc(pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id );
+static pj_status_t ilbc_default_attr(pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr );
+static pj_status_t ilbc_enum_codecs(pjmedia_codec_factory *factory,
+ unsigned *count,
+ pjmedia_codec_info codecs[]);
+static pj_status_t ilbc_alloc_codec(pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec);
+static pj_status_t ilbc_dealloc_codec(pjmedia_codec_factory *factory,
+ pjmedia_codec *codec );
+
+/* Prototypes for iLBC implementation. */
+static pj_status_t ilbc_codec_init(pjmedia_codec *codec,
+ pj_pool_t *pool );
+static pj_status_t ilbc_codec_open(pjmedia_codec *codec,
+ pjmedia_codec_param *attr );
+static pj_status_t ilbc_codec_close(pjmedia_codec *codec );
+static pj_status_t ilbc_codec_modify(pjmedia_codec *codec,
+ const pjmedia_codec_param *attr );
+static pj_status_t ilbc_codec_parse(pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[]);
+static pj_status_t ilbc_codec_encode(pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+static pj_status_t ilbc_codec_decode(pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+static pj_status_t ilbc_codec_recover(pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+
+/* Definition for iLBC codec operations. */
+static pjmedia_codec_op ilbc_op =
+{
+ &ilbc_codec_init,
+ &ilbc_codec_open,
+ &ilbc_codec_close,
+ &ilbc_codec_modify,
+ &ilbc_codec_parse,
+ &ilbc_codec_encode,
+ &ilbc_codec_decode,
+ &ilbc_codec_recover
+};
+
+/* Definition for iLBC codec factory operations. */
+static pjmedia_codec_factory_op ilbc_factory_op =
+{
+ &ilbc_test_alloc,
+ &ilbc_default_attr,
+ &ilbc_enum_codecs,
+ &ilbc_alloc_codec,
+ &ilbc_dealloc_codec,
+ &pjmedia_codec_ilbc_deinit
+};
+
+/* iLBC factory */
+static struct ilbc_factory
+{
+ pjmedia_codec_factory base;
+ pjmedia_endpt *endpt;
+
+ int mode;
+ int bps;
+} ilbc_factory;
+
+
+/* iLBC codec private data. */
+struct ilbc_codec
+{
+ pjmedia_codec base;
+ pj_pool_t *pool;
+ char obj_name[PJ_MAX_OBJ_NAME];
+ pjmedia_silence_det *vad;
+ pj_bool_t vad_enabled;
+ pj_bool_t plc_enabled;
+ pj_timestamp last_tx;
+
+
+ pj_bool_t enc_ready;
+ iLBC_Enc_Inst_t enc;
+ unsigned enc_frame_size;
+ unsigned enc_samples_per_frame;
+ float enc_block[BLOCKL_MAX];
+
+ pj_bool_t dec_ready;
+ iLBC_Dec_Inst_t dec;
+ unsigned dec_frame_size;
+ unsigned dec_samples_per_frame;
+ float dec_block[BLOCKL_MAX];
+
+#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO
+ unsigned enc_total_packets;
+ char *enc_buffer;
+ unsigned enc_buffer_offset;
+
+ unsigned dec_total_packets;
+ char *dec_buffer;
+ unsigned dec_buffer_offset;
+#endif
+};
+
+static pj_str_t STR_MODE = {"mode", 4};
+
+/*
+ * Initialize and register iLBC codec factory to pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_ilbc_init( pjmedia_endpt *endpt,
+ int mode )
+{
+ pjmedia_codec_mgr *codec_mgr;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(endpt != NULL, PJ_EINVAL);
+ PJ_ASSERT_RETURN(mode==0 || mode==20 || mode==30, PJ_EINVAL);
+
+ /* Create iLBC codec factory. */
+ ilbc_factory.base.op = &ilbc_factory_op;
+ ilbc_factory.base.factory_data = NULL;
+ ilbc_factory.endpt = endpt;
+
+ if (mode == 0)
+ mode = DEFAULT_MODE;
+
+ ilbc_factory.mode = mode;
+
+ if (mode == 20) {
+ ilbc_factory.bps = 15200;
+ } else {
+ ilbc_factory.bps = 13333;
+ }
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
+ if (!codec_mgr)
+ return PJ_EINVALIDOP;
+
+ /* Register codec factory to endpoint. */
+ status = pjmedia_codec_mgr_register_factory(codec_mgr,
+ &ilbc_factory.base);
+ if (status != PJ_SUCCESS)
+ return status;
+
+
+ /* Done. */
+ return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Unregister iLBC codec factory from pjmedia endpoint and deinitialize
+ * the iLBC codec library.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_ilbc_deinit(void)
+{
+ pjmedia_codec_mgr *codec_mgr;
+ pj_status_t status;
+
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(ilbc_factory.endpt);
+ if (!codec_mgr)
+ return PJ_EINVALIDOP;
+
+ /* Unregister iLBC codec factory. */
+ status = pjmedia_codec_mgr_unregister_factory(codec_mgr,
+ &ilbc_factory.base);
+
+ return status;
+}
+
+/*
+ * Check if factory can allocate the specified codec.
+ */
+static pj_status_t ilbc_test_alloc( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *info )
+{
+ const pj_str_t ilbc_tag = { "iLBC", 4};
+
+ PJ_UNUSED_ARG(factory);
+ PJ_ASSERT_RETURN(factory==&ilbc_factory.base, PJ_EINVAL);
+
+
+ /* Type MUST be audio. */
+ if (info->type != PJMEDIA_TYPE_AUDIO)
+ return PJMEDIA_CODEC_EUNSUP;
+
+ /* Check encoding name. */
+ if (pj_stricmp(&info->encoding_name, &ilbc_tag) != 0)
+ return PJMEDIA_CODEC_EUNSUP;
+
+ /* Check clock-rate */
+ if (info->clock_rate != CLOCK_RATE)
+ return PJMEDIA_CODEC_EUNSUP;
+
+ /* Channel count must be one */
+ if (info->channel_cnt != 1)
+ return PJMEDIA_CODEC_EUNSUP;
+
+ /* Yes, this should be iLBC! */
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Generate default attribute.
+ */
+static pj_status_t ilbc_default_attr (pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr )
+{
+ PJ_UNUSED_ARG(factory);
+ PJ_ASSERT_RETURN(factory==&ilbc_factory.base, PJ_EINVAL);
+
+ PJ_UNUSED_ARG(id);
+ PJ_ASSERT_RETURN(pj_stricmp2(&id->encoding_name, "iLBC")==0, PJ_EINVAL);
+
+ pj_bzero(attr, sizeof(pjmedia_codec_param));
+
+ attr->info.clock_rate = CLOCK_RATE;
+ attr->info.channel_cnt = 1;
+ attr->info.avg_bps = ilbc_factory.bps;
+ attr->info.max_bps = 15200;
+ attr->info.pcm_bits_per_sample = 16;
+ attr->info.frm_ptime = (short)ilbc_factory.mode;
+ attr->info.pt = PJMEDIA_RTP_PT_ILBC;
+
+ attr->setting.frm_per_pkt = 1;
+ attr->setting.vad = 1;
+ attr->setting.plc = 1;
+ attr->setting.penh = 1;
+ attr->setting.dec_fmtp.cnt = 1;
+ attr->setting.dec_fmtp.param[0].name = STR_MODE;
+ if (ilbc_factory.mode == 30)
+ attr->setting.dec_fmtp.param[0].val = pj_str("30");
+ else
+ attr->setting.dec_fmtp.param[0].val = pj_str("20");
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Enum codecs supported by this factory (i.e. only iLBC!).
+ */
+static pj_status_t ilbc_enum_codecs(pjmedia_codec_factory *factory,
+ unsigned *count,
+ pjmedia_codec_info codecs[])
+{
+ PJ_UNUSED_ARG(factory);
+ PJ_ASSERT_RETURN(factory==&ilbc_factory.base, PJ_EINVAL);
+
+ PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL);
+
+ pj_bzero(&codecs[0], sizeof(pjmedia_codec_info));
+
+ codecs[0].encoding_name = pj_str("iLBC");
+ codecs[0].pt = PJMEDIA_RTP_PT_ILBC;
+ codecs[0].type = PJMEDIA_TYPE_AUDIO;
+ codecs[0].clock_rate = 8000;
+ codecs[0].channel_cnt = 1;
+
+ *count = 1;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Allocate a new iLBC codec instance.
+ */
+static pj_status_t ilbc_alloc_codec(pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec)
+{
+ pj_pool_t *pool;
+ struct ilbc_codec *codec;
+
+ PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory == &ilbc_factory.base, PJ_EINVAL);
+
+ pool = pjmedia_endpt_create_pool(ilbc_factory.endpt, "iLBC%p",
+ 2000, 2000);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ codec = PJ_POOL_ZALLOC_T(pool, struct ilbc_codec);
+ codec->base.op = &ilbc_op;
+ codec->base.factory = factory;
+ codec->pool = pool;
+
+ pj_ansi_snprintf(codec->obj_name, sizeof(codec->obj_name),
+ "ilbc%p", codec);
+
+ *p_codec = &codec->base;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Free codec.
+ */
+static pj_status_t ilbc_dealloc_codec( pjmedia_codec_factory *factory,
+ pjmedia_codec *codec )
+{
+ struct ilbc_codec *ilbc_codec;
+
+ PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL);
+ PJ_UNUSED_ARG(factory);
+ PJ_ASSERT_RETURN(factory == &ilbc_factory.base, PJ_EINVAL);
+
+ ilbc_codec = (struct ilbc_codec*) codec;
+
+#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO
+ if (ilbc_codec->enc) {
+ AudioConverterDispose(ilbc_codec->enc);
+ ilbc_codec->enc = NULL;
+ }
+ if (ilbc_codec->dec) {
+ AudioConverterDispose(ilbc_codec->dec);
+ ilbc_codec->dec = NULL;
+ }
+#endif
+
+ pj_pool_release(ilbc_codec->pool);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Init codec.
+ */
+static pj_status_t ilbc_codec_init(pjmedia_codec *codec,
+ pj_pool_t *pool )
+{
+ PJ_UNUSED_ARG(codec);
+ PJ_UNUSED_ARG(pool);
+ return PJ_SUCCESS;
+}
+
+/*
+ * Open codec.
+ */
+static pj_status_t ilbc_codec_open(pjmedia_codec *codec,
+ pjmedia_codec_param *attr )
+{
+ struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec;
+ pj_status_t status;
+ unsigned i;
+ pj_uint16_t dec_fmtp_mode = DEFAULT_MODE,
+ enc_fmtp_mode = DEFAULT_MODE;
+
+#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO
+ AudioStreamBasicDescription srcFormat, dstFormat;
+ UInt32 size;
+
+ srcFormat.mSampleRate = attr->info.clock_rate;
+ srcFormat.mFormatID = kAudioFormatLinearPCM;
+ srcFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger
+ | kLinearPCMFormatFlagIsPacked;
+ srcFormat.mBitsPerChannel = attr->info.pcm_bits_per_sample;
+ srcFormat.mChannelsPerFrame = attr->info.channel_cnt;
+ srcFormat.mBytesPerFrame = srcFormat.mChannelsPerFrame
+ * srcFormat.mBitsPerChannel >> 3;
+ srcFormat.mFramesPerPacket = 1;
+ srcFormat.mBytesPerPacket = srcFormat.mBytesPerFrame *
+ srcFormat.mFramesPerPacket;
+
+ memset(&dstFormat, 0, sizeof(dstFormat));
+ dstFormat.mSampleRate = attr->info.clock_rate;
+ dstFormat.mFormatID = kAudioFormatiLBC;
+ dstFormat.mChannelsPerFrame = attr->info.channel_cnt;
+#endif
+
+ pj_assert(ilbc_codec != NULL);
+ pj_assert(ilbc_codec->enc_ready == PJ_FALSE &&
+ ilbc_codec->dec_ready == PJ_FALSE);
+
+ /* Get decoder mode */
+ for (i = 0; i < attr->setting.dec_fmtp.cnt; ++i) {
+ if (pj_stricmp(&attr->setting.dec_fmtp.param[i].name, &STR_MODE) == 0)
+ {
+ dec_fmtp_mode = (pj_uint16_t)
+ pj_strtoul(&attr->setting.dec_fmtp.param[i].val);
+ break;
+ }
+ }
+
+ /* Decoder mode must be set */
+ PJ_ASSERT_RETURN(dec_fmtp_mode == 20 || dec_fmtp_mode == 30,
+ PJMEDIA_CODEC_EINMODE);
+
+ /* Get encoder mode */
+ for (i = 0; i < attr->setting.enc_fmtp.cnt; ++i) {
+ if (pj_stricmp(&attr->setting.enc_fmtp.param[i].name, &STR_MODE) == 0)
+ {
+ enc_fmtp_mode = (pj_uint16_t)
+ pj_strtoul(&attr->setting.enc_fmtp.param[i].val);
+ break;
+ }
+ }
+
+ PJ_ASSERT_RETURN(enc_fmtp_mode==20 || enc_fmtp_mode==30,
+ PJMEDIA_CODEC_EINMODE);
+
+ /* Both sides of a bi-directional session MUST use the same "mode" value.
+ * In this point, possible values are only 20 or 30, so when encoder and
+ * decoder modes are not same, just use the default mode, it is 30.
+ */
+ if (enc_fmtp_mode != dec_fmtp_mode) {
+ enc_fmtp_mode = dec_fmtp_mode = DEFAULT_MODE;
+ PJ_LOG(4,(ilbc_codec->obj_name,
+ "Normalized iLBC encoder and decoder modes to %d",
+ DEFAULT_MODE));
+ }
+
+ /* Update some attributes based on negotiated mode. */
+ attr->info.avg_bps = (dec_fmtp_mode == 30? 13333 : 15200);
+ attr->info.frm_ptime = dec_fmtp_mode;
+
+ /* Create encoder */
+#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO
+ dstFormat.mFramesPerPacket = CLOCK_RATE * enc_fmtp_mode / 1000;
+ dstFormat.mBytesPerPacket = (enc_fmtp_mode == 20? 38 : 50);
+
+ /* Use AudioFormat API to fill out the rest of the description */
+ size = sizeof(dstFormat);
+ AudioFormatGetProperty(kAudioFormatProperty_FormatInfo,
+ 0, NULL, &size, &dstFormat);
+
+ if (AudioConverterNew(&srcFormat, &dstFormat, &ilbc_codec->enc) != noErr)
+ return PJMEDIA_CODEC_EFAILED;
+ ilbc_codec->enc_frame_size = (enc_fmtp_mode == 20? 38 : 50);
+#else
+ ilbc_codec->enc_frame_size = initEncode(&ilbc_codec->enc, enc_fmtp_mode);
+#endif
+ ilbc_codec->enc_samples_per_frame = CLOCK_RATE * enc_fmtp_mode / 1000;
+ ilbc_codec->enc_ready = PJ_TRUE;
+
+ /* Create decoder */
+#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO
+ if (AudioConverterNew(&dstFormat, &srcFormat, &ilbc_codec->dec) != noErr)
+ return PJMEDIA_CODEC_EFAILED;
+ ilbc_codec->dec_samples_per_frame = CLOCK_RATE * dec_fmtp_mode / 1000;
+#else
+ ilbc_codec->dec_samples_per_frame = initDecode(&ilbc_codec->dec,
+ dec_fmtp_mode,
+ attr->setting.penh);
+#endif
+ ilbc_codec->dec_frame_size = (dec_fmtp_mode == 20? 38 : 50);
+ ilbc_codec->dec_ready = PJ_TRUE;
+
+ /* Save plc flags */
+ ilbc_codec->plc_enabled = (attr->setting.plc != 0);
+
+ /* Create silence detector. */
+ ilbc_codec->vad_enabled = (attr->setting.vad != 0);
+ status = pjmedia_silence_det_create(ilbc_codec->pool, CLOCK_RATE,
+ ilbc_codec->enc_samples_per_frame,
+ &ilbc_codec->vad);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Init last_tx (not necessary because of zalloc, but better
+ * be safe in case someone remove zalloc later.
+ */
+ pj_set_timestamp32(&ilbc_codec->last_tx, 0, 0);
+
+ PJ_LOG(5,(ilbc_codec->obj_name,
+ "iLBC codec opened, mode=%d", dec_fmtp_mode));
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Close codec.
+ */
+static pj_status_t ilbc_codec_close( pjmedia_codec *codec )
+{
+ struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec;
+
+ PJ_UNUSED_ARG(codec);
+
+ PJ_LOG(5,(ilbc_codec->obj_name, "iLBC codec closed"));
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Modify codec settings.
+ */
+static pj_status_t ilbc_codec_modify(pjmedia_codec *codec,
+ const pjmedia_codec_param *attr )
+{
+ struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec;
+
+ ilbc_codec->plc_enabled = (attr->setting.plc != 0);
+ ilbc_codec->vad_enabled = (attr->setting.vad != 0);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get frames in the packet.
+ */
+static pj_status_t ilbc_codec_parse( pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[])
+{
+ struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec;
+ unsigned count;
+
+ PJ_ASSERT_RETURN(frame_cnt, PJ_EINVAL);
+
+ count = 0;
+ while (pkt_size >= ilbc_codec->dec_frame_size && count < *frame_cnt) {
+ frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frames[count].buf = pkt;
+ frames[count].size = ilbc_codec->dec_frame_size;
+ frames[count].timestamp.u64 = ts->u64 + count *
+ ilbc_codec->dec_samples_per_frame;
+
+ pkt = ((char*)pkt) + ilbc_codec->dec_frame_size;
+ pkt_size -= ilbc_codec->dec_frame_size;
+
+ ++count;
+ }
+
+ *frame_cnt = count;
+ return PJ_SUCCESS;
+}
+
+#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO
+static OSStatus encodeDataProc (
+ AudioConverterRef inAudioConverter,
+ UInt32 *ioNumberDataPackets,
+ AudioBufferList *ioData,
+ AudioStreamPacketDescription **outDataPacketDescription,
+ void *inUserData
+)
+{
+ struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)inUserData;
+
+ /* Initialize in case of failure */
+ ioData->mBuffers[0].mData = NULL;
+ ioData->mBuffers[0].mDataByteSize = 0;
+
+ if (ilbc_codec->enc_total_packets < *ioNumberDataPackets) {
+ *ioNumberDataPackets = ilbc_codec->enc_total_packets;
+ }
+
+ if (*ioNumberDataPackets) {
+ ioData->mBuffers[0].mData = ilbc_codec->enc_buffer +
+ ilbc_codec->enc_buffer_offset;
+ ioData->mBuffers[0].mDataByteSize = *ioNumberDataPackets *
+ ilbc_codec->enc_samples_per_frame
+ << 1;
+ ilbc_codec->enc_buffer_offset += ioData->mBuffers[0].mDataByteSize;
+ }
+
+ ilbc_codec->enc_total_packets -= *ioNumberDataPackets;
+ return noErr;
+}
+
+static OSStatus decodeDataProc (
+ AudioConverterRef inAudioConverter,
+ UInt32 *ioNumberDataPackets,
+ AudioBufferList *ioData,
+ AudioStreamPacketDescription **outDataPacketDescription,
+ void *inUserData
+)
+{
+ struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)inUserData;
+
+ /* Initialize in case of failure */
+ ioData->mBuffers[0].mData = NULL;
+ ioData->mBuffers[0].mDataByteSize = 0;
+
+ if (ilbc_codec->dec_total_packets < *ioNumberDataPackets) {
+ *ioNumberDataPackets = ilbc_codec->dec_total_packets;
+ }
+
+ if (*ioNumberDataPackets) {
+ ioData->mBuffers[0].mData = ilbc_codec->dec_buffer +
+ ilbc_codec->dec_buffer_offset;
+ ioData->mBuffers[0].mDataByteSize = *ioNumberDataPackets *
+ ilbc_codec->dec_frame_size;
+ ilbc_codec->dec_buffer_offset += ioData->mBuffers[0].mDataByteSize;
+ }
+
+ ilbc_codec->dec_total_packets -= *ioNumberDataPackets;
+ return noErr;
+}
+#endif
+
+/*
+ * Encode frame.
+ */
+static pj_status_t ilbc_codec_encode(pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec;
+ pj_int16_t *pcm_in;
+ unsigned nsamples;
+#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO
+ UInt32 npackets;
+ OSStatus err;
+ AudioBufferList theABL;
+#endif
+
+ pj_assert(ilbc_codec && input && output);
+
+ pcm_in = (pj_int16_t*)input->buf;
+ nsamples = input->size >> 1;
+
+ PJ_ASSERT_RETURN(nsamples % ilbc_codec->enc_samples_per_frame == 0,
+ PJMEDIA_CODEC_EPCMFRMINLEN);
+ PJ_ASSERT_RETURN(output_buf_len >= ilbc_codec->enc_frame_size * nsamples /
+ ilbc_codec->enc_samples_per_frame,
+ PJMEDIA_CODEC_EFRMTOOSHORT);
+
+ /* Detect silence */
+ if (ilbc_codec->vad_enabled) {
+ pj_bool_t is_silence;
+ pj_int32_t silence_period;
+
+ silence_period = pj_timestamp_diff32(&ilbc_codec->last_tx,
+ &input->timestamp);
+
+ is_silence = pjmedia_silence_det_detect(ilbc_codec->vad,
+ (const pj_int16_t*)input->buf,
+ (input->size >> 1),
+ NULL);
+ if (is_silence &&
+ (PJMEDIA_CODEC_MAX_SILENCE_PERIOD == -1 ||
+ silence_period < PJMEDIA_CODEC_MAX_SILENCE_PERIOD*8000/1000))
+ {
+ output->type = PJMEDIA_FRAME_TYPE_NONE;
+ output->buf = NULL;
+ output->size = 0;
+ output->timestamp = input->timestamp;
+ return PJ_SUCCESS;
+ } else {
+ ilbc_codec->last_tx = input->timestamp;
+ }
+ }
+
+ /* Encode */
+ output->size = 0;
+#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO
+ npackets = nsamples / ilbc_codec->enc_samples_per_frame;
+
+ theABL.mNumberBuffers = 1;
+ theABL.mBuffers[0].mNumberChannels = 1;
+ theABL.mBuffers[0].mDataByteSize = output_buf_len;
+ theABL.mBuffers[0].mData = output->buf;
+
+ ilbc_codec->enc_total_packets = npackets;
+ ilbc_codec->enc_buffer = (char *)input->buf;
+ ilbc_codec->enc_buffer_offset = 0;
+
+ err = AudioConverterFillComplexBuffer(ilbc_codec->enc, encodeDataProc,
+ ilbc_codec, &npackets,
+ &theABL, NULL);
+ if (err == noErr) {
+ output->size = npackets * ilbc_codec->enc_frame_size;
+ }
+#else
+ while (nsamples >= ilbc_codec->enc_samples_per_frame) {
+ unsigned i;
+
+ /* Convert to float */
+ for (i=0; i<ilbc_codec->enc_samples_per_frame; ++i) {
+ ilbc_codec->enc_block[i] = (float) (*pcm_in++);
+ }
+
+ iLBC_encode((unsigned char *)output->buf + output->size,
+ ilbc_codec->enc_block,
+ &ilbc_codec->enc);
+
+ output->size += ilbc_codec->enc.no_of_bytes;
+ nsamples -= ilbc_codec->enc_samples_per_frame;
+ }
+#endif
+
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->timestamp = input->timestamp;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Decode frame.
+ */
+static pj_status_t ilbc_codec_decode(pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec;
+#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO
+ UInt32 npackets;
+ OSStatus err;
+ AudioBufferList theABL;
+#else
+ unsigned i;
+#endif
+
+ pj_assert(ilbc_codec != NULL);
+ PJ_ASSERT_RETURN(input && output, PJ_EINVAL);
+
+ if (output_buf_len < (ilbc_codec->dec_samples_per_frame << 1))
+ return PJMEDIA_CODEC_EPCMTOOSHORT;
+
+ if (input->size != ilbc_codec->dec_frame_size)
+ return PJMEDIA_CODEC_EFRMINLEN;
+
+ /* Decode to temporary buffer */
+#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO
+ npackets = input->size / ilbc_codec->dec_frame_size *
+ ilbc_codec->dec_samples_per_frame;
+
+ theABL.mNumberBuffers = 1;
+ theABL.mBuffers[0].mNumberChannels = 1;
+ theABL.mBuffers[0].mDataByteSize = output_buf_len;
+ theABL.mBuffers[0].mData = output->buf;
+
+ ilbc_codec->dec_total_packets = npackets;
+ ilbc_codec->dec_buffer = (char *)input->buf;
+ ilbc_codec->dec_buffer_offset = 0;
+
+ err = AudioConverterFillComplexBuffer(ilbc_codec->dec, decodeDataProc,
+ ilbc_codec, &npackets,
+ &theABL, NULL);
+ if (err == noErr) {
+ output->size = npackets * (ilbc_codec->dec_samples_per_frame << 1);
+ }
+#else
+ iLBC_decode(ilbc_codec->dec_block, (unsigned char*) input->buf,
+ &ilbc_codec->dec, 1);
+
+ /* Convert decodec samples from float to short */
+ for (i=0; i<ilbc_codec->dec_samples_per_frame; ++i) {
+ ((short*)output->buf)[i] = (short)ilbc_codec->dec_block[i];
+ }
+ output->size = (ilbc_codec->dec_samples_per_frame << 1);
+#endif
+
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->timestamp = input->timestamp;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Recover lost frame.
+ */
+static pj_status_t ilbc_codec_recover(pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec;
+#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO
+ UInt32 npackets;
+ OSStatus err;
+ AudioBufferList theABL;
+#else
+ unsigned i;
+#endif
+
+ pj_assert(ilbc_codec != NULL);
+ PJ_ASSERT_RETURN(output, PJ_EINVAL);
+
+ if (output_buf_len < (ilbc_codec->dec_samples_per_frame << 1))
+ return PJMEDIA_CODEC_EPCMTOOSHORT;
+
+ /* Decode to temporary buffer */
+#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO
+ npackets = 1;
+
+ theABL.mNumberBuffers = 1;
+ theABL.mBuffers[0].mNumberChannels = 1;
+ theABL.mBuffers[0].mDataByteSize = output_buf_len;
+ theABL.mBuffers[0].mData = output->buf;
+
+ ilbc_codec->dec_total_packets = npackets;
+ ilbc_codec->dec_buffer_offset = 0;
+ if (ilbc_codec->dec_buffer) {
+ err = AudioConverterFillComplexBuffer(ilbc_codec->dec, decodeDataProc,
+ ilbc_codec, &npackets,
+ &theABL, NULL);
+ if (err == noErr) {
+ output->size = npackets *
+ (ilbc_codec->dec_samples_per_frame << 1);
+ }
+ } else {
+ output->size = npackets * (ilbc_codec->dec_samples_per_frame << 1);
+ pj_bzero(output->buf, output->size);
+ }
+#else
+ iLBC_decode(ilbc_codec->dec_block, NULL, &ilbc_codec->dec, 0);
+
+ /* Convert decodec samples from float to short */
+ for (i=0; i<ilbc_codec->dec_samples_per_frame; ++i) {
+ ((short*)output->buf)[i] = (short)ilbc_codec->dec_block[i];
+ }
+ output->size = (ilbc_codec->dec_samples_per_frame << 1);
+#endif
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+
+ return PJ_SUCCESS;
+}
+
+
+#endif /* PJMEDIA_HAS_ILBC_CODEC */
diff --git a/pjmedia/src/pjmedia-codec/ipp_codecs.c b/pjmedia/src/pjmedia-codec/ipp_codecs.c
new file mode 100644
index 0000000..c7a3a77
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/ipp_codecs.c
@@ -0,0 +1,1680 @@
+/* $Id: ipp_codecs.c 4002 2012-03-30 08:05:43Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia-codec/ipp_codecs.h>
+#include <pjmedia-codec/amr_sdp_match.h>
+#include <pjmedia-codec/g7221_sdp_match.h>
+#include <pjmedia/codec.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/endpoint.h>
+#include <pjmedia/plc.h>
+#include <pjmedia/port.h>
+#include <pjmedia/silencedet.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/os.h>
+
+
+/*
+ * Only build this file if PJMEDIA_HAS_INTEL_IPP != 0
+ */
+#if defined(PJMEDIA_HAS_INTEL_IPP) && PJMEDIA_HAS_INTEL_IPP != 0
+
+#include <usc.h>
+#include <ippversion.h>
+
+#define THIS_FILE "ipp_codecs.c"
+
+
+/* Prototypes for IPP codecs factory */
+static pj_status_t ipp_test_alloc( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id );
+static pj_status_t ipp_default_attr( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr );
+static pj_status_t ipp_enum_codecs( pjmedia_codec_factory *factory,
+ unsigned *count,
+ pjmedia_codec_info codecs[]);
+static pj_status_t ipp_alloc_codec( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec);
+static pj_status_t ipp_dealloc_codec( pjmedia_codec_factory *factory,
+ pjmedia_codec *codec );
+
+/* Prototypes for IPP codecs implementation. */
+static pj_status_t ipp_codec_init( pjmedia_codec *codec,
+ pj_pool_t *pool );
+static pj_status_t ipp_codec_open( pjmedia_codec *codec,
+ pjmedia_codec_param *attr );
+static pj_status_t ipp_codec_close( pjmedia_codec *codec );
+static pj_status_t ipp_codec_modify(pjmedia_codec *codec,
+ const pjmedia_codec_param *attr );
+static pj_status_t ipp_codec_parse( pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[]);
+static pj_status_t ipp_codec_encode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+static pj_status_t ipp_codec_decode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+static pj_status_t ipp_codec_recover(pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+
+/* Definition for IPP codecs operations. */
+static pjmedia_codec_op ipp_op =
+{
+ &ipp_codec_init,
+ &ipp_codec_open,
+ &ipp_codec_close,
+ &ipp_codec_modify,
+ &ipp_codec_parse,
+ &ipp_codec_encode,
+ &ipp_codec_decode,
+ &ipp_codec_recover
+};
+
+/* Definition for IPP codecs factory operations. */
+static pjmedia_codec_factory_op ipp_factory_op =
+{
+ &ipp_test_alloc,
+ &ipp_default_attr,
+ &ipp_enum_codecs,
+ &ipp_alloc_codec,
+ &ipp_dealloc_codec,
+ &pjmedia_codec_ipp_deinit
+};
+
+/* IPP codecs factory */
+static struct ipp_factory {
+ pjmedia_codec_factory base;
+ pjmedia_endpt *endpt;
+ pj_pool_t *pool;
+ pj_mutex_t *mutex;
+ unsigned g7221_pcm_shift;
+} ipp_factory;
+
+/* IPP codecs private data. */
+typedef struct ipp_private {
+ int codec_idx; /**< Codec index. */
+ void *codec_setting; /**< Specific codec setting. */
+ pj_pool_t *pool; /**< Pool for each instance. */
+
+ USC_Handle enc; /**< Encoder state. */
+ USC_Handle dec; /**< Decoder state. */
+ USC_CodecInfo *info; /**< Native codec info. */
+ pj_uint16_t frame_size; /**< Bitstream frame size. */
+
+ pj_bool_t plc_enabled; /**< PLC enabled flag. */
+ pjmedia_plc *plc; /**< PJMEDIA PLC engine, NULL if
+ codec has internal PLC. */
+
+ pj_bool_t vad_enabled; /**< VAD enabled flag. */
+ pjmedia_silence_det *vad; /**< PJMEDIA VAD engine, NULL if
+ codec has internal VAD. */
+ pj_timestamp last_tx; /**< Timestamp of last transmit.*/
+
+ unsigned g7221_pcm_shift; /**< G722.1 PCM level adjustment*/
+} ipp_private_t;
+
+
+/* USC codec implementations. */
+extern USC_Fxns USC_G729AFP_Fxns;
+extern USC_Fxns USC_G729I_Fxns;
+extern USC_Fxns USC_G723_Fxns;
+extern USC_Fxns USC_G726_Fxns;
+extern USC_Fxns USC_G728_Fxns;
+extern USC_Fxns USC_G722_Fxns;
+extern USC_Fxns USC_GSMAMR_Fxns;
+extern USC_Fxns USC_AMRWB_Fxns;
+extern USC_Fxns USC_AMRWBE_Fxns;
+
+
+/* CUSTOM CALLBACKS */
+
+/* This callback is useful for translating RTP frame into USC frame, e.g:
+ * reassigning frame attributes, reorder bitstream. Default behaviour of
+ * the translation is just setting the USC frame buffer & its size as
+ * specified in RTP frame, setting USC frame frametype to 0, setting bitrate
+ * of USC frame to bitrate info of codec_data. Implement this callback when
+ * the default behaviour is unapplicable.
+ */
+typedef void (*predecode_cb)(ipp_private_t *codec_data,
+ const pjmedia_frame *rtp_frame,
+ USC_Bitstream *usc_frame);
+
+/* Parse frames from a packet. Default behaviour of frame parsing is
+ * just separating frames based on calculating frame length derived
+ * from bitrate. Implement this callback when the default behaviour is
+ * unapplicable.
+ */
+typedef pj_status_t (*parse_cb)(ipp_private_t *codec_data, void *pkt,
+ pj_size_t pkt_size, const pj_timestamp *ts,
+ unsigned *frame_cnt, pjmedia_frame frames[]);
+
+/* Pack frames into a packet. Default behaviour of packing frames is
+ * just stacking the frames with octet aligned without adding any
+ * payload header. Implement this callback when the default behaviour is
+ * unapplicable.
+ */
+typedef pj_status_t (*pack_cb)(ipp_private_t *codec_data, void *pkt,
+ pj_size_t *pkt_size, pj_size_t max_pkt_size);
+
+
+
+/* Custom callback implementations. */
+static void predecode_g723( ipp_private_t *codec_data,
+ const pjmedia_frame *rtp_frame,
+ USC_Bitstream *usc_frame);
+static pj_status_t parse_g723( ipp_private_t *codec_data, void *pkt,
+ pj_size_t pkt_size, const pj_timestamp *ts,
+ unsigned *frame_cnt, pjmedia_frame frames[]);
+
+static void predecode_g729( ipp_private_t *codec_data,
+ const pjmedia_frame *rtp_frame,
+ USC_Bitstream *usc_frame);
+
+static void predecode_amr( ipp_private_t *codec_data,
+ const pjmedia_frame *rtp_frame,
+ USC_Bitstream *usc_frame);
+static pj_status_t parse_amr( ipp_private_t *codec_data, void *pkt,
+ pj_size_t pkt_size, const pj_timestamp *ts,
+ unsigned *frame_cnt, pjmedia_frame frames[]);
+static pj_status_t pack_amr( ipp_private_t *codec_data, void *pkt,
+ pj_size_t *pkt_size, pj_size_t max_pkt_size);
+
+static void predecode_g7221( ipp_private_t *codec_data,
+ const pjmedia_frame *rtp_frame,
+ USC_Bitstream *usc_frame);
+static pj_status_t pack_g7221( ipp_private_t *codec_data, void *pkt,
+ pj_size_t *pkt_size, pj_size_t max_pkt_size);
+
+/* IPP codec implementation descriptions. */
+static struct ipp_codec {
+ int enabled; /* Is this codec enabled? */
+ const char *name; /* Codec name. */
+ pj_uint8_t pt; /* Payload type. */
+ USC_Fxns *fxns; /* USC callback functions. */
+ unsigned clock_rate; /* Codec's clock rate. */
+ unsigned channel_count; /* Codec's channel count. */
+ unsigned samples_per_frame; /* Codec's samples count. */
+
+ unsigned def_bitrate; /* Default bitrate of this codec. */
+ unsigned max_bitrate; /* Maximum bitrate of this codec. */
+ pj_uint8_t frm_per_pkt; /* Default num of frames per packet.*/
+ int has_native_vad; /* Codec has internal VAD? */
+ int has_native_plc; /* Codec has internal PLC? */
+
+ predecode_cb predecode; /* Callback to translate RTP frame
+ into USC frame. */
+ parse_cb parse; /* Callback to parse bitstream. */
+ pack_cb pack; /* Callback to pack bitstream. */
+
+ pjmedia_codec_fmtp dec_fmtp; /* Decoder's fmtp params. */
+}
+
+ipp_codec[] =
+{
+# if PJMEDIA_HAS_INTEL_IPP_CODEC_AMR
+ {1, "AMR", PJMEDIA_RTP_PT_AMR, &USC_GSMAMR_Fxns, 8000, 1, 160,
+ 7400, 12200, 2, 1, 1,
+ &predecode_amr, &parse_amr, &pack_amr,
+ {1, {{{"octet-align", 11}, {"1", 1}}} }
+ },
+# endif
+
+# if PJMEDIA_HAS_INTEL_IPP_CODEC_AMRWB
+ {1, "AMR-WB", PJMEDIA_RTP_PT_AMRWB, &USC_AMRWB_Fxns, 16000, 1, 320,
+ 15850, 23850, 2, 1, 1,
+ &predecode_amr, &parse_amr, &pack_amr,
+ {1, {{{"octet-align", 11}, {"1", 1}}} }
+ },
+# endif
+
+# if PJMEDIA_HAS_INTEL_IPP_CODEC_G729
+# if defined(PJ_HAS_FLOATING_POINT) && (PJ_HAS_FLOATING_POINT != 0)
+ {1, "G729", PJMEDIA_RTP_PT_G729, &USC_G729AFP_Fxns, 8000, 1, 80,
+ 8000, 11800, 2, 1, 1,
+ &predecode_g729, NULL, NULL
+ },
+# else
+ {1, "G729", PJMEDIA_RTP_PT_G729, &USC_G729I_Fxns, 8000, 1, 80,
+ 8000, 11800, 2, 1, 1,
+ &predecode_g729, NULL, NULL
+ },
+# endif
+# endif
+
+# if PJMEDIA_HAS_INTEL_IPP_CODEC_G723_1
+ /* This is actually G.723.1 */
+ {1, "G723", PJMEDIA_RTP_PT_G723, &USC_G723_Fxns, 8000, 1, 240,
+ 6300, 6300, 1, 1, 1,
+ &predecode_g723, &parse_g723, NULL
+ },
+# endif
+
+# if PJMEDIA_HAS_INTEL_IPP_CODEC_G726
+ {0, "G726-16", PJMEDIA_RTP_PT_G726_16, &USC_G726_Fxns, 8000, 1, 80,
+ 16000, 16000, 2, 0, 0,
+ NULL, NULL, NULL
+ },
+ {0, "G726-24", PJMEDIA_RTP_PT_G726_24, &USC_G726_Fxns, 8000, 1, 80,
+ 24000, 24000, 2, 0, 0,
+ NULL, NULL, NULL
+ },
+ {1, "G726-32", PJMEDIA_RTP_PT_G726_32, &USC_G726_Fxns, 8000, 1, 80,
+ 32000, 32000, 2, 0, 0,
+ NULL, NULL, NULL
+ },
+ {0, "G726-40", PJMEDIA_RTP_PT_G726_40, &USC_G726_Fxns, 8000, 1, 80,
+ 40000, 40000, 2, 0, 0,
+ NULL, NULL, NULL
+ },
+ /* Old definition of G726-32 */
+ {1, "G721", PJMEDIA_RTP_PT_G721, &USC_G726_Fxns, 8000, 1, 80,
+ 32000, 32000, 2, 0, 0,
+ NULL, NULL, NULL
+ },
+# endif
+
+# if PJMEDIA_HAS_INTEL_IPP_CODEC_G728
+ {1, "G728", PJMEDIA_RTP_PT_G728, &USC_G728_Fxns, 8000, 1, 80,
+ 16000, 16000, 2, 0, 1,
+ NULL, NULL, NULL
+ },
+# endif
+
+# if PJMEDIA_HAS_INTEL_IPP_CODEC_G722_1
+ {0, "G7221", PJMEDIA_RTP_PT_G722_1_16, &USC_G722_Fxns, 16000, 1, 320,
+ 16000, 16000, 1, 0, 1,
+ predecode_g7221, NULL, pack_g7221,
+ {1, {{{"bitrate", 7}, {"16000", 5}}} }
+ },
+ {1, "G7221", PJMEDIA_RTP_PT_G722_1_24, &USC_G722_Fxns, 16000, 1, 320,
+ 24000, 24000, 1, 0, 1,
+ predecode_g7221, NULL, pack_g7221,
+ {1, {{{"bitrate", 7}, {"24000", 5}}} }
+ },
+ {1, "G7221", PJMEDIA_RTP_PT_G722_1_32, &USC_G722_Fxns, 16000, 1, 320,
+ 32000, 32000, 1, 0, 1,
+ predecode_g7221, NULL, pack_g7221,
+ {1, {{{"bitrate", 7}, {"32000", 5}}} }
+ },
+# endif
+};
+
+
+#if PJMEDIA_HAS_INTEL_IPP_CODEC_G729
+
+static void predecode_g729( ipp_private_t *codec_data,
+ const pjmedia_frame *rtp_frame,
+ USC_Bitstream *usc_frame)
+{
+ switch (rtp_frame->size) {
+ case 2:
+ /* SID */
+ usc_frame->frametype = 1;
+ usc_frame->bitrate = codec_data->info->params.modes.bitrate;
+ break;
+ case 8:
+ /* G729D */
+ usc_frame->frametype = 2;
+ usc_frame->bitrate = 6400;
+ break;
+ case 10:
+ /* G729 */
+ usc_frame->frametype = 3;
+ usc_frame->bitrate = 8000;
+ break;
+ case 15:
+ /* G729E */
+ usc_frame->frametype = 4;
+ usc_frame->bitrate = 11800;
+ break;
+ default:
+ usc_frame->frametype = 0;
+ usc_frame->bitrate = 0;
+ break;
+ }
+
+ usc_frame->pBuffer = rtp_frame->buf;
+ usc_frame->nbytes = rtp_frame->size;
+}
+
+#endif /* PJMEDIA_HAS_INTEL_IPP_CODEC_G729 */
+
+
+#if PJMEDIA_HAS_INTEL_IPP_CODEC_G723_1
+
+static void predecode_g723( ipp_private_t *codec_data,
+ const pjmedia_frame *rtp_frame,
+ USC_Bitstream *usc_frame)
+{
+ int i, HDR = 0;
+ pj_uint8_t *f = (pj_uint8_t*)rtp_frame->buf;
+
+ PJ_UNUSED_ARG(codec_data);
+
+ for (i = 0; i < 2; ++i){
+ int tmp;
+ tmp = (f[0] >> (i & 0x7)) & 1;
+ HDR += tmp << i ;
+ }
+
+ usc_frame->pBuffer = rtp_frame->buf;
+ usc_frame->nbytes = rtp_frame->size;
+ usc_frame->bitrate = HDR == 0? 6300 : 5300;
+ usc_frame->frametype = 0;
+}
+
+static pj_status_t parse_g723(ipp_private_t *codec_data, void *pkt,
+ pj_size_t pkt_size, const pj_timestamp *ts,
+ unsigned *frame_cnt, pjmedia_frame frames[])
+{
+ unsigned count = 0;
+ pj_uint8_t *f = (pj_uint8_t*)pkt;
+
+ while (pkt_size && count < *frame_cnt) {
+ int framesize, i, j;
+ int HDR = 0;
+
+ for (i = 0; i < 2; ++i){
+ j = (f[0] >> (i & 0x7)) & 1;
+ HDR += j << i ;
+ }
+
+ if (HDR == 0)
+ framesize = 24;
+ else if (HDR == 1)
+ framesize = 20;
+ else if (HDR == 2)
+ framesize = 4;
+ else if (HDR == 3)
+ framesize = 1;
+ else {
+ pj_assert(!"Unknown G723.1 frametype, packet may be corrupted!");
+ return PJMEDIA_CODEC_EINMODE;
+ }
+
+ frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frames[count].buf = f;
+ frames[count].size = framesize;
+ frames[count].timestamp.u64 = ts->u64 + count *
+ ipp_codec[codec_data->codec_idx].samples_per_frame;
+
+ f += framesize;
+ pkt_size -= framesize;
+
+ ++count;
+ }
+
+ *frame_cnt = count;
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_HAS_INTEL_IPP_CODEC_G723_1 */
+
+
+#if PJMEDIA_HAS_INTEL_IPP_CODEC_AMR || PJMEDIA_HAS_INTEL_IPP_CODEC_AMRWB
+
+#include <pjmedia-codec/amr_helper.h>
+
+typedef struct amr_settings_t {
+ pjmedia_codec_amr_pack_setting enc_setting;
+ pjmedia_codec_amr_pack_setting dec_setting;
+ pj_int8_t enc_mode;
+} amr_settings_t;
+
+
+/* Rearrange AMR bitstream and convert RTP frame into USC frame:
+ * - make the start_bit to be 0
+ * - if it is speech frame, reorder bitstream from sensitivity bits order
+ * to encoder bits order.
+ * - set the appropriate value of usc_frame.
+ */
+static void predecode_amr( ipp_private_t *codec_data,
+ const pjmedia_frame *rtp_frame,
+ USC_Bitstream *usc_frame)
+{
+ pjmedia_frame frame;
+ pjmedia_codec_amr_bit_info *info;
+ pjmedia_codec_amr_pack_setting *setting;
+
+ setting = &((amr_settings_t*)codec_data->codec_setting)->dec_setting;
+
+ frame = *rtp_frame;
+ pjmedia_codec_amr_predecode(rtp_frame, setting, &frame);
+ info = (pjmedia_codec_amr_bit_info*) &frame.bit_info;
+
+ usc_frame->pBuffer = frame.buf;
+ usc_frame->nbytes = frame.size;
+ if (info->mode != -1) {
+ usc_frame->bitrate = setting->amr_nb?
+ pjmedia_codec_amrnb_bitrates[info->mode]:
+ pjmedia_codec_amrwb_bitrates[info->mode];
+ } else {
+ usc_frame->bitrate = 0;
+ }
+
+ if (frame.size > 5) {
+ /* Speech */
+ if (info->good_quality)
+ usc_frame->frametype = 0;
+ else
+ usc_frame->frametype = setting->amr_nb ? 5 : 6;
+ } else if (frame.size == 5) {
+ /* SID */
+ if (info->good_quality) {
+ usc_frame->frametype = info->STI? 2 : 1;
+ } else {
+ usc_frame->frametype = setting->amr_nb ? 6 : 7;
+ }
+ } else {
+ /* no data */
+ usc_frame->frametype = 3;
+ }
+}
+
+/* Pack AMR payload */
+static pj_status_t pack_amr(ipp_private_t *codec_data, void *pkt,
+ pj_size_t *pkt_size, pj_size_t max_pkt_size)
+{
+ enum {MAX_FRAMES_PER_PACKET = PJMEDIA_MAX_FRAME_DURATION_MS / 20};
+
+ pjmedia_frame frames[MAX_FRAMES_PER_PACKET];
+ unsigned nframes = 0;
+ pjmedia_codec_amr_bit_info *info;
+ pj_uint8_t *r; /* Read cursor */
+ pj_uint8_t SID_FT;
+ pjmedia_codec_amr_pack_setting *setting;
+ const pj_uint8_t *framelen_tbl;
+
+ setting = &((amr_settings_t*)codec_data->codec_setting)->enc_setting;
+ framelen_tbl = setting->amr_nb? pjmedia_codec_amrnb_framelen:
+ pjmedia_codec_amrwb_framelen;
+
+ SID_FT = (pj_uint8_t)(setting->amr_nb? 8 : 9);
+
+ /* Align pkt buf right */
+ r = (pj_uint8_t*)pkt + max_pkt_size - *pkt_size;
+ pj_memmove(r, pkt, *pkt_size);
+
+ /* Get frames */
+ for (;;) {
+ pj_bool_t eof;
+ pj_uint16_t info_;
+
+ info_ = *((pj_uint16_t*)r);
+ eof = ((info_ & 0x40) != 0);
+
+ info = (pjmedia_codec_amr_bit_info*) &frames[nframes].bit_info;
+ pj_bzero(info, sizeof(*info));
+ info->frame_type = (pj_uint8_t)(info_ & 0x0F);
+ info->good_quality = (pj_uint8_t)((info_ & 0x80) == 0);
+ info->mode = (pj_int8_t) ((info_ >> 8) & 0x0F);
+ info->STI = (pj_uint8_t)((info_ >> 5) & 1);
+
+ frames[nframes].buf = r + 2;
+ frames[nframes].size = info->frame_type <= SID_FT ?
+ framelen_tbl[info->frame_type] : 0;
+
+ r += frames[nframes].size + 2;
+
+ /* Last frame */
+ if (++nframes >= MAX_FRAMES_PER_PACKET || eof)
+ break;
+ }
+
+ /* Pack */
+ *pkt_size = max_pkt_size;
+ return pjmedia_codec_amr_pack(frames, nframes, setting, pkt, pkt_size);
+}
+
+
+/* Parse AMR payload into frames. */
+static pj_status_t parse_amr(ipp_private_t *codec_data, void *pkt,
+ pj_size_t pkt_size, const pj_timestamp *ts,
+ unsigned *frame_cnt, pjmedia_frame frames[])
+{
+ amr_settings_t* s = (amr_settings_t*)codec_data->codec_setting;
+ pjmedia_codec_amr_pack_setting *setting;
+ pj_status_t status;
+ pj_uint8_t cmr;
+
+ setting = &s->dec_setting;
+
+ status = pjmedia_codec_amr_parse(pkt, pkt_size, ts, setting, frames,
+ frame_cnt, &cmr);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Check Change Mode Request. */
+ if (((setting->amr_nb && cmr <= 7) || (!setting->amr_nb && cmr <= 8)) &&
+ s->enc_mode != cmr)
+ {
+ struct ipp_codec *ippc = &ipp_codec[codec_data->codec_idx];
+
+ s->enc_mode = cmr;
+ codec_data->info->params.modes.bitrate = s->enc_setting.amr_nb?
+ pjmedia_codec_amrnb_bitrates[s->enc_mode] :
+ pjmedia_codec_amrwb_bitrates[s->enc_mode];
+ ippc->fxns->std.Control(&codec_data->info->params.modes,
+ codec_data->enc);
+
+ PJ_LOG(4,(THIS_FILE, "AMR%s switched encoding mode to: %d (%dbps)",
+ (s->enc_setting.amr_nb?"":"-WB"),
+ s->enc_mode,
+ codec_data->info->params.modes.bitrate));
+ }
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_HAS_INTEL_IPP_CODEC_AMR */
+
+
+#if PJMEDIA_HAS_INTEL_IPP_CODEC_G722_1
+
+static void predecode_g7221( ipp_private_t *codec_data,
+ const pjmedia_frame *rtp_frame,
+ USC_Bitstream *usc_frame)
+{
+ usc_frame->pBuffer = (char*)rtp_frame->buf;
+ usc_frame->nbytes = rtp_frame->size;
+ usc_frame->frametype = 0;
+ usc_frame->bitrate = codec_data->info->params.modes.bitrate;
+
+#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN!=0
+ {
+ pj_uint16_t *p, *p_end;
+
+ p = (pj_uint16_t*)rtp_frame->buf;
+ p_end = p + rtp_frame->size/2;
+ while (p < p_end) {
+ *p = pj_ntohs(*p);
+ ++p;
+ }
+ }
+#endif
+}
+
+static pj_status_t pack_g7221( ipp_private_t *codec_data, void *pkt,
+ pj_size_t *pkt_size, pj_size_t max_pkt_size)
+{
+ PJ_UNUSED_ARG(codec_data);
+ PJ_UNUSED_ARG(max_pkt_size);
+
+#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN!=0
+ {
+ pj_uint16_t *p, *p_end;
+
+ p = (pj_uint16_t*)pkt;
+ p_end = p + *pkt_size/2;
+ while (p < p_end) {
+ *p = pj_htons(*p);
+ ++p;
+ }
+ }
+#else
+ PJ_UNUSED_ARG(pkt);
+ PJ_UNUSED_ARG(pkt_size);
+#endif
+
+ return PJ_SUCCESS;
+}
+
+
+#include <pjmedia-codec/g7221.h>
+
+
+PJ_DEF(pj_status_t) pjmedia_codec_g7221_set_pcm_shift(int val)
+{
+ PJ_ASSERT_RETURN(val >= 0, PJ_EINVAL);
+
+ ipp_factory.g7221_pcm_shift = val;
+ return PJ_SUCCESS;
+}
+
+
+#endif /* PJMEDIA_HAS_INTEL_IPP_CODEC_G722_1 */
+
+/*
+ * Initialize and register IPP codec factory to pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_ipp_init( pjmedia_endpt *endpt )
+{
+ pjmedia_codec_mgr *codec_mgr;
+ pj_str_t codec_name;
+ pj_status_t status;
+
+ if (ipp_factory.pool != NULL) {
+ /* Already initialized. */
+ return PJ_SUCCESS;
+ }
+
+ /* Create IPP codec factory. */
+ ipp_factory.base.op = &ipp_factory_op;
+ ipp_factory.base.factory_data = NULL;
+ ipp_factory.endpt = endpt;
+ ipp_factory.g7221_pcm_shift = PJMEDIA_G7221_DEFAULT_PCM_SHIFT;
+
+ ipp_factory.pool = pjmedia_endpt_create_pool(endpt, "IPP codecs", 4000, 4000);
+ if (!ipp_factory.pool)
+ return PJ_ENOMEM;
+
+ /* Create mutex. */
+ status = pj_mutex_create_simple(ipp_factory.pool, "IPP codecs",
+ &ipp_factory.mutex);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
+ if (!codec_mgr) {
+ status = PJ_EINVALIDOP;
+ goto on_error;
+ }
+
+ /* Register format match callback. */
+#if PJMEDIA_HAS_INTEL_IPP_CODEC_G722_1
+ pj_cstr(&codec_name, "G7221");
+ status = pjmedia_sdp_neg_register_fmt_match_cb(
+ &codec_name,
+ &pjmedia_codec_g7221_match_sdp);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+#endif
+
+#if PJMEDIA_HAS_INTEL_IPP_CODEC_AMR
+ pj_cstr(&codec_name, "AMR");
+ status = pjmedia_sdp_neg_register_fmt_match_cb(
+ &codec_name,
+ &pjmedia_codec_amr_match_sdp);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+#endif
+
+#if PJMEDIA_HAS_INTEL_IPP_CODEC_AMRWB
+ pj_cstr(&codec_name, "AMR-WB");
+ status = pjmedia_sdp_neg_register_fmt_match_cb(
+ &codec_name,
+ &pjmedia_codec_amr_match_sdp);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+#endif
+
+ /* Suppress compile warning */
+ PJ_UNUSED_ARG(codec_name);
+
+ /* Register codec factory to endpoint. */
+ status = pjmedia_codec_mgr_register_factory(codec_mgr,
+ &ipp_factory.base);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Done. */
+ return PJ_SUCCESS;
+
+on_error:
+ pj_pool_release(ipp_factory.pool);
+ ipp_factory.pool = NULL;
+ return status;
+}
+
+/*
+ * Unregister IPP codecs factory from pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_ipp_deinit(void)
+{
+ pjmedia_codec_mgr *codec_mgr;
+ pj_status_t status;
+
+ if (ipp_factory.pool == NULL) {
+ /* Already deinitialized */
+ return PJ_SUCCESS;
+ }
+
+ pj_mutex_lock(ipp_factory.mutex);
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(ipp_factory.endpt);
+ if (!codec_mgr) {
+ pj_pool_release(ipp_factory.pool);
+ ipp_factory.pool = NULL;
+ return PJ_EINVALIDOP;
+ }
+
+ /* Unregister IPP codecs factory. */
+ status = pjmedia_codec_mgr_unregister_factory(codec_mgr,
+ &ipp_factory.base);
+
+ /* Destroy mutex. */
+ pj_mutex_destroy(ipp_factory.mutex);
+
+ /* Destroy pool. */
+ pj_pool_release(ipp_factory.pool);
+ ipp_factory.pool = NULL;
+
+ return status;
+}
+
+
+/*
+ * Check if factory can allocate the specified codec.
+ */
+static pj_status_t ipp_test_alloc( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *info )
+{
+ unsigned i;
+
+ PJ_UNUSED_ARG(factory);
+
+ /* Type MUST be audio. */
+ if (info->type != PJMEDIA_TYPE_AUDIO)
+ return PJMEDIA_CODEC_EUNSUP;
+
+ for (i = 0; i < PJ_ARRAY_SIZE(ipp_codec); ++i) {
+ pj_str_t name = pj_str((char*)ipp_codec[i].name);
+ if ((pj_stricmp(&info->encoding_name, &name) == 0) &&
+ (info->clock_rate == (unsigned)ipp_codec[i].clock_rate) &&
+ (info->channel_cnt == (unsigned)ipp_codec[i].channel_count) &&
+ (ipp_codec[i].enabled))
+ {
+ return PJ_SUCCESS;
+ }
+ }
+
+ /* Unsupported, or mode is disabled. */
+ return PJMEDIA_CODEC_EUNSUP;
+}
+
+/*
+ * Generate default attribute.
+ */
+static pj_status_t ipp_default_attr (pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr )
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(factory==&ipp_factory.base, PJ_EINVAL);
+
+ pj_bzero(attr, sizeof(pjmedia_codec_param));
+
+ for (i = 0; i < PJ_ARRAY_SIZE(ipp_codec); ++i) {
+ pj_str_t name = pj_str((char*)ipp_codec[i].name);
+ if ((pj_stricmp(&id->encoding_name, &name) == 0) &&
+ (id->clock_rate == (unsigned)ipp_codec[i].clock_rate) &&
+ (id->channel_cnt == (unsigned)ipp_codec[i].channel_count) &&
+ (id->pt == (unsigned)ipp_codec[i].pt))
+ {
+ attr->info.pt = (pj_uint8_t)id->pt;
+ attr->info.channel_cnt = ipp_codec[i].channel_count;
+ attr->info.clock_rate = ipp_codec[i].clock_rate;
+ attr->info.avg_bps = ipp_codec[i].def_bitrate;
+ attr->info.max_bps = ipp_codec[i].max_bitrate;
+ attr->info.pcm_bits_per_sample = 16;
+ attr->info.frm_ptime = (pj_uint16_t)
+ (ipp_codec[i].samples_per_frame * 1000 /
+ ipp_codec[i].channel_count /
+ ipp_codec[i].clock_rate);
+ attr->setting.frm_per_pkt = ipp_codec[i].frm_per_pkt;
+
+ /* Default flags. */
+ attr->setting.plc = 1;
+ attr->setting.penh= 0;
+ attr->setting.vad = 1;
+ attr->setting.cng = attr->setting.vad;
+ attr->setting.dec_fmtp = ipp_codec[i].dec_fmtp;
+
+ if (attr->setting.vad == 0) {
+#if PJMEDIA_HAS_INTEL_IPP_CODEC_G729
+ if (id->pt == PJMEDIA_RTP_PT_G729) {
+ /* Signal G729 Annex B is being disabled */
+ attr->setting.dec_fmtp.cnt = 1;
+ pj_strset2(&attr->setting.dec_fmtp.param[0].name, "annexb");
+ pj_strset2(&attr->setting.dec_fmtp.param[0].val, "no");
+ }
+#endif
+ }
+
+ return PJ_SUCCESS;
+ }
+ }
+
+ return PJMEDIA_CODEC_EUNSUP;
+}
+
+/*
+ * Enum codecs supported by this factory.
+ */
+static pj_status_t ipp_enum_codecs(pjmedia_codec_factory *factory,
+ unsigned *count,
+ pjmedia_codec_info codecs[])
+{
+ unsigned max;
+ unsigned i;
+
+ PJ_UNUSED_ARG(factory);
+ PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL);
+
+ max = *count;
+
+ for (i = 0, *count = 0; i < PJ_ARRAY_SIZE(ipp_codec) && *count < max; ++i)
+ {
+ if (!ipp_codec[i].enabled)
+ continue;
+
+ pj_bzero(&codecs[*count], sizeof(pjmedia_codec_info));
+ codecs[*count].encoding_name = pj_str((char*)ipp_codec[i].name);
+ codecs[*count].pt = ipp_codec[i].pt;
+ codecs[*count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[*count].clock_rate = ipp_codec[i].clock_rate;
+ codecs[*count].channel_cnt = ipp_codec[i].channel_count;
+
+ ++*count;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Allocate a new codec instance.
+ */
+static pj_status_t ipp_alloc_codec( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec)
+{
+ ipp_private_t *codec_data;
+ pjmedia_codec *codec;
+ int idx;
+ pj_pool_t *pool;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory == &ipp_factory.base, PJ_EINVAL);
+
+ pj_mutex_lock(ipp_factory.mutex);
+
+ /* Find codec's index */
+ idx = -1;
+ for (i = 0; i < PJ_ARRAY_SIZE(ipp_codec); ++i) {
+ pj_str_t name = pj_str((char*)ipp_codec[i].name);
+ if ((pj_stricmp(&id->encoding_name, &name) == 0) &&
+ (id->clock_rate == (unsigned)ipp_codec[i].clock_rate) &&
+ (id->channel_cnt == (unsigned)ipp_codec[i].channel_count) &&
+ (ipp_codec[i].enabled))
+ {
+ idx = i;
+ break;
+ }
+ }
+ if (idx == -1) {
+ *p_codec = NULL;
+ return PJMEDIA_CODEC_EFAILED;
+ }
+
+ /* Create pool for codec instance */
+ pool = pjmedia_endpt_create_pool(ipp_factory.endpt, "IPPcodec", 512, 512);
+ codec = PJ_POOL_ZALLOC_T(pool, pjmedia_codec);
+ PJ_ASSERT_RETURN(codec != NULL, PJ_ENOMEM);
+ codec->op = &ipp_op;
+ codec->factory = factory;
+ codec->codec_data = PJ_POOL_ZALLOC_T(pool, ipp_private_t);
+ codec_data = (ipp_private_t*) codec->codec_data;
+
+ /* Create PLC if codec has no internal PLC */
+ if (!ipp_codec[idx].has_native_plc) {
+ pj_status_t status;
+ status = pjmedia_plc_create(pool, ipp_codec[idx].clock_rate,
+ ipp_codec[idx].samples_per_frame, 0,
+ &codec_data->plc);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(pool);
+ pj_mutex_unlock(ipp_factory.mutex);
+ return status;
+ }
+ }
+
+ /* Create silence detector if codec has no internal VAD */
+ if (!ipp_codec[idx].has_native_vad) {
+ pj_status_t status;
+ status = pjmedia_silence_det_create(pool,
+ ipp_codec[idx].clock_rate,
+ ipp_codec[idx].samples_per_frame,
+ &codec_data->vad);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(pool);
+ pj_mutex_unlock(ipp_factory.mutex);
+ return status;
+ }
+ }
+
+ codec_data->pool = pool;
+ codec_data->codec_idx = idx;
+
+ pj_mutex_unlock(ipp_factory.mutex);
+
+ *p_codec = codec;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Free codec.
+ */
+static pj_status_t ipp_dealloc_codec( pjmedia_codec_factory *factory,
+ pjmedia_codec *codec )
+{
+ ipp_private_t *codec_data;
+
+ PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory == &ipp_factory.base, PJ_EINVAL);
+
+ /* Close codec, if it's not closed. */
+ codec_data = (ipp_private_t*) codec->codec_data;
+ if (codec_data->enc != NULL || codec_data->dec != NULL) {
+ ipp_codec_close(codec);
+ }
+
+ pj_pool_release(codec_data->pool);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Init codec.
+ */
+static pj_status_t ipp_codec_init( pjmedia_codec *codec,
+ pj_pool_t *pool )
+{
+ PJ_UNUSED_ARG(codec);
+ PJ_UNUSED_ARG(pool);
+ return PJ_SUCCESS;
+}
+
+/*
+ * Open codec.
+ */
+static pj_status_t ipp_codec_open( pjmedia_codec *codec,
+ pjmedia_codec_param *attr )
+{
+ ipp_private_t *codec_data = (ipp_private_t*) codec->codec_data;
+ struct ipp_codec *ippc = &ipp_codec[codec_data->codec_idx];
+ int info_size;
+ pj_pool_t *pool;
+ int i, j;
+ USC_MemBank *membanks;
+ int nb_membanks;
+
+ pool = codec_data->pool;
+
+ /* Get the codec info size */
+ if (USC_NoError != ippc->fxns->std.GetInfoSize(&info_size)) {
+ PJ_LOG(1,(THIS_FILE, "Error getting codec info size"));
+ goto on_error;
+ }
+ /* Get the codec info */
+ codec_data->info = pj_pool_zalloc(pool, info_size);
+ if (USC_NoError != ippc->fxns->std.GetInfo((USC_Handle)NULL,
+ codec_data->info))
+ {
+ PJ_LOG(1,(THIS_FILE, "Error getting codec info"));
+ goto on_error;
+ }
+
+ /* PREPARING THE ENCODER */
+
+ /* Setting the encoder params */
+ codec_data->info->params.direction = USC_ENCODE;
+ codec_data->info->params.modes.vad = attr->setting.vad &&
+ ippc->has_native_vad;
+ codec_data->info->params.modes.bitrate = attr->info.avg_bps;
+ codec_data->info->params.law = 0; /* Linear PCM input */
+
+#if PJMEDIA_HAS_INTEL_IPP_CODEC_G729
+ if (ippc->pt == PJMEDIA_RTP_PT_G729) {
+ /* Check if G729 Annex B is signaled to be disabled */
+ for (i = 0; i < attr->setting.enc_fmtp.cnt; ++i) {
+ if (pj_stricmp2(&attr->setting.enc_fmtp.param[i].name, "annexb")==0)
+ {
+ if (pj_stricmp2(&attr->setting.enc_fmtp.param[i].val, "no")==0)
+ {
+ attr->setting.vad = 0;
+ codec_data->info->params.modes.vad = 0;
+ }
+ break;
+ }
+ }
+ }
+#endif
+
+ /* Get number of memory blocks needed by the encoder */
+ if (USC_NoError != ippc->fxns->std.NumAlloc(&codec_data->info->params,
+ &nb_membanks))
+ {
+ PJ_LOG(1,(THIS_FILE, "Error getting no of memory blocks of encoder"));
+ goto on_error;
+ }
+
+ /* Allocate memory blocks table */
+ membanks = (USC_MemBank*) pj_pool_zalloc(pool,
+ sizeof(USC_MemBank) * nb_membanks);
+ /* Get size of each memory block */
+ if (USC_NoError != ippc->fxns->std.MemAlloc(&codec_data->info->params,
+ membanks))
+ {
+ PJ_LOG(1,(THIS_FILE, "Error getting memory blocks size of encoder"));
+ goto on_error;
+ }
+
+ /* Allocate memory for each block */
+ for (i = 0; i < nb_membanks; i++) {
+ membanks[i].pMem = (char*) pj_pool_zalloc(pool, membanks[i].nbytes);
+ }
+
+ /* Create encoder instance */
+ if (USC_NoError != ippc->fxns->std.Init(&codec_data->info->params,
+ membanks,
+ &codec_data->enc))
+ {
+ PJ_LOG(1,(THIS_FILE, "Error initializing encoder"));
+ goto on_error;
+ }
+
+ /* PREPARING THE DECODER */
+
+ /* Setting the decoder params */
+ codec_data->info->params.direction = USC_DECODE;
+
+ /* Not sure if VAD affects decoder, just try to be safe */
+ //codec_data->info->params.modes.vad = ippc->has_native_vad;
+
+ /* Get number of memory blocks needed by the decoder */
+ if (USC_NoError != ippc->fxns->std.NumAlloc(&codec_data->info->params,
+ &nb_membanks))
+ {
+ PJ_LOG(1,(THIS_FILE, "Error getting no of memory blocks of decoder"));
+ goto on_error;
+ }
+
+ /* Allocate memory blocks table */
+ membanks = (USC_MemBank*) pj_pool_zalloc(pool,
+ sizeof(USC_MemBank) * nb_membanks);
+ /* Get size of each memory block */
+ if (USC_NoError != ippc->fxns->std.MemAlloc(&codec_data->info->params,
+ membanks))
+ {
+ PJ_LOG(1,(THIS_FILE, "Error getting memory blocks size of decoder"));
+ goto on_error;
+ }
+
+ /* Allocate memory for each block */
+ for (i = 0; i < nb_membanks; i++) {
+ membanks[i].pMem = (char*) pj_pool_zalloc(pool, membanks[i].nbytes);
+ }
+
+ /* Create decoder instance */
+ if (USC_NoError != ippc->fxns->std.Init(&codec_data->info->params,
+ membanks, &codec_data->dec))
+ {
+ PJ_LOG(1,(THIS_FILE, "Error initializing decoder"));
+ goto on_error;
+ }
+
+ /* Update codec info */
+ ippc->fxns->std.GetInfo((USC_Handle)codec_data->enc, codec_data->info);
+
+ /* Get bitstream size */
+ i = codec_data->info->params.modes.bitrate * ippc->samples_per_frame;
+ j = ippc->clock_rate << 3;
+ codec_data->frame_size = (pj_uint16_t)(i / j);
+ if (i % j) ++codec_data->frame_size;
+
+ codec_data->vad_enabled = (attr->setting.vad != 0);
+ codec_data->plc_enabled = (attr->setting.plc != 0);
+
+#if PJMEDIA_HAS_INTEL_IPP_CODEC_AMR
+ /* Init AMR settings */
+ if (ippc->pt == PJMEDIA_RTP_PT_AMR || ippc->pt == PJMEDIA_RTP_PT_AMRWB) {
+ amr_settings_t *s;
+ pj_uint8_t octet_align = 0;
+ pj_int8_t enc_mode;
+
+ enc_mode = pjmedia_codec_amr_get_mode(
+ codec_data->info->params.modes.bitrate);
+ pj_assert(enc_mode >= 0 && enc_mode <= 8);
+
+ /* Check AMR specific attributes */
+
+ for (i = 0; i < attr->setting.dec_fmtp.cnt; ++i) {
+ /* octet-align, one of the parameters that must have same value
+ * in offer & answer (RFC 4867 Section 8.3.1). Just check fmtp
+ * in the decoder side, since it's value is guaranteed to fulfil
+ * above requirement (by SDP negotiator).
+ */
+ const pj_str_t STR_FMTP_OCTET_ALIGN = {"octet-align", 11};
+
+ if (pj_stricmp(&attr->setting.dec_fmtp.param[i].name,
+ &STR_FMTP_OCTET_ALIGN) == 0)
+ {
+ octet_align=(pj_uint8_t)
+ pj_strtoul(&attr->setting.dec_fmtp.param[i].val);
+ break;
+ }
+ }
+
+ for (i = 0; i < attr->setting.enc_fmtp.cnt; ++i) {
+ /* mode-set, encoding mode is chosen based on local default mode
+ * setting:
+ * - if local default mode is included in the mode-set, use it
+ * - otherwise, find the closest mode to local default mode;
+ * if there are two closest modes, prefer to use the higher
+ * one, e.g: local default mode is 4, the mode-set param
+ * contains '2,3,5,6', then 5 will be chosen.
+ */
+ const pj_str_t STR_FMTP_MODE_SET = {"mode-set", 8};
+
+ if (pj_stricmp(&attr->setting.enc_fmtp.param[i].name,
+ &STR_FMTP_MODE_SET) == 0)
+ {
+ const char *p;
+ pj_size_t l;
+ pj_int8_t diff = 99;
+
+ p = pj_strbuf(&attr->setting.enc_fmtp.param[i].val);
+ l = pj_strlen(&attr->setting.enc_fmtp.param[i].val);
+
+ while (l--) {
+ if ((ippc->pt==PJMEDIA_RTP_PT_AMR && *p>='0' && *p<='7') ||
+ (ippc->pt==PJMEDIA_RTP_PT_AMRWB && *p>='0' && *p<='8'))
+ {
+ pj_int8_t tmp = (pj_int8_t)(*p - '0' - enc_mode);
+
+ if (PJ_ABS(diff) > PJ_ABS(tmp) ||
+ (PJ_ABS(diff) == PJ_ABS(tmp) && tmp > diff))
+ {
+ diff = tmp;
+ if (diff == 0) break;
+ }
+ }
+ ++p;
+ }
+
+ if (diff == 99)
+ goto on_error;
+
+ enc_mode = (pj_int8_t)(enc_mode + diff);
+
+ break;
+ }
+ }
+
+ /* Initialize AMR specific settings */
+ s = PJ_POOL_ZALLOC_T(pool, amr_settings_t);
+ codec_data->codec_setting = s;
+
+ s->enc_setting.amr_nb = (pj_uint8_t)(ippc->pt == PJMEDIA_RTP_PT_AMR);
+ s->enc_setting.octet_aligned = octet_align;
+ s->enc_setting.reorder = PJ_TRUE;
+ s->enc_setting.cmr = 15;
+
+ s->dec_setting.amr_nb = (pj_uint8_t)(ippc->pt == PJMEDIA_RTP_PT_AMR);
+ s->dec_setting.octet_aligned = octet_align;
+ s->dec_setting.reorder = PJ_TRUE;
+
+ /* Apply encoder mode/bitrate */
+ s->enc_mode = enc_mode;
+ codec_data->info->params.modes.bitrate = s->enc_setting.amr_nb?
+ pjmedia_codec_amrnb_bitrates[s->enc_mode]:
+ pjmedia_codec_amrwb_bitrates[s->enc_mode];
+ ippc->fxns->std.Control(&codec_data->info->params.modes,
+ codec_data->enc);
+
+ PJ_LOG(4,(THIS_FILE, "AMR%s encoding mode: %d (%dbps)",
+ (s->enc_setting.amr_nb?"":"-WB"),
+ s->enc_mode,
+ codec_data->info->params.modes.bitrate));
+
+ /* Return back bitrate info to application */
+ attr->info.avg_bps = codec_data->info->params.modes.bitrate;
+ }
+#endif
+
+#if PJMEDIA_HAS_INTEL_IPP_CODEC_G722_1
+ if (ippc->pt >= PJMEDIA_RTP_PT_G722_1_16 &&
+ ippc->pt <= PJMEDIA_RTP_PT_G7221_RSV2)
+ {
+ codec_data->g7221_pcm_shift = ipp_factory.g7221_pcm_shift;
+ }
+#endif
+
+ return PJ_SUCCESS;
+
+on_error:
+ return PJMEDIA_CODEC_EFAILED;
+}
+
+/*
+ * Close codec.
+ */
+static pj_status_t ipp_codec_close( pjmedia_codec *codec )
+{
+ PJ_UNUSED_ARG(codec);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Modify codec settings.
+ */
+static pj_status_t ipp_codec_modify(pjmedia_codec *codec,
+ const pjmedia_codec_param *attr )
+{
+ ipp_private_t *codec_data = (ipp_private_t*) codec->codec_data;
+ struct ipp_codec *ippc = &ipp_codec[codec_data->codec_idx];
+
+ codec_data->vad_enabled = (attr->setting.vad != 0);
+ codec_data->plc_enabled = (attr->setting.plc != 0);
+
+ if (ippc->has_native_vad) {
+ USC_Modes modes;
+
+ modes = codec_data->info->params.modes;
+ modes.vad = codec_data->vad_enabled;
+ ippc->fxns->std.Control(&modes, codec_data->enc);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get frames in the packet.
+ */
+static pj_status_t ipp_codec_parse( pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[])
+{
+ ipp_private_t *codec_data = (ipp_private_t*) codec->codec_data;
+ struct ipp_codec *ippc = &ipp_codec[codec_data->codec_idx];
+ unsigned count = 0;
+
+ PJ_ASSERT_RETURN(frame_cnt, PJ_EINVAL);
+
+ if (ippc->parse != NULL) {
+ return ippc->parse(codec_data, pkt, pkt_size, ts, frame_cnt, frames);
+ }
+
+ while (pkt_size >= codec_data->frame_size && count < *frame_cnt) {
+ frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frames[count].buf = pkt;
+ frames[count].size = codec_data->frame_size;
+ frames[count].timestamp.u64 = ts->u64 + count*ippc->samples_per_frame;
+
+ pkt = ((char*)pkt) + codec_data->frame_size;
+ pkt_size -= codec_data->frame_size;
+
+ ++count;
+ }
+
+ if (pkt_size && count < *frame_cnt) {
+ frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frames[count].buf = pkt;
+ frames[count].size = pkt_size;
+ frames[count].timestamp.u64 = ts->u64 + count*ippc->samples_per_frame;
+ ++count;
+ }
+
+ *frame_cnt = count;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Encode frames.
+ */
+static pj_status_t ipp_codec_encode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ ipp_private_t *codec_data = (ipp_private_t*) codec->codec_data;
+ struct ipp_codec *ippc = &ipp_codec[codec_data->codec_idx];
+ unsigned samples_per_frame;
+ unsigned nsamples;
+ pj_size_t tx = 0;
+ pj_int16_t *pcm_in = (pj_int16_t*)input->buf;
+ pj_uint8_t *bits_out = (pj_uint8_t*) output->buf;
+ pj_uint8_t pt;
+
+ /* Invoke external VAD if codec has no internal VAD */
+ if (codec_data->vad && codec_data->vad_enabled) {
+ pj_bool_t is_silence;
+ pj_int32_t silence_duration;
+
+ silence_duration = pj_timestamp_diff32(&codec_data->last_tx,
+ &input->timestamp);
+
+ is_silence = pjmedia_silence_det_detect(codec_data->vad,
+ (const pj_int16_t*) input->buf,
+ (input->size >> 1),
+ NULL);
+ if (is_silence &&
+ (PJMEDIA_CODEC_MAX_SILENCE_PERIOD == -1 ||
+ silence_duration < (PJMEDIA_CODEC_MAX_SILENCE_PERIOD *
+ (int)ippc->clock_rate / 1000)))
+ {
+ output->type = PJMEDIA_FRAME_TYPE_NONE;
+ output->buf = NULL;
+ output->size = 0;
+ output->timestamp = input->timestamp;
+ return PJ_SUCCESS;
+ } else {
+ codec_data->last_tx = input->timestamp;
+ }
+ }
+
+ nsamples = input->size >> 1;
+ samples_per_frame = ippc->samples_per_frame;
+ pt = ippc->pt;
+
+ PJ_ASSERT_RETURN(nsamples % samples_per_frame == 0,
+ PJMEDIA_CODEC_EPCMFRMINLEN);
+
+ /* Encode the frames */
+ while (nsamples >= samples_per_frame) {
+ USC_PCMStream in;
+ USC_Bitstream out;
+
+ in.bitrate = codec_data->info->params.modes.bitrate;
+ in.nbytes = samples_per_frame << 1;
+ in.pBuffer = (char*)pcm_in;
+ in.pcmType.bitPerSample = codec_data->info->params.pcmType.bitPerSample;
+ in.pcmType.nChannels = codec_data->info->params.pcmType.nChannels;
+ in.pcmType.sample_frequency = codec_data->info->params.pcmType.sample_frequency;
+
+ out.pBuffer = (char*)bits_out;
+
+#if PJMEDIA_HAS_INTEL_IPP_CODEC_AMR
+ /* For AMR: reserve two octets for AMR frame info */
+ if (pt == PJMEDIA_RTP_PT_AMR || pt == PJMEDIA_RTP_PT_AMRWB) {
+ out.pBuffer += 2;
+ }
+#endif
+
+#if PJMEDIA_HAS_INTEL_IPP_CODEC_G722_1
+ /* For G722.1: adjust the encoder input signal level */
+ if (pt >= PJMEDIA_RTP_PT_G722_1_16 &&
+ pt <= PJMEDIA_RTP_PT_G7221_RSV2 &&
+ codec_data->g7221_pcm_shift)
+ {
+ unsigned i;
+ for (i = 0; i < samples_per_frame; ++i)
+ pcm_in[i] >>= codec_data->g7221_pcm_shift;
+ }
+#endif
+
+ if (USC_NoError != ippc->fxns->Encode(codec_data->enc, &in, &out)) {
+ break;
+ }
+
+#if PJMEDIA_HAS_INTEL_IPP_CODEC_AMR
+ /* For AMR: put info (frametype, degraded, last frame, mode) in the
+ * first two octets for payload packing.
+ */
+ if (pt == PJMEDIA_RTP_PT_AMR || pt == PJMEDIA_RTP_PT_AMRWB) {
+ pj_uint16_t *info = (pj_uint16_t*)bits_out;
+
+ /* Two octets for AMR frame info, 0=LSB:
+ * bit 0-3 : frame type
+ * bit 5 : STI flag
+ * bit 6 : last frame flag
+ * bit 7 : quality flag
+ * bit 8-11 : mode
+ */
+ out.nbytes += 2;
+ if (out.frametype == 0 || out.frametype == 4 ||
+ (pt == PJMEDIA_RTP_PT_AMR && out.frametype == 5) ||
+ (pt == PJMEDIA_RTP_PT_AMRWB && out.frametype == 6))
+ {
+ /* Speech frame type */
+ *info = (char)pjmedia_codec_amr_get_mode(out.bitrate);
+ /* Quality */
+ if (out.frametype == 5 || out.frametype == 6)
+ *info |= 0x80;
+ } else if (out.frametype == 1 || out.frametype == 2 ||
+ (pt == PJMEDIA_RTP_PT_AMR && out.frametype == 6) ||
+ (pt == PJMEDIA_RTP_PT_AMRWB && out.frametype == 7))
+ {
+ /* SID frame type */
+ *info = (pj_uint8_t)(pt == PJMEDIA_RTP_PT_AMRWB? 9 : 8);
+ /* Quality */
+ if (out.frametype == 6 || out.frametype == 7)
+ *info |= 0x80;
+ /* STI */
+ if (out.frametype != 1)
+ *info |= 0x20;
+ } else {
+ /* Untransmited */
+ *info = 15;
+ out.nbytes = 2;
+ }
+
+ /* Mode */
+ *info |= (char)pjmedia_codec_amr_get_mode(out.bitrate) << 8;
+
+ /* Last frame flag */
+ if (nsamples == samples_per_frame)
+ *info |= 0x40;
+ }
+#endif
+
+ pcm_in += samples_per_frame;
+ nsamples -= samples_per_frame;
+ tx += out.nbytes;
+ bits_out += out.nbytes;
+
+#if PJMEDIA_HAS_INTEL_IPP_CODEC_G729
+ if (pt == PJMEDIA_RTP_PT_G729) {
+ if (out.frametype == 1) {
+ /* SID */
+ break;
+ } else if (out.frametype == 0) {
+ /* Untransmitted */
+ tx -= out.nbytes;
+ break;
+ }
+ }
+#endif
+
+ }
+
+ if (ippc->pack != NULL) {
+ ippc->pack(codec_data, output->buf, &tx, output_buf_len);
+ }
+
+ /* Check if we don't need to transmit the frame (DTX) */
+ if (tx == 0) {
+ output->buf = NULL;
+ output->size = 0;
+ output->timestamp.u64 = input->timestamp.u64;
+ output->type = PJMEDIA_FRAME_TYPE_NONE;
+ return PJ_SUCCESS;
+ }
+
+ output->size = tx;
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->timestamp = input->timestamp;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Decode frame.
+ */
+static pj_status_t ipp_codec_decode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ ipp_private_t *codec_data = (ipp_private_t*) codec->codec_data;
+ struct ipp_codec *ippc = &ipp_codec[codec_data->codec_idx];
+ unsigned samples_per_frame;
+ USC_PCMStream out;
+ USC_Bitstream in;
+ pj_uint8_t pt;
+
+ pt = ippc->pt;
+ samples_per_frame = ippc->samples_per_frame;
+
+ PJ_ASSERT_RETURN(output_buf_len >= samples_per_frame << 1,
+ PJMEDIA_CODEC_EPCMTOOSHORT);
+
+ if (input->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+ if (ippc->predecode) {
+ ippc->predecode(codec_data, input, &in);
+ } else {
+ /* Most IPP codecs have frametype==0 for speech frame */
+ in.pBuffer = (char*)input->buf;
+ in.nbytes = input->size;
+ in.frametype = 0;
+ in.bitrate = codec_data->info->params.modes.bitrate;
+ }
+
+ out.pBuffer = output->buf;
+ }
+
+ if (input->type != PJMEDIA_FRAME_TYPE_AUDIO ||
+ USC_NoError != ippc->fxns->Decode(codec_data->dec, &in, &out))
+ {
+ pjmedia_zero_samples((pj_int16_t*)output->buf, samples_per_frame);
+ output->size = samples_per_frame << 1;
+ output->timestamp.u64 = input->timestamp.u64;
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ return PJ_SUCCESS;
+ }
+
+#if PJMEDIA_HAS_INTEL_IPP_CODEC_G726
+ /* For G.726: amplify decoding result (USC G.726 encoder deamplified it) */
+ if (pt == PJMEDIA_RTP_PT_G726_16 || pt == PJMEDIA_RTP_PT_G726_24 ||
+ pt == PJMEDIA_RTP_PT_G726_32 || pt == PJMEDIA_RTP_PT_G726_40 ||
+ pt == PJMEDIA_RTP_PT_G721)
+ {
+ unsigned i;
+ pj_int16_t *s = (pj_int16_t*)output->buf;
+
+ for (i = 0; i < samples_per_frame; ++i)
+ s[i] <<= 2;
+ }
+#endif
+
+#if PJMEDIA_HAS_INTEL_IPP_CODEC_G722_1
+ /* For G722.1: adjust the decoder output signal level */
+ if (pt >= PJMEDIA_RTP_PT_G722_1_16 &&
+ pt <= PJMEDIA_RTP_PT_G7221_RSV2 &&
+ codec_data->g7221_pcm_shift)
+ {
+ unsigned i;
+ pj_int16_t *s = (pj_int16_t*)output->buf;
+
+ for (i = 0; i < samples_per_frame; ++i)
+ s[i] <<= codec_data->g7221_pcm_shift;
+ }
+#endif
+
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->size = samples_per_frame << 1;
+ output->timestamp.u64 = input->timestamp.u64;
+
+ /* Invoke external PLC if codec has no internal PLC */
+ if (codec_data->plc && codec_data->plc_enabled)
+ pjmedia_plc_save(codec_data->plc, (pj_int16_t*)output->buf);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Recover lost frame.
+ */
+static pj_status_t ipp_codec_recover(pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ ipp_private_t *codec_data = (ipp_private_t*) codec->codec_data;
+ struct ipp_codec *ippc = &ipp_codec[codec_data->codec_idx];
+ unsigned samples_per_frame;
+
+ PJ_UNUSED_ARG(output_buf_len);
+
+ samples_per_frame = ippc->samples_per_frame;
+
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->size = samples_per_frame << 1;
+
+ if (codec_data->plc_enabled) {
+ if (codec_data->plc) {
+ pjmedia_plc_generate(codec_data->plc, (pj_int16_t*)output->buf);
+ } else {
+ USC_PCMStream out;
+ out.pBuffer = output->buf;
+ ippc->fxns->Decode(codec_data->dec, NULL, &out);
+ }
+ } else {
+ pjmedia_zero_samples((pj_int16_t*)output->buf, samples_per_frame);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+#if defined(_MSC_VER) && PJMEDIA_AUTO_LINK_IPP_LIBS
+# pragma comment( lib, "ippcore.lib")
+# pragma comment( lib, "ipps.lib")
+# pragma comment( lib, "ippsc.lib")
+# if defined(IPP_VERSION_MAJOR) && IPP_VERSION_MAJOR<=6
+# pragma comment( lib, "ippsr.lib")
+# endif
+//# pragma comment( lib, "ippcorel.lib")
+//# pragma comment( lib, "ippsemerged.lib")
+//# pragma comment( lib, "ippsmerged.lib")
+//# pragma comment( lib, "ippscemerged.lib")
+//# pragma comment( lib, "ippscmerged.lib")
+//# pragma comment( lib, "ippsremerged.lib")
+//# pragma comment( lib, "ippsrmerged.lib")
+# if defined(IPP_VERSION_MAJOR) && IPP_VERSION_MAJOR>=6
+# pragma comment( lib, "speech.lib")
+# else
+# pragma comment( lib, "usc.lib")
+# endif
+#endif
+
+
+#endif /* PJMEDIA_HAS_INTEL_IPP */
+
diff --git a/pjmedia/src/pjmedia-codec/l16.c b/pjmedia/src/pjmedia-codec/l16.c
new file mode 100644
index 0000000..063abc9
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/l16.c
@@ -0,0 +1,729 @@
+/* $Id: l16.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia-codec/l16.h>
+#include <pjmedia/codec.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/endpoint.h>
+#include <pjmedia/plc.h>
+#include <pjmedia/silencedet.h>
+#include <pj/assert.h>
+#include <pj/pool.h>
+#include <pj/sock.h>
+#include <pj/string.h>
+
+
+/*
+ * Only build this file if PJMEDIA_HAS_L16_CODEC != 0
+ */
+#if defined(PJMEDIA_HAS_L16_CODEC) && PJMEDIA_HAS_L16_CODEC != 0
+
+#define PLC_DISABLED 0
+
+
+static const pj_str_t STR_L16 = { "L16", 3 };
+
+/* To keep frame size below 1400 MTU, set ptime to 10ms for
+ * sampling rate > 35 KHz
+ */
+#define GET_PTIME(clock_rate) ((pj_uint16_t)(clock_rate > 35000 ? 10 : 20))
+
+
+/* Prototypes for L16 factory */
+static pj_status_t l16_test_alloc( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id );
+static pj_status_t l16_default_attr( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr );
+static pj_status_t l16_enum_codecs (pjmedia_codec_factory *factory,
+ unsigned *count,
+ pjmedia_codec_info codecs[]);
+static pj_status_t l16_alloc_codec( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec);
+static pj_status_t l16_dealloc_codec( pjmedia_codec_factory *factory,
+ pjmedia_codec *codec );
+
+/* Prototypes for L16 implementation. */
+static pj_status_t l16_init( pjmedia_codec *codec,
+ pj_pool_t *pool );
+static pj_status_t l16_open( pjmedia_codec *codec,
+ pjmedia_codec_param *attr );
+static pj_status_t l16_close( pjmedia_codec *codec );
+static pj_status_t l16_modify(pjmedia_codec *codec,
+ const pjmedia_codec_param *attr );
+static pj_status_t l16_parse(pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[]);
+static pj_status_t l16_encode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+static pj_status_t l16_decode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+#if !PLC_DISABLED
+static pj_status_t l16_recover(pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+#endif
+
+/* Definition for L16 codec operations. */
+static pjmedia_codec_op l16_op =
+{
+ &l16_init,
+ &l16_open,
+ &l16_close,
+ &l16_modify,
+ &l16_parse,
+ &l16_encode,
+ &l16_decode,
+#if !PLC_DISABLED
+ &l16_recover
+#else
+ NULL
+#endif
+};
+
+/* Definition for L16 codec factory operations. */
+static pjmedia_codec_factory_op l16_factory_op =
+{
+ &l16_test_alloc,
+ &l16_default_attr,
+ &l16_enum_codecs,
+ &l16_alloc_codec,
+ &l16_dealloc_codec,
+ &pjmedia_codec_l16_deinit
+};
+
+/* L16 factory private data */
+static struct l16_factory
+{
+ pjmedia_codec_factory base;
+ pjmedia_endpt *endpt;
+ pj_pool_t *pool;
+ pj_mutex_t *mutex;
+} l16_factory;
+
+
+/* L16 codec private data. */
+struct l16_data
+{
+ pj_pool_t *pool;
+ unsigned frame_size; /* Frame size, in bytes */
+ unsigned clock_rate; /* Clock rate */
+
+#if !PLC_DISABLED
+ pj_bool_t plc_enabled;
+ pjmedia_plc *plc;
+#endif
+ pj_bool_t vad_enabled;
+ pjmedia_silence_det *vad;
+ pj_timestamp last_tx;
+};
+
+
+
+PJ_DEF(pj_status_t) pjmedia_codec_l16_init(pjmedia_endpt *endpt,
+ unsigned options)
+{
+ pjmedia_codec_mgr *codec_mgr;
+ pj_status_t status;
+
+
+ PJ_UNUSED_ARG(options);
+
+
+ if (l16_factory.endpt != NULL) {
+ /* Already initialized. */
+ return PJ_SUCCESS;
+ }
+
+ /* Init factory */
+ l16_factory.base.op = &l16_factory_op;
+ l16_factory.base.factory_data = NULL;
+ l16_factory.endpt = endpt;
+
+ /* Create pool */
+ l16_factory.pool = pjmedia_endpt_create_pool(endpt, "l16", 4000, 4000);
+ if (!l16_factory.pool)
+ return PJ_ENOMEM;
+
+ /* Create mutex. */
+ status = pj_mutex_create_simple(l16_factory.pool, "l16",
+ &l16_factory.mutex);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
+ if (!codec_mgr) {
+ return PJ_EINVALIDOP;
+ }
+
+ /* Register codec factory to endpoint. */
+ status = pjmedia_codec_mgr_register_factory(codec_mgr,
+ &l16_factory.base);
+ if (status != PJ_SUCCESS)
+ return status;
+
+
+ return PJ_SUCCESS;
+
+on_error:
+ if (l16_factory.mutex) {
+ pj_mutex_destroy(l16_factory.mutex);
+ l16_factory.mutex = NULL;
+ }
+ if (l16_factory.pool) {
+ pj_pool_release(l16_factory.pool);
+ l16_factory.pool = NULL;
+ }
+ return status;
+}
+
+PJ_DEF(pj_status_t) pjmedia_codec_l16_deinit(void)
+{
+ pjmedia_codec_mgr *codec_mgr;
+ pj_status_t status;
+
+ if (l16_factory.endpt == NULL) {
+ /* Not registered. */
+ return PJ_SUCCESS;
+ }
+
+ /* Lock mutex. */
+ pj_mutex_lock(l16_factory.mutex);
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(l16_factory.endpt);
+ if (!codec_mgr) {
+ l16_factory.endpt = NULL;
+ pj_mutex_unlock(l16_factory.mutex);
+ return PJ_EINVALIDOP;
+ }
+
+ /* Unregister L16 codec factory. */
+ status = pjmedia_codec_mgr_unregister_factory(codec_mgr,
+ &l16_factory.base);
+ l16_factory.endpt = NULL;
+
+ /* Destroy mutex. */
+ pj_mutex_destroy(l16_factory.mutex);
+ l16_factory.mutex = NULL;
+
+
+ /* Release pool. */
+ pj_pool_release(l16_factory.pool);
+ l16_factory.pool = NULL;
+
+
+ return status;
+}
+
+static pj_status_t l16_test_alloc(pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id )
+{
+ PJ_UNUSED_ARG(factory);
+
+ if (pj_stricmp(&id->encoding_name, &STR_L16)==0) {
+ /* Match! */
+ return PJ_SUCCESS;
+ }
+
+ return -1;
+}
+
+static pj_status_t l16_default_attr( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr )
+{
+ PJ_UNUSED_ARG(factory);
+
+ pj_bzero(attr, sizeof(pjmedia_codec_param));
+ attr->info.pt = (pj_uint8_t)id->pt;
+ attr->info.clock_rate = id->clock_rate;
+ attr->info.channel_cnt = id->channel_cnt;
+ attr->info.avg_bps = id->clock_rate * id->channel_cnt * 16;
+ attr->info.max_bps = attr->info.avg_bps;
+ attr->info.pcm_bits_per_sample = 16;
+
+ /* To keep frame size below 1400 MTU, set ptime to 10ms for
+ * sampling rate > 35 KHz
+ */
+ attr->info.frm_ptime = GET_PTIME(id->clock_rate);
+
+ attr->setting.frm_per_pkt = 1;
+
+ attr->setting.vad = 1;
+#if !PLC_DISABLED
+ attr->setting.plc = 1;
+#endif
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t l16_enum_codecs( pjmedia_codec_factory *factory,
+ unsigned *max_count,
+ pjmedia_codec_info codecs[])
+{
+ unsigned count = 0;
+
+ PJ_UNUSED_ARG(factory);
+
+ if (count < *max_count) {
+ /* Register 44100Hz 1 channel L16 codec */
+ codecs[count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[count].pt = PJMEDIA_RTP_PT_L16_1;
+ codecs[count].encoding_name = STR_L16;
+ codecs[count].clock_rate = 44100;
+ codecs[count].channel_cnt = 1;
+ ++count;
+ }
+
+ if (count < *max_count) {
+ /* Register 44100Hz 2 channels L16 codec */
+ codecs[count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[count].pt = PJMEDIA_RTP_PT_L16_2;
+ codecs[count].encoding_name = STR_L16;
+ codecs[count].clock_rate = 44100;
+ codecs[count].channel_cnt = 2;
+ ++count;
+ }
+
+ if (count < *max_count) {
+ /* 8KHz mono */
+ codecs[count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[count].pt = PJMEDIA_RTP_PT_L16_8KHZ_MONO;
+ codecs[count].encoding_name = STR_L16;
+ codecs[count].clock_rate = 8000;
+ codecs[count].channel_cnt = 1;
+ ++count;
+ }
+
+ if (count < *max_count) {
+ /* 8KHz stereo */
+ codecs[count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[count].pt = PJMEDIA_RTP_PT_L16_8KHZ_STEREO;
+ codecs[count].encoding_name = STR_L16;
+ codecs[count].clock_rate = 8000;
+ codecs[count].channel_cnt = 2;
+ ++count;
+ }
+
+// disable some L16 modes
+#if 0
+ if (count < *max_count) {
+ /* 11025 Hz mono */
+ codecs[count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[count].pt = PJMEDIA_RTP_PT_L16_11KHZ_MONO;
+ codecs[count].encoding_name = STR_L16;
+ codecs[count].clock_rate = 11025;
+ codecs[count].channel_cnt = 1;
+ ++count;
+ }
+
+ if (count < *max_count) {
+ /* 11025 Hz stereo */
+ codecs[count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[count].pt = PJMEDIA_RTP_PT_L16_11KHZ_STEREO;
+ codecs[count].encoding_name = STR_L16;
+ codecs[count].clock_rate = 11025;
+ codecs[count].channel_cnt = 2;
+ ++count;
+ }
+#endif
+
+ if (count < *max_count) {
+ /* 16000 Hz mono */
+ codecs[count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[count].pt = PJMEDIA_RTP_PT_L16_16KHZ_MONO;
+ codecs[count].encoding_name = STR_L16;
+ codecs[count].clock_rate = 16000;
+ codecs[count].channel_cnt = 1;
+ ++count;
+ }
+
+
+ if (count < *max_count) {
+ /* 16000 Hz stereo */
+ codecs[count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[count].pt = PJMEDIA_RTP_PT_L16_16KHZ_STEREO;
+ codecs[count].encoding_name = STR_L16;
+ codecs[count].clock_rate = 16000;
+ codecs[count].channel_cnt = 2;
+ ++count;
+ }
+
+// disable some L16 modes
+#if 0
+ if (count < *max_count) {
+ /* 22050 Hz mono */
+ codecs[count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[count].pt = PJMEDIA_RTP_PT_L16_22KHZ_MONO;
+ codecs[count].encoding_name = STR_L16;
+ codecs[count].clock_rate = 22050;
+ codecs[count].channel_cnt = 1;
+ ++count;
+ }
+
+
+ if (count < *max_count) {
+ /* 22050 Hz stereo */
+ codecs[count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[count].pt = PJMEDIA_RTP_PT_L16_22KHZ_STEREO;
+ codecs[count].encoding_name = STR_L16;
+ codecs[count].clock_rate = 22050;
+ codecs[count].channel_cnt = 2;
+ ++count;
+ }
+
+ if (count < *max_count) {
+ /* 32000 Hz mono */
+ codecs[count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[count].pt = PJMEDIA_RTP_PT_L16_32KHZ_MONO;
+ codecs[count].encoding_name = STR_L16;
+ codecs[count].clock_rate = 32000;
+ codecs[count].channel_cnt = 1;
+ ++count;
+ }
+
+ if (count < *max_count) {
+ /* 32000 Hz stereo */
+ codecs[count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[count].pt = PJMEDIA_RTP_PT_L16_32KHZ_STEREO;
+ codecs[count].encoding_name = STR_L16;
+ codecs[count].clock_rate = 32000;
+ codecs[count].channel_cnt = 2;
+ ++count;
+ }
+
+ if (count < *max_count) {
+ /* 48KHz mono */
+ codecs[count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[count].pt = PJMEDIA_RTP_PT_L16_48KHZ_MONO;
+ codecs[count].encoding_name = STR_L16;
+ codecs[count].clock_rate = 48000;
+ codecs[count].channel_cnt = 1;
+ ++count;
+ }
+
+ if (count < *max_count) {
+ /* 48KHz stereo */
+ codecs[count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[count].pt = PJMEDIA_RTP_PT_L16_48KHZ_STEREO;
+ codecs[count].encoding_name = STR_L16;
+ codecs[count].clock_rate = 48000;
+ codecs[count].channel_cnt = 2;
+ ++count;
+ }
+#endif
+
+
+ *max_count = count;
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t l16_alloc_codec( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec)
+{
+ pjmedia_codec *codec = NULL;
+ struct l16_data *data;
+ unsigned ptime;
+ pj_pool_t *pool;
+
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(factory==&l16_factory.base, PJ_EINVAL);
+
+ /* Lock mutex. */
+ pj_mutex_lock(l16_factory.mutex);
+
+
+ pool = pjmedia_endpt_create_pool(l16_factory.endpt, "l16", 4000, 4000);
+ codec = PJ_POOL_ZALLOC_T(pool, pjmedia_codec);
+ codec->codec_data = pj_pool_alloc(pool, sizeof(struct l16_data));
+ codec->factory = factory;
+ codec->op = &l16_op;
+
+ /* Init private data */
+ ptime = GET_PTIME(id->clock_rate);
+ data = (struct l16_data*) codec->codec_data;
+ data->frame_size = ptime * id->clock_rate * id->channel_cnt * 2 / 1000;
+ data->clock_rate = id->clock_rate;
+ data->pool = pool;
+
+#if !PLC_DISABLED
+ /* Create PLC */
+ status = pjmedia_plc_create(pool, id->clock_rate,
+ data->frame_size >> 1, 0,
+ &data->plc);
+ if (status != PJ_SUCCESS) {
+ pj_mutex_unlock(l16_factory.mutex);
+ return status;
+ }
+#endif
+
+ /* Create silence detector */
+ status = pjmedia_silence_det_create(pool, id->clock_rate,
+ data->frame_size >> 1,
+ &data->vad);
+ if (status != PJ_SUCCESS) {
+ pj_mutex_unlock(l16_factory.mutex);
+ return status;
+ }
+
+ *p_codec = codec;
+
+ /* Unlock mutex. */
+ pj_mutex_unlock(l16_factory.mutex);
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t l16_dealloc_codec(pjmedia_codec_factory *factory,
+ pjmedia_codec *codec )
+{
+ struct l16_data *data;
+
+ PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory==&l16_factory.base, PJ_EINVAL);
+
+ /* Lock mutex. */
+ pj_mutex_lock(l16_factory.mutex);
+
+ /* Just release codec data pool */
+ data = (struct l16_data*) codec->codec_data;
+ pj_assert(data);
+ pj_pool_release(data->pool);
+
+ /* Unlock mutex. */
+ pj_mutex_unlock(l16_factory.mutex);
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t l16_init( pjmedia_codec *codec, pj_pool_t *pool )
+{
+ /* There's nothing to do here really */
+ PJ_UNUSED_ARG(codec);
+ PJ_UNUSED_ARG(pool);
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t l16_open(pjmedia_codec *codec,
+ pjmedia_codec_param *attr )
+{
+ struct l16_data *data = NULL;
+
+ PJ_ASSERT_RETURN(codec && codec->codec_data && attr, PJ_EINVAL);
+
+ data = (struct l16_data*) codec->codec_data;
+
+ data->vad_enabled = (attr->setting.vad != 0);
+#if !PLC_DISABLED
+ data->plc_enabled = (attr->setting.plc != 0);
+#endif
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t l16_close( pjmedia_codec *codec )
+{
+ PJ_UNUSED_ARG(codec);
+ /* Nothing to do */
+ return PJ_SUCCESS;
+}
+
+static pj_status_t l16_modify(pjmedia_codec *codec,
+ const pjmedia_codec_param *attr )
+{
+ struct l16_data *data = (struct l16_data*) codec->codec_data;
+
+ pj_assert(data != NULL);
+
+ data->vad_enabled = (attr->setting.vad != 0);
+#if !PLC_DISABLED
+ data->plc_enabled = (attr->setting.plc != 0);
+#endif
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t l16_parse( pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[])
+{
+ unsigned count = 0;
+ struct l16_data *data = (struct l16_data*) codec->codec_data;
+
+ PJ_UNUSED_ARG(codec);
+ PJ_ASSERT_RETURN(frame_cnt, PJ_EINVAL);
+
+ while (pkt_size >= data->frame_size && count < *frame_cnt) {
+ frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frames[count].buf = pkt;
+ frames[count].size = data->frame_size;
+ frames[count].timestamp.u64 = ts->u64 + (count * data->frame_size);
+
+ pkt = ((char*)pkt) + data->frame_size;
+ pkt_size -= data->frame_size;
+
+ ++count;
+ }
+
+ *frame_cnt = count;
+ return PJ_SUCCESS;
+}
+
+static pj_status_t l16_encode(pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ struct l16_data *data = (struct l16_data*) codec->codec_data;
+ const pj_int16_t *samp = (const pj_int16_t*) input->buf;
+ const pj_int16_t *samp_end = samp + input->size/sizeof(pj_int16_t);
+ pj_int16_t *samp_out = (pj_int16_t*) output->buf;
+
+ pj_assert(data && input && output);
+
+ /* Check output buffer length */
+ if (output_buf_len < input->size)
+ return PJMEDIA_CODEC_EFRMTOOSHORT;
+
+ /* Detect silence */
+ if (data->vad_enabled) {
+ pj_bool_t is_silence;
+ pj_int32_t silence_duration;
+
+ silence_duration = pj_timestamp_diff32(&data->last_tx,
+ &input->timestamp);
+
+ is_silence = pjmedia_silence_det_detect(data->vad,
+ (const pj_int16_t*) input->buf,
+ (input->size >> 1),
+ NULL);
+ if (is_silence &&
+ (PJMEDIA_CODEC_MAX_SILENCE_PERIOD == -1 ||
+ silence_duration < PJMEDIA_CODEC_MAX_SILENCE_PERIOD*
+ (int)data->clock_rate/1000))
+ {
+ output->type = PJMEDIA_FRAME_TYPE_NONE;
+ output->buf = NULL;
+ output->size = 0;
+ output->timestamp = input->timestamp;
+ return PJ_SUCCESS;
+ } else {
+ data->last_tx = input->timestamp;
+ }
+ }
+
+ /* Encode */
+#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN!=0
+ while (samp!=samp_end)
+ *samp_out++ = pj_htons(*samp++);
+#else
+ pjmedia_copy_samples(samp_out, samp, input->size >> 1);
+#endif
+
+
+ /* Done */
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->size = input->size;
+ output->timestamp = input->timestamp;
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t l16_decode(pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ struct l16_data *l16_data = (struct l16_data*) codec->codec_data;
+ const pj_int16_t *samp = (const pj_int16_t*) input->buf;
+ const pj_int16_t *samp_end = samp + input->size/sizeof(pj_int16_t);
+ pj_int16_t *samp_out = (pj_int16_t*) output->buf;
+
+ pj_assert(l16_data != NULL);
+ PJ_ASSERT_RETURN(input && output, PJ_EINVAL);
+
+
+ /* Check output buffer length */
+ if (output_buf_len < input->size)
+ return PJMEDIA_CODEC_EPCMTOOSHORT;
+
+
+ /* Decode */
+#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN!=0
+ while (samp!=samp_end)
+ *samp_out++ = pj_htons(*samp++);
+#else
+ pjmedia_copy_samples(samp_out, samp, input->size >> 1);
+#endif
+
+
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->size = input->size;
+ output->timestamp = input->timestamp;
+
+#if !PLC_DISABLED
+ if (l16_data->plc_enabled)
+ pjmedia_plc_save( l16_data->plc, (pj_int16_t*)output->buf);
+#endif
+
+ return PJ_SUCCESS;
+}
+
+#if !PLC_DISABLED
+/*
+ * Recover lost frame.
+ */
+static pj_status_t l16_recover(pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ struct l16_data *data = (struct l16_data*) codec->codec_data;
+
+ PJ_ASSERT_RETURN(data->plc_enabled, PJ_EINVALIDOP);
+
+ PJ_ASSERT_RETURN(output_buf_len >= data->frame_size,
+ PJMEDIA_CODEC_EPCMTOOSHORT);
+
+ pjmedia_plc_generate(data->plc, (pj_int16_t*)output->buf);
+ output->size = data->frame_size;
+
+ return PJ_SUCCESS;
+}
+#endif
+
+#endif /* PJMEDIA_HAS_L16_CODEC */
+
+
diff --git a/pjmedia/src/pjmedia-codec/opencore_amrnb.c b/pjmedia/src/pjmedia-codec/opencore_amrnb.c
new file mode 100644
index 0000000..9b6daa9
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/opencore_amrnb.c
@@ -0,0 +1,831 @@
+/* $Id: opencore_amrnb.c 3939 2012-01-10 05:38:40Z nanang $ */
+/*
+ * Copyright (C) 2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2011 Dan Arrhenius <dan@keystream.se>
+ *
+ * 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
+ */
+
+/*
+ * AMR-NB codec implementation with OpenCORE AMRNB library
+ */
+#include <pjmedia-codec/g722.h>
+#include <pjmedia-codec/amr_sdp_match.h>
+#include <pjmedia/codec.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/endpoint.h>
+#include <pjmedia/plc.h>
+#include <pjmedia/port.h>
+#include <pjmedia/silencedet.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/os.h>
+#include <pj/math.h>
+
+#if defined(PJMEDIA_HAS_OPENCORE_AMRNB_CODEC) && \
+ (PJMEDIA_HAS_OPENCORE_AMRNB_CODEC != 0)
+
+#include <opencore-amrnb/interf_enc.h>
+#include <opencore-amrnb/interf_dec.h>
+#include <pjmedia-codec/amr_helper.h>
+#include <pjmedia-codec/opencore_amrnb.h>
+
+#define THIS_FILE "opencore_amrnb.c"
+
+/* Tracing */
+#define PJ_TRACE 0
+
+#if PJ_TRACE
+# define TRACE_(expr) PJ_LOG(4,expr)
+#else
+# define TRACE_(expr)
+#endif
+
+/* Use PJMEDIA PLC */
+#define USE_PJMEDIA_PLC 1
+
+
+
+/* Prototypes for AMR-NB factory */
+static pj_status_t amr_test_alloc(pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id );
+static pj_status_t amr_default_attr(pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr );
+static pj_status_t amr_enum_codecs(pjmedia_codec_factory *factory,
+ unsigned *count,
+ pjmedia_codec_info codecs[]);
+static pj_status_t amr_alloc_codec(pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec);
+static pj_status_t amr_dealloc_codec(pjmedia_codec_factory *factory,
+ pjmedia_codec *codec );
+
+/* Prototypes for AMR-NB implementation. */
+static pj_status_t amr_codec_init(pjmedia_codec *codec,
+ pj_pool_t *pool );
+static pj_status_t amr_codec_open(pjmedia_codec *codec,
+ pjmedia_codec_param *attr );
+static pj_status_t amr_codec_close(pjmedia_codec *codec );
+static pj_status_t amr_codec_modify(pjmedia_codec *codec,
+ const pjmedia_codec_param *attr );
+static pj_status_t amr_codec_parse(pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[]);
+static pj_status_t amr_codec_encode(pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+static pj_status_t amr_codec_decode(pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+static pj_status_t amr_codec_recover(pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+
+
+
+/* Definition for AMR-NB codec operations. */
+static pjmedia_codec_op amr_op =
+{
+ &amr_codec_init,
+ &amr_codec_open,
+ &amr_codec_close,
+ &amr_codec_modify,
+ &amr_codec_parse,
+ &amr_codec_encode,
+ &amr_codec_decode,
+ &amr_codec_recover
+};
+
+/* Definition for AMR-NB codec factory operations. */
+static pjmedia_codec_factory_op amr_factory_op =
+{
+ &amr_test_alloc,
+ &amr_default_attr,
+ &amr_enum_codecs,
+ &amr_alloc_codec,
+ &amr_dealloc_codec,
+ &pjmedia_codec_opencore_amrnb_deinit
+};
+
+
+/* AMR-NB factory */
+static struct amr_codec_factory
+{
+ pjmedia_codec_factory base;
+ pjmedia_endpt *endpt;
+ pj_pool_t *pool;
+} amr_codec_factory;
+
+
+/* AMR-NB codec private data. */
+struct amr_data
+{
+ pj_pool_t *pool;
+ void *encoder;
+ void *decoder;
+ pj_bool_t plc_enabled;
+ pj_bool_t vad_enabled;
+ int enc_mode;
+ pjmedia_codec_amr_pack_setting enc_setting;
+ pjmedia_codec_amr_pack_setting dec_setting;
+#if USE_PJMEDIA_PLC
+ pjmedia_plc *plc;
+#endif
+ pj_timestamp last_tx;
+};
+
+static pjmedia_codec_amrnb_config def_config =
+{
+ PJ_FALSE, /* octet align */
+ 5900 /* bitrate */
+};
+
+
+
+/*
+ * Initialize and register AMR-NB codec factory to pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_opencore_amrnb_init( pjmedia_endpt *endpt )
+{
+ pjmedia_codec_mgr *codec_mgr;
+ pj_str_t codec_name;
+ pj_status_t status;
+
+ if (amr_codec_factory.pool != NULL)
+ return PJ_SUCCESS;
+
+ /* Create AMR-NB codec factory. */
+ amr_codec_factory.base.op = &amr_factory_op;
+ amr_codec_factory.base.factory_data = NULL;
+ amr_codec_factory.endpt = endpt;
+
+ amr_codec_factory.pool = pjmedia_endpt_create_pool(endpt, "amrnb", 1000,
+ 1000);
+ if (!amr_codec_factory.pool)
+ return PJ_ENOMEM;
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
+ if (!codec_mgr) {
+ status = PJ_EINVALIDOP;
+ goto on_error;
+ }
+
+ /* Register format match callback. */
+ pj_cstr(&codec_name, "AMR");
+ status = pjmedia_sdp_neg_register_fmt_match_cb(
+ &codec_name,
+ &pjmedia_codec_amr_match_sdp);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Register codec factory to endpoint. */
+ status = pjmedia_codec_mgr_register_factory(codec_mgr,
+ &amr_codec_factory.base);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Done. */
+ return PJ_SUCCESS;
+
+on_error:
+ pj_pool_release(amr_codec_factory.pool);
+ amr_codec_factory.pool = NULL;
+ return status;
+}
+
+
+/*
+ * Unregister AMR-NB codec factory from pjmedia endpoint and deinitialize
+ * the AMR-NB codec library.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_opencore_amrnb_deinit(void)
+{
+ pjmedia_codec_mgr *codec_mgr;
+ pj_status_t status;
+
+ if (amr_codec_factory.pool == NULL)
+ return PJ_SUCCESS;
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(amr_codec_factory.endpt);
+ if (!codec_mgr) {
+ pj_pool_release(amr_codec_factory.pool);
+ amr_codec_factory.pool = NULL;
+ return PJ_EINVALIDOP;
+ }
+
+ /* Unregister AMR-NB codec factory. */
+ status = pjmedia_codec_mgr_unregister_factory(codec_mgr,
+ &amr_codec_factory.base);
+
+ /* Destroy pool. */
+ pj_pool_release(amr_codec_factory.pool);
+ amr_codec_factory.pool = NULL;
+
+ return status;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_codec_opencore_amrnb_set_config(
+ const pjmedia_codec_amrnb_config *config)
+{
+ unsigned nbitrates;
+
+
+ def_config = *config;
+
+ /* Normalize bitrate. */
+ nbitrates = PJ_ARRAY_SIZE(pjmedia_codec_amrnb_bitrates);
+ if (def_config.bitrate < pjmedia_codec_amrnb_bitrates[0])
+ def_config.bitrate = pjmedia_codec_amrnb_bitrates[0];
+ else if (def_config.bitrate > pjmedia_codec_amrnb_bitrates[nbitrates-1])
+ def_config.bitrate = pjmedia_codec_amrnb_bitrates[nbitrates-1];
+ else
+ {
+ unsigned i;
+
+ for (i = 0; i < nbitrates; ++i) {
+ if (def_config.bitrate <= pjmedia_codec_amrnb_bitrates[i])
+ break;
+ }
+ def_config.bitrate = pjmedia_codec_amrnb_bitrates[i];
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Check if factory can allocate the specified codec.
+ */
+static pj_status_t amr_test_alloc( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *info )
+{
+ PJ_UNUSED_ARG(factory);
+
+ /* Check payload type. */
+ if (info->pt != PJMEDIA_RTP_PT_AMR)
+ return PJMEDIA_CODEC_EUNSUP;
+
+ /* Ignore the rest, since it's static payload type. */
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Generate default attribute.
+ */
+static pj_status_t amr_default_attr( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr )
+{
+ PJ_UNUSED_ARG(factory);
+ PJ_UNUSED_ARG(id);
+
+ pj_bzero(attr, sizeof(pjmedia_codec_param));
+ attr->info.clock_rate = 8000;
+ attr->info.channel_cnt = 1;
+ attr->info.avg_bps = def_config.bitrate;
+ attr->info.max_bps = pjmedia_codec_amrnb_bitrates[7];
+ attr->info.pcm_bits_per_sample = 16;
+ attr->info.frm_ptime = 20;
+ attr->info.pt = PJMEDIA_RTP_PT_AMR;
+
+ attr->setting.frm_per_pkt = 2;
+ attr->setting.vad = 1;
+ attr->setting.plc = 1;
+
+ if (def_config.octet_align) {
+ attr->setting.dec_fmtp.cnt = 1;
+ attr->setting.dec_fmtp.param[0].name = pj_str("octet-align");
+ attr->setting.dec_fmtp.param[0].val = pj_str("1");
+ }
+
+ /* Default all other flag bits disabled. */
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Enum codecs supported by this factory (i.e. only AMR-NB!).
+ */
+static pj_status_t amr_enum_codecs( pjmedia_codec_factory *factory,
+ unsigned *count,
+ pjmedia_codec_info codecs[])
+{
+ PJ_UNUSED_ARG(factory);
+ PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL);
+
+ pj_bzero(&codecs[0], sizeof(pjmedia_codec_info));
+ codecs[0].encoding_name = pj_str("AMR");
+ codecs[0].pt = PJMEDIA_RTP_PT_AMR;
+ codecs[0].type = PJMEDIA_TYPE_AUDIO;
+ codecs[0].clock_rate = 8000;
+ codecs[0].channel_cnt = 1;
+
+ *count = 1;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Allocate a new AMR-NB codec instance.
+ */
+static pj_status_t amr_alloc_codec( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec)
+{
+ pj_pool_t *pool;
+ pjmedia_codec *codec;
+ struct amr_data *amr_data;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory == &amr_codec_factory.base, PJ_EINVAL);
+
+ pool = pjmedia_endpt_create_pool(amr_codec_factory.endpt, "amrnb-inst",
+ 512, 512);
+
+ codec = PJ_POOL_ZALLOC_T(pool, pjmedia_codec);
+ PJ_ASSERT_RETURN(codec != NULL, PJ_ENOMEM);
+ codec->op = &amr_op;
+ codec->factory = factory;
+
+ amr_data = PJ_POOL_ZALLOC_T(pool, struct amr_data);
+ codec->codec_data = amr_data;
+ amr_data->pool = pool;
+
+#if USE_PJMEDIA_PLC
+ /* Create PLC */
+ status = pjmedia_plc_create(pool, 8000, 160, 0, &amr_data->plc);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+#else
+ PJ_UNUSED_ARG(status);
+#endif
+ *p_codec = codec;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Free codec.
+ */
+static pj_status_t amr_dealloc_codec( pjmedia_codec_factory *factory,
+ pjmedia_codec *codec )
+{
+ struct amr_data *amr_data;
+
+ PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory == &amr_codec_factory.base, PJ_EINVAL);
+
+ amr_data = (struct amr_data*) codec->codec_data;
+
+ /* Close codec, if it's not closed. */
+ amr_codec_close(codec);
+
+ pj_pool_release(amr_data->pool);
+ amr_data = NULL;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Init codec.
+ */
+static pj_status_t amr_codec_init( pjmedia_codec *codec,
+ pj_pool_t *pool )
+{
+ PJ_UNUSED_ARG(codec);
+ PJ_UNUSED_ARG(pool);
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Open codec.
+ */
+static pj_status_t amr_codec_open( pjmedia_codec *codec,
+ pjmedia_codec_param *attr )
+{
+ struct amr_data *amr_data = (struct amr_data*) codec->codec_data;
+ pjmedia_codec_amr_pack_setting *setting;
+ unsigned i;
+ pj_uint8_t octet_align = 0;
+ pj_int8_t enc_mode;
+ const pj_str_t STR_FMTP_OCTET_ALIGN = {"octet-align", 11};
+
+ PJ_ASSERT_RETURN(codec && attr, PJ_EINVAL);
+ PJ_ASSERT_RETURN(amr_data != NULL, PJ_EINVALIDOP);
+
+ enc_mode = pjmedia_codec_amr_get_mode(attr->info.avg_bps);
+ pj_assert(enc_mode >= 0 && enc_mode <= 7);
+
+ /* Check octet-align */
+ for (i = 0; i < attr->setting.dec_fmtp.cnt; ++i) {
+ if (pj_stricmp(&attr->setting.dec_fmtp.param[i].name,
+ &STR_FMTP_OCTET_ALIGN) == 0)
+ {
+ octet_align = (pj_uint8_t)
+ (pj_strtoul(&attr->setting.dec_fmtp.param[i].val));
+ break;
+ }
+ }
+
+ /* Check mode-set */
+ for (i = 0; i < attr->setting.enc_fmtp.cnt; ++i) {
+ const pj_str_t STR_FMTP_MODE_SET = {"mode-set", 8};
+
+ if (pj_stricmp(&attr->setting.enc_fmtp.param[i].name,
+ &STR_FMTP_MODE_SET) == 0)
+ {
+ const char *p;
+ pj_size_t l;
+ pj_int8_t diff = 99;
+
+ /* Encoding mode is chosen based on local default mode setting:
+ * - if local default mode is included in the mode-set, use it
+ * - otherwise, find the closest mode to local default mode;
+ * if there are two closest modes, prefer to use the higher
+ * one, e.g: local default mode is 4, the mode-set param
+ * contains '2,3,5,6', then 5 will be chosen.
+ */
+ p = pj_strbuf(&attr->setting.enc_fmtp.param[i].val);
+ l = pj_strlen(&attr->setting.enc_fmtp.param[i].val);
+ while (l--) {
+ if (*p>='0' && *p<='7') {
+ pj_int8_t tmp = *p - '0' - enc_mode;
+
+ if (PJ_ABS(diff) > PJ_ABS(tmp) ||
+ (PJ_ABS(diff) == PJ_ABS(tmp) && tmp > diff))
+ {
+ diff = tmp;
+ if (diff == 0) break;
+ }
+ }
+ ++p;
+ }
+ PJ_ASSERT_RETURN(diff != 99, PJMEDIA_CODEC_EFAILED);
+
+ enc_mode = enc_mode + diff;
+
+ break;
+ }
+ }
+
+ amr_data->vad_enabled = (attr->setting.vad != 0);
+ amr_data->plc_enabled = (attr->setting.plc != 0);
+ amr_data->enc_mode = enc_mode;
+
+ amr_data->encoder = Encoder_Interface_init(amr_data->vad_enabled);
+ if (amr_data->encoder == NULL) {
+ TRACE_((THIS_FILE, "Encoder_Interface_init() failed"));
+ amr_codec_close(codec);
+ return PJMEDIA_CODEC_EFAILED;
+ }
+ setting = &amr_data->enc_setting;
+ pj_bzero(setting, sizeof(pjmedia_codec_amr_pack_setting));
+ setting->amr_nb = 1;
+ setting->reorder = 0;
+ setting->octet_aligned = octet_align;
+ setting->cmr = 15;
+
+ amr_data->decoder = Decoder_Interface_init();
+ if (amr_data->decoder == NULL) {
+ TRACE_((THIS_FILE, "Decoder_Interface_init() failed"));
+ amr_codec_close(codec);
+ return PJMEDIA_CODEC_EFAILED;
+ }
+ setting = &amr_data->dec_setting;
+ pj_bzero(setting, sizeof(pjmedia_codec_amr_pack_setting));
+ setting->amr_nb = 1;
+ setting->reorder = 0;
+ setting->octet_aligned = octet_align;
+
+ TRACE_((THIS_FILE, "AMR-NB codec allocated: vad=%d, plc=%d, bitrate=%d",
+ amr_data->vad_enabled, amr_data->plc_enabled,
+ pjmedia_codec_amrnb_bitrates[amr_data->enc_mode]));
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Close codec.
+ */
+static pj_status_t amr_codec_close( pjmedia_codec *codec )
+{
+ struct amr_data *amr_data;
+
+ PJ_ASSERT_RETURN(codec, PJ_EINVAL);
+
+ amr_data = (struct amr_data*) codec->codec_data;
+ PJ_ASSERT_RETURN(amr_data != NULL, PJ_EINVALIDOP);
+
+ if (amr_data->encoder) {
+ Encoder_Interface_exit(amr_data->encoder);
+ amr_data->encoder = NULL;
+ }
+
+ if (amr_data->decoder) {
+ Decoder_Interface_exit(amr_data->decoder);
+ amr_data->decoder = NULL;
+ }
+
+ TRACE_((THIS_FILE, "AMR-NB codec closed"));
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Modify codec settings.
+ */
+static pj_status_t amr_codec_modify( pjmedia_codec *codec,
+ const pjmedia_codec_param *attr )
+{
+ struct amr_data *amr_data = (struct amr_data*) codec->codec_data;
+ pj_bool_t prev_vad_state;
+
+ pj_assert(amr_data != NULL);
+ pj_assert(amr_data->encoder != NULL && amr_data->decoder != NULL);
+
+ prev_vad_state = amr_data->vad_enabled;
+ amr_data->vad_enabled = (attr->setting.vad != 0);
+ amr_data->plc_enabled = (attr->setting.plc != 0);
+
+ if (prev_vad_state != amr_data->vad_enabled) {
+ /* Reinit AMR encoder to update VAD setting */
+ TRACE_((THIS_FILE, "Reiniting AMR encoder to update VAD setting."));
+ Encoder_Interface_exit(amr_data->encoder);
+ amr_data->encoder = Encoder_Interface_init(amr_data->vad_enabled);
+ if (amr_data->encoder == NULL) {
+ TRACE_((THIS_FILE, "Encoder_Interface_init() failed"));
+ amr_codec_close(codec);
+ return PJMEDIA_CODEC_EFAILED;
+ }
+ }
+
+ TRACE_((THIS_FILE, "AMR-NB codec modified: vad=%d, plc=%d",
+ amr_data->vad_enabled, amr_data->plc_enabled));
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get frames in the packet.
+ */
+static pj_status_t amr_codec_parse( pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[])
+{
+ struct amr_data *amr_data = (struct amr_data*) codec->codec_data;
+ pj_uint8_t cmr;
+ pj_status_t status;
+
+ status = pjmedia_codec_amr_parse(pkt, pkt_size, ts, &amr_data->dec_setting,
+ frames, frame_cnt, &cmr);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Check for Change Mode Request. */
+ if (cmr <= 7 && amr_data->enc_mode != cmr) {
+ amr_data->enc_mode = cmr;
+ TRACE_((THIS_FILE, "AMR-NB encoder switched mode to %d (%dbps)",
+ amr_data->enc_mode,
+ pjmedia_codec_amrnb_bitrates[amr_data->enc_mode]));
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Encode frame.
+ */
+static pj_status_t amr_codec_encode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ struct amr_data *amr_data = (struct amr_data*) codec->codec_data;
+ unsigned char *bitstream;
+ pj_int16_t *speech;
+ unsigned nsamples, samples_per_frame;
+ enum {MAX_FRAMES_PER_PACKET = 16};
+ pjmedia_frame frames[MAX_FRAMES_PER_PACKET];
+ pj_uint8_t *p;
+ unsigned i, out_size = 0, nframes = 0;
+ pj_size_t payload_len;
+ unsigned dtx_cnt, sid_cnt;
+ pj_status_t status;
+ int size;
+
+ pj_assert(amr_data != NULL);
+ PJ_ASSERT_RETURN(input && output, PJ_EINVAL);
+
+ nsamples = input->size >> 1;
+ samples_per_frame = 160;
+ PJ_ASSERT_RETURN(nsamples % samples_per_frame == 0,
+ PJMEDIA_CODEC_EPCMFRMINLEN);
+
+ nframes = nsamples / samples_per_frame;
+ PJ_ASSERT_RETURN(nframes <= MAX_FRAMES_PER_PACKET,
+ PJMEDIA_CODEC_EFRMTOOSHORT);
+
+ /* Encode the frames */
+ speech = (pj_int16_t*)input->buf;
+ bitstream = (unsigned char*)output->buf;
+ while (nsamples >= samples_per_frame) {
+ size = Encoder_Interface_Encode (amr_data->encoder, amr_data->enc_mode,
+ speech, bitstream, 0);
+ if (size == 0) {
+ output->size = 0;
+ output->buf = NULL;
+ output->type = PJMEDIA_FRAME_TYPE_NONE;
+ TRACE_((THIS_FILE, "AMR-NB encode() failed"));
+ return PJMEDIA_CODEC_EFAILED;
+ }
+ nsamples -= 160;
+ speech += samples_per_frame;
+ bitstream += size;
+ out_size += size;
+ TRACE_((THIS_FILE, "AMR-NB encode(): mode=%d, size=%d",
+ amr_data->enc_mode, out_size));
+ }
+
+ /* Pack payload */
+ p = (pj_uint8_t*)output->buf + output_buf_len - out_size;
+ pj_memmove(p, output->buf, out_size);
+ dtx_cnt = sid_cnt = 0;
+ for (i = 0; i < nframes; ++i) {
+ pjmedia_codec_amr_bit_info *info = (pjmedia_codec_amr_bit_info*)
+ &frames[i].bit_info;
+ info->frame_type = (pj_uint8_t)((*p >> 3) & 0x0F);
+ info->good_quality = (pj_uint8_t)((*p >> 2) & 0x01);
+ info->mode = (pj_int8_t)amr_data->enc_mode;
+ info->start_bit = 0;
+ frames[i].buf = p + 1;
+ frames[i].size = (info->frame_type <= 8)?
+ pjmedia_codec_amrnb_framelen[info->frame_type] : 0;
+ p += frames[i].size + 1;
+
+ /* Count the number of SID and DTX frames */
+ if (info->frame_type == 15) /* DTX*/
+ ++dtx_cnt;
+ else if (info->frame_type == 8) /* SID */
+ ++sid_cnt;
+ }
+
+ /* VA generates DTX frames as DTX+SID frames switching quickly and it
+ * seems that the SID frames occur too often (assuming the purpose is
+ * only for keeping NAT alive?). So let's modify the behavior a bit.
+ * Only an SID frame will be sent every PJMEDIA_CODEC_MAX_SILENCE_PERIOD
+ * milliseconds.
+ */
+ if (sid_cnt + dtx_cnt == nframes) {
+ pj_int32_t dtx_duration;
+
+ dtx_duration = pj_timestamp_diff32(&amr_data->last_tx,
+ &input->timestamp);
+ if (PJMEDIA_CODEC_MAX_SILENCE_PERIOD == -1 ||
+ dtx_duration < PJMEDIA_CODEC_MAX_SILENCE_PERIOD*8000/1000)
+ {
+ output->size = 0;
+ output->type = PJMEDIA_FRAME_TYPE_NONE;
+ output->timestamp = input->timestamp;
+ return PJ_SUCCESS;
+ }
+ }
+
+ payload_len = output_buf_len;
+
+ status = pjmedia_codec_amr_pack(frames, nframes, &amr_data->enc_setting,
+ output->buf, &payload_len);
+ if (status != PJ_SUCCESS) {
+ output->size = 0;
+ output->buf = NULL;
+ output->type = PJMEDIA_FRAME_TYPE_NONE;
+ TRACE_((THIS_FILE, "Failed to pack AMR payload, status=%d", status));
+ return status;
+ }
+
+ output->size = payload_len;
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->timestamp = input->timestamp;
+
+ amr_data->last_tx = input->timestamp;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Decode frame.
+ */
+static pj_status_t amr_codec_decode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ struct amr_data *amr_data = (struct amr_data*) codec->codec_data;
+ pjmedia_frame input_;
+ pjmedia_codec_amr_bit_info *info;
+ /* VA AMR-NB decoding buffer: AMR-NB max frame size + 1 byte header. */
+ unsigned char bitstream[32];
+
+ pj_assert(amr_data != NULL);
+ PJ_ASSERT_RETURN(input && output, PJ_EINVAL);
+
+ if (output_buf_len < 320)
+ return PJMEDIA_CODEC_EPCMTOOSHORT;
+
+ input_.buf = &bitstream[1];
+ input_.size = 31; /* AMR-NB max frame size */
+ pjmedia_codec_amr_predecode(input, &amr_data->dec_setting, &input_);
+ info = (pjmedia_codec_amr_bit_info*)&input_.bit_info;
+
+ /* VA AMRNB decoder requires frame info in the first byte. */
+ bitstream[0] = (info->frame_type << 3) | (info->good_quality << 2);
+
+ TRACE_((THIS_FILE, "AMR-NB decode(): mode=%d, ft=%d, size=%d",
+ info->mode, info->frame_type, input_.size));
+
+ /* Decode */
+ Decoder_Interface_Decode(amr_data->decoder, bitstream,
+ (pj_int16_t*)output->buf, 0);
+
+ output->size = 320;
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->timestamp = input->timestamp;
+
+#if USE_PJMEDIA_PLC
+ if (amr_data->plc_enabled)
+ pjmedia_plc_save(amr_data->plc, (pj_int16_t*)output->buf);
+#endif
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Recover lost frame.
+ */
+#if USE_PJMEDIA_PLC
+/*
+ * Recover lost frame.
+ */
+static pj_status_t amr_codec_recover( pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ struct amr_data *amr_data = codec->codec_data;
+
+ TRACE_((THIS_FILE, "amr_codec_recover"));
+
+ PJ_ASSERT_RETURN(amr_data->plc_enabled, PJ_EINVALIDOP);
+
+ PJ_ASSERT_RETURN(output_buf_len >= 320, PJMEDIA_CODEC_EPCMTOOSHORT);
+
+ pjmedia_plc_generate(amr_data->plc, (pj_int16_t*)output->buf);
+
+ output->size = 320;
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+
+ return PJ_SUCCESS;
+}
+#endif
+
+#if defined(_MSC_VER) && PJMEDIA_AUTO_LINK_OPENCORE_AMR_LIBS
+# if PJMEDIA_OPENCORE_AMR_BUILT_WITH_GCC
+# pragma comment( lib, "libopencore-amrnb.a")
+# else
+# error Unsupported OpenCORE AMR library, fix here
+# endif
+#endif
+
+#endif
diff --git a/pjmedia/src/pjmedia-codec/passthrough.c b/pjmedia/src/pjmedia-codec/passthrough.c
new file mode 100644
index 0000000..d315993
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/passthrough.c
@@ -0,0 +1,1054 @@
+/* $Id: passthrough.c 4082 2012-04-24 13:09:14Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia-codec/passthrough.h>
+#include <pjmedia-codec/amr_sdp_match.h>
+#include <pjmedia/codec.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/endpoint.h>
+#include <pjmedia/port.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/os.h>
+
+/*
+ * Only build this file if PJMEDIA_HAS_PASSTHROUGH_CODECS != 0
+ */
+#if defined(PJMEDIA_HAS_PASSTHROUGH_CODECS) && PJMEDIA_HAS_PASSTHROUGH_CODECS!=0
+
+#define THIS_FILE "passthrough.c"
+
+
+/* Prototypes for passthrough codecs factory */
+static pj_status_t test_alloc( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id );
+static pj_status_t default_attr( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr );
+static pj_status_t enum_codecs( pjmedia_codec_factory *factory,
+ unsigned *count,
+ pjmedia_codec_info codecs[]);
+static pj_status_t alloc_codec( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec);
+static pj_status_t dealloc_codec( pjmedia_codec_factory *factory,
+ pjmedia_codec *codec );
+
+/* Prototypes for passthrough codecs implementation. */
+static pj_status_t codec_init( pjmedia_codec *codec,
+ pj_pool_t *pool );
+static pj_status_t codec_open( pjmedia_codec *codec,
+ pjmedia_codec_param *attr );
+static pj_status_t codec_close( pjmedia_codec *codec );
+static pj_status_t codec_modify(pjmedia_codec *codec,
+ const pjmedia_codec_param *attr );
+static pj_status_t codec_parse( pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[]);
+static pj_status_t codec_encode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+static pj_status_t codec_decode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+static pj_status_t codec_recover( pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+
+/* Definition for passthrough codecs operations. */
+static pjmedia_codec_op codec_op =
+{
+ &codec_init,
+ &codec_open,
+ &codec_close,
+ &codec_modify,
+ &codec_parse,
+ &codec_encode,
+ &codec_decode,
+ &codec_recover
+};
+
+/* Definition for passthrough codecs factory operations. */
+static pjmedia_codec_factory_op codec_factory_op =
+{
+ &test_alloc,
+ &default_attr,
+ &enum_codecs,
+ &alloc_codec,
+ &dealloc_codec,
+ &pjmedia_codec_passthrough_deinit
+};
+
+/* Passthrough codecs factory */
+static struct codec_factory {
+ pjmedia_codec_factory base;
+ pjmedia_endpt *endpt;
+ pj_pool_t *pool;
+ pj_mutex_t *mutex;
+} codec_factory;
+
+/* Passthrough codecs private data. */
+typedef struct codec_private {
+ pj_pool_t *pool; /**< Pool for each instance. */
+ int codec_idx; /**< Codec index. */
+ void *codec_setting; /**< Specific codec setting. */
+ pj_uint16_t avg_frame_size; /**< Average of frame size. */
+ unsigned samples_per_frame; /**< Samples per frame, for iLBC
+ this can be 240 or 160, can
+ only be known after codec is
+ opened. */
+} codec_private_t;
+
+
+
+/* CUSTOM CALLBACKS */
+
+/* Parse frames from a packet. Default behaviour of frame parsing is
+ * just separating frames based on calculating frame length derived
+ * from bitrate. Implement this callback when the default behaviour is
+ * unapplicable.
+ */
+typedef pj_status_t (*parse_cb)(codec_private_t *codec_data, void *pkt,
+ pj_size_t pkt_size, const pj_timestamp *ts,
+ unsigned *frame_cnt, pjmedia_frame frames[]);
+
+/* Pack frames into a packet. Default behaviour of packing frames is
+ * just stacking the frames with octet aligned without adding any
+ * payload header. Implement this callback when the default behaviour is
+ * unapplicable.
+ */
+typedef pj_status_t (*pack_cb)(codec_private_t *codec_data,
+ const struct pjmedia_frame_ext *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+
+
+/* Custom callback implementations. */
+static pj_status_t parse_amr( codec_private_t *codec_data, void *pkt,
+ pj_size_t pkt_size, const pj_timestamp *ts,
+ unsigned *frame_cnt, pjmedia_frame frames[]);
+static pj_status_t pack_amr ( codec_private_t *codec_data,
+ const struct pjmedia_frame_ext *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+
+
+/* Passthrough codec implementation descriptions. */
+static struct codec_desc {
+ int enabled; /* Is this codec enabled? */
+ const char *name; /* Codec name. */
+ pj_uint8_t pt; /* Payload type. */
+ pjmedia_format_id fmt_id; /* Source format. */
+ unsigned clock_rate; /* Codec's clock rate. */
+ unsigned channel_count; /* Codec's channel count. */
+ unsigned samples_per_frame; /* Codec's samples count. */
+ unsigned def_bitrate; /* Default bitrate of this codec. */
+ unsigned max_bitrate; /* Maximum bitrate of this codec. */
+ pj_uint8_t frm_per_pkt; /* Default num of frames per packet.*/
+ pj_uint8_t vad; /* VAD enabled/disabled. */
+ pj_uint8_t plc; /* PLC enabled/disabled. */
+ parse_cb parse; /* Callback to parse bitstream. */
+ pack_cb pack; /* Callback to pack bitstream. */
+ pjmedia_codec_fmtp dec_fmtp; /* Decoder's fmtp params. */
+}
+
+codec_desc[] =
+{
+# if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
+ {1, "AMR", PJMEDIA_RTP_PT_AMR, PJMEDIA_FORMAT_AMR,
+ 8000, 1, 160,
+ 7400, 12200, 2, 1, 1,
+ &parse_amr, &pack_amr
+ /*, {1, {{{"octet-align", 11}, {"1", 1}}} } */
+ },
+# endif
+
+# if PJMEDIA_HAS_PASSTHROUGH_CODEC_G729
+ {1, "G729", PJMEDIA_RTP_PT_G729, PJMEDIA_FORMAT_G729,
+ 8000, 1, 80,
+ 8000, 8000, 2, 1, 1
+ },
+# endif
+
+# if PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC
+ {1, "iLBC", PJMEDIA_RTP_PT_ILBC, PJMEDIA_FORMAT_ILBC,
+ 8000, 1, 240,
+ 13333, 15200, 1, 1, 1,
+ NULL, NULL,
+ {1, {{{"mode", 4}, {"30", 2}}} }
+ },
+# endif
+
+# if PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU
+ {1, "PCMU", PJMEDIA_RTP_PT_PCMU, PJMEDIA_FORMAT_PCMU,
+ 8000, 1, 80,
+ 64000, 64000, 2, 1, 1
+ },
+# endif
+
+# if PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA
+ {1, "PCMA", PJMEDIA_RTP_PT_PCMA, PJMEDIA_FORMAT_PCMA,
+ 8000, 1, 80,
+ 64000, 64000, 2, 1, 1
+ },
+# endif
+};
+
+
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
+
+#include <pjmedia-codec/amr_helper.h>
+
+typedef struct amr_settings_t {
+ pjmedia_codec_amr_pack_setting enc_setting;
+ pjmedia_codec_amr_pack_setting dec_setting;
+ pj_int8_t enc_mode;
+} amr_settings_t;
+
+
+/* Pack AMR payload */
+static pj_status_t pack_amr ( codec_private_t *codec_data,
+ const struct pjmedia_frame_ext *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ enum {MAX_FRAMES_PER_PACKET = PJMEDIA_MAX_FRAME_DURATION_MS / 20};
+
+ pjmedia_frame frames[MAX_FRAMES_PER_PACKET];
+ amr_settings_t* setting = (amr_settings_t*)codec_data->codec_setting;
+ pjmedia_codec_amr_pack_setting *enc_setting = &setting->enc_setting;
+ pj_uint8_t SID_FT;
+ unsigned i;
+
+ pj_assert(input->subframe_cnt <= MAX_FRAMES_PER_PACKET);
+
+ SID_FT = (pj_uint8_t)(enc_setting->amr_nb? 8 : 9);
+
+ /* Get frames */
+ for (i = 0; i < input->subframe_cnt; ++i) {
+ pjmedia_frame_ext_subframe *sf;
+ pjmedia_codec_amr_bit_info *info;
+ unsigned len;
+
+ sf = pjmedia_frame_ext_get_subframe(input, i);
+ len = (sf->bitlen + 7) >> 3;
+
+ info = (pjmedia_codec_amr_bit_info*) &frames[i].bit_info;
+ pj_bzero(info, sizeof(*info));
+
+ if (len == 0) {
+ /* DTX */
+ info->frame_type = 15;
+ } else {
+ info->frame_type = pjmedia_codec_amr_get_mode2(enc_setting->amr_nb,
+ len);
+ }
+ info->good_quality = 1;
+ info->mode = setting->enc_mode;
+ if (info->frame_type == SID_FT)
+ info->STI = (sf->data[4] >> 4) & 1;
+
+ frames[i].buf = sf->data;
+ frames[i].size = len;
+ }
+
+ output->size = output_buf_len;
+
+ return pjmedia_codec_amr_pack(frames, input->subframe_cnt, enc_setting,
+ output->buf, &output->size);
+}
+
+
+/* Parse AMR payload into frames. */
+static pj_status_t parse_amr(codec_private_t *codec_data, void *pkt,
+ pj_size_t pkt_size, const pj_timestamp *ts,
+ unsigned *frame_cnt, pjmedia_frame frames[])
+{
+ amr_settings_t* s = (amr_settings_t*)codec_data->codec_setting;
+ pjmedia_codec_amr_pack_setting *setting;
+ pj_status_t status;
+ pj_uint8_t cmr;
+
+ setting = &s->dec_setting;
+
+ status = pjmedia_codec_amr_parse(pkt, pkt_size, ts, setting, frames,
+ frame_cnt, &cmr);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ // CMR is not supported for now.
+ /* Check Change Mode Request. */
+ //if ((setting->amr_nb && cmr <= 7) || (!setting->amr_nb && cmr <= 8)) {
+ // s->enc_mode = cmr;
+ //}
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_HAS_PASSTROUGH_CODEC_AMR */
+
+
+/*
+ * Initialize and register passthrough codec factory to pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_passthrough_init( pjmedia_endpt *endpt )
+{
+ pjmedia_codec_mgr *codec_mgr;
+ pj_str_t codec_name;
+ pj_status_t status;
+
+ if (codec_factory.pool != NULL) {
+ /* Already initialized. */
+ return PJ_EEXISTS;
+ }
+
+ /* Create passthrough codec factory. */
+ codec_factory.base.op = &codec_factory_op;
+ codec_factory.base.factory_data = NULL;
+ codec_factory.endpt = endpt;
+
+ codec_factory.pool = pjmedia_endpt_create_pool(endpt, "Passthrough codecs",
+ 4000, 4000);
+ if (!codec_factory.pool)
+ return PJ_ENOMEM;
+
+ /* Create mutex. */
+ status = pj_mutex_create_simple(codec_factory.pool, "Passthrough codecs",
+ &codec_factory.mutex);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
+ if (!codec_mgr) {
+ status = PJ_EINVALIDOP;
+ goto on_error;
+ }
+
+ /* Register format match callback. */
+#if PJMEDIA_HAS_PASSTROUGH_CODEC_AMR
+ pj_cstr(&codec_name, "AMR");
+ status = pjmedia_sdp_neg_register_fmt_match_cb(
+ &codec_name,
+ &pjmedia_codec_amr_match_sdp);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+#endif
+
+ /* Suppress compile warning */
+ PJ_UNUSED_ARG(codec_name);
+
+ /* Register codec factory to endpoint. */
+ status = pjmedia_codec_mgr_register_factory(codec_mgr,
+ &codec_factory.base);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Done. */
+ return PJ_SUCCESS;
+
+on_error:
+ pj_pool_release(codec_factory.pool);
+ codec_factory.pool = NULL;
+ return status;
+}
+
+/*
+ * Initialize and register passthrough codec factory to pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_passthrough_init2(
+ pjmedia_endpt *endpt,
+ const pjmedia_codec_passthrough_setting *setting)
+{
+ if (codec_factory.pool != NULL) {
+ /* Already initialized. */
+ return PJ_EEXISTS;
+ }
+
+ if (setting != NULL) {
+ unsigned i;
+
+ /* Enable/disable codecs based on the specified encoding formats */
+ for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) {
+ pj_bool_t enabled = PJ_FALSE;
+ unsigned j;
+
+ for (j = 0; j < setting->fmt_cnt && !enabled; ++j) {
+ if ((pj_uint32_t)codec_desc[i].fmt_id == setting->fmts[j].id)
+ enabled = PJ_TRUE;
+ }
+
+ codec_desc[i].enabled = enabled;
+ }
+
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC
+ /* Update iLBC codec description based on default mode setting. */
+ for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) {
+ if (codec_desc[i].enabled &&
+ codec_desc[i].fmt_id == PJMEDIA_FORMAT_ILBC)
+ {
+ codec_desc[i].samples_per_frame =
+ (setting->ilbc_mode == 20? 160 : 240);
+ codec_desc[i].def_bitrate =
+ (setting->ilbc_mode == 20? 15200 : 13333);
+ pj_strset2(&codec_desc[i].dec_fmtp.param[0].val,
+ (setting->ilbc_mode == 20? "20" : "30"));
+ break;
+ }
+ }
+#endif
+ }
+
+ return pjmedia_codec_passthrough_init(endpt);
+}
+
+/*
+ * Unregister passthrough codecs factory from pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_passthrough_deinit(void)
+{
+ pjmedia_codec_mgr *codec_mgr;
+ unsigned i;
+ pj_status_t status;
+
+ if (codec_factory.pool == NULL) {
+ /* Already deinitialized */
+ return PJ_SUCCESS;
+ }
+
+ pj_mutex_lock(codec_factory.mutex);
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(codec_factory.endpt);
+ if (!codec_mgr) {
+ pj_pool_release(codec_factory.pool);
+ codec_factory.pool = NULL;
+ return PJ_EINVALIDOP;
+ }
+
+ /* Unregister passthrough codecs factory. */
+ status = pjmedia_codec_mgr_unregister_factory(codec_mgr,
+ &codec_factory.base);
+
+ /* Destroy mutex. */
+ pj_mutex_destroy(codec_factory.mutex);
+
+ /* Destroy pool. */
+ pj_pool_release(codec_factory.pool);
+ codec_factory.pool = NULL;
+
+ /* Re-enable all codecs in the codec_desc. */
+ for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) {
+ codec_desc[i].enabled = PJ_TRUE;
+ }
+
+ return status;
+}
+
+/*
+ * Check if factory can allocate the specified codec.
+ */
+static pj_status_t test_alloc( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *info )
+{
+ unsigned i;
+
+ PJ_UNUSED_ARG(factory);
+
+ /* Type MUST be audio. */
+ if (info->type != PJMEDIA_TYPE_AUDIO)
+ return PJMEDIA_CODEC_EUNSUP;
+
+ for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) {
+ pj_str_t name = pj_str((char*)codec_desc[i].name);
+ if ((pj_stricmp(&info->encoding_name, &name) == 0) &&
+ (info->clock_rate == (unsigned)codec_desc[i].clock_rate) &&
+ (info->channel_cnt == (unsigned)codec_desc[i].channel_count) &&
+ (codec_desc[i].enabled))
+ {
+ return PJ_SUCCESS;
+ }
+ }
+
+ /* Unsupported, or mode is disabled. */
+ return PJMEDIA_CODEC_EUNSUP;
+}
+
+/*
+ * Generate default attribute.
+ */
+static pj_status_t default_attr ( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr )
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(factory==&codec_factory.base, PJ_EINVAL);
+
+ pj_bzero(attr, sizeof(pjmedia_codec_param));
+
+ for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) {
+ pj_str_t name = pj_str((char*)codec_desc[i].name);
+ if ((pj_stricmp(&id->encoding_name, &name) == 0) &&
+ (id->clock_rate == (unsigned)codec_desc[i].clock_rate) &&
+ (id->channel_cnt == (unsigned)codec_desc[i].channel_count) &&
+ (id->pt == (unsigned)codec_desc[i].pt))
+ {
+ attr->info.pt = (pj_uint8_t)id->pt;
+ attr->info.channel_cnt = codec_desc[i].channel_count;
+ attr->info.clock_rate = codec_desc[i].clock_rate;
+ attr->info.avg_bps = codec_desc[i].def_bitrate;
+ attr->info.max_bps = codec_desc[i].max_bitrate;
+ attr->info.pcm_bits_per_sample = 16;
+ attr->info.frm_ptime = (pj_uint16_t)
+ (codec_desc[i].samples_per_frame * 1000 /
+ codec_desc[i].channel_count /
+ codec_desc[i].clock_rate);
+ attr->info.fmt_id = codec_desc[i].fmt_id;
+
+ /* Default flags. */
+ attr->setting.frm_per_pkt = codec_desc[i].frm_per_pkt;
+ attr->setting.plc = codec_desc[i].plc;
+ attr->setting.penh= 0;
+ attr->setting.vad = codec_desc[i].vad;
+ attr->setting.cng = attr->setting.vad;
+ attr->setting.dec_fmtp = codec_desc[i].dec_fmtp;
+
+ if (attr->setting.vad == 0) {
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_G729
+ if (id->pt == PJMEDIA_RTP_PT_G729) {
+ /* Signal G729 Annex B is being disabled */
+ attr->setting.dec_fmtp.cnt = 1;
+ pj_strset2(&attr->setting.dec_fmtp.param[0].name, "annexb");
+ pj_strset2(&attr->setting.dec_fmtp.param[0].val, "no");
+ }
+#endif
+ }
+
+ return PJ_SUCCESS;
+ }
+ }
+
+ return PJMEDIA_CODEC_EUNSUP;
+}
+
+/*
+ * Enum codecs supported by this factory.
+ */
+static pj_status_t enum_codecs( pjmedia_codec_factory *factory,
+ unsigned *count,
+ pjmedia_codec_info codecs[])
+{
+ unsigned max;
+ unsigned i;
+
+ PJ_UNUSED_ARG(factory);
+ PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL);
+
+ max = *count;
+
+ for (i = 0, *count = 0; i < PJ_ARRAY_SIZE(codec_desc) && *count < max; ++i)
+ {
+ if (!codec_desc[i].enabled)
+ continue;
+
+ pj_bzero(&codecs[*count], sizeof(pjmedia_codec_info));
+ codecs[*count].encoding_name = pj_str((char*)codec_desc[i].name);
+ codecs[*count].pt = codec_desc[i].pt;
+ codecs[*count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[*count].clock_rate = codec_desc[i].clock_rate;
+ codecs[*count].channel_cnt = codec_desc[i].channel_count;
+
+ ++*count;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Allocate a new codec instance.
+ */
+static pj_status_t alloc_codec( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec)
+{
+ codec_private_t *codec_data;
+ pjmedia_codec *codec;
+ int idx;
+ pj_pool_t *pool;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory == &codec_factory.base, PJ_EINVAL);
+
+ pj_mutex_lock(codec_factory.mutex);
+
+ /* Find codec's index */
+ idx = -1;
+ for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) {
+ pj_str_t name = pj_str((char*)codec_desc[i].name);
+ if ((pj_stricmp(&id->encoding_name, &name) == 0) &&
+ (id->clock_rate == (unsigned)codec_desc[i].clock_rate) &&
+ (id->channel_cnt == (unsigned)codec_desc[i].channel_count) &&
+ (codec_desc[i].enabled))
+ {
+ idx = i;
+ break;
+ }
+ }
+ if (idx == -1) {
+ *p_codec = NULL;
+ return PJMEDIA_CODEC_EUNSUP;
+ }
+
+ /* Create pool for codec instance */
+ pool = pjmedia_endpt_create_pool(codec_factory.endpt, "passthroughcodec",
+ 512, 512);
+ codec = PJ_POOL_ZALLOC_T(pool, pjmedia_codec);
+ codec->op = &codec_op;
+ codec->factory = factory;
+ codec->codec_data = PJ_POOL_ZALLOC_T(pool, codec_private_t);
+ codec_data = (codec_private_t*) codec->codec_data;
+ codec_data->pool = pool;
+ codec_data->codec_idx = idx;
+
+ pj_mutex_unlock(codec_factory.mutex);
+
+ *p_codec = codec;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Free codec.
+ */
+static pj_status_t dealloc_codec( pjmedia_codec_factory *factory,
+ pjmedia_codec *codec )
+{
+ codec_private_t *codec_data;
+
+ PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory == &codec_factory.base, PJ_EINVAL);
+
+ /* Close codec, if it's not closed. */
+ codec_data = (codec_private_t*) codec->codec_data;
+ codec_close(codec);
+
+ pj_pool_release(codec_data->pool);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Init codec.
+ */
+static pj_status_t codec_init( pjmedia_codec *codec,
+ pj_pool_t *pool )
+{
+ PJ_UNUSED_ARG(codec);
+ PJ_UNUSED_ARG(pool);
+ return PJ_SUCCESS;
+}
+
+/*
+ * Open codec.
+ */
+static pj_status_t codec_open( pjmedia_codec *codec,
+ pjmedia_codec_param *attr )
+{
+ codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+ struct codec_desc *desc = &codec_desc[codec_data->codec_idx];
+ pj_pool_t *pool;
+ int i, j;
+
+ pool = codec_data->pool;
+
+ /* Cache samples per frame value */
+ codec_data->samples_per_frame = desc->samples_per_frame;
+
+ /* Calculate bitstream size */
+ i = attr->info.avg_bps * codec_data->samples_per_frame;
+ j = desc->clock_rate << 3;
+ codec_data->avg_frame_size = (pj_uint16_t)(i / j);
+ if (i % j) ++codec_data->avg_frame_size;
+
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
+ /* Init AMR settings */
+ if (desc->pt == PJMEDIA_RTP_PT_AMR || desc->pt == PJMEDIA_RTP_PT_AMRWB) {
+ amr_settings_t *s;
+ pj_uint8_t octet_align = 0;
+ pj_int8_t enc_mode;
+
+ enc_mode = pjmedia_codec_amr_get_mode(attr->info.avg_bps);
+ pj_assert(enc_mode >= 0 && enc_mode <= 8);
+
+ for (i = 0; i < attr->setting.dec_fmtp.cnt; ++i) {
+ const pj_str_t STR_FMTP_OCTET_ALIGN = {"octet-align", 11};
+
+ /* Fetch octet-align setting. It should be fine to fetch only
+ * the decoder, since encoder & decoder must use the same setting
+ * (RFC 4867 section 8.3.1).
+ */
+ if (pj_stricmp(&attr->setting.dec_fmtp.param[i].name,
+ &STR_FMTP_OCTET_ALIGN) == 0)
+ {
+ octet_align=(pj_uint8_t)
+ (pj_strtoul(&attr->setting.dec_fmtp.param[i].val));
+ break;
+ }
+ }
+
+ for (i = 0; i < attr->setting.enc_fmtp.cnt; ++i) {
+ const pj_str_t STR_FMTP_MODE_SET = {"mode-set", 8};
+
+ /* mode-set, encoding mode is chosen based on local default mode
+ * setting:
+ * - if local default mode is included in the mode-set, use it
+ * - otherwise, find the closest mode to local default mode;
+ * if there are two closest modes, prefer to use the higher
+ * one, e.g: local default mode is 4, the mode-set param
+ * contains '2,3,5,6', then 5 will be chosen.
+ */
+ if (pj_stricmp(&attr->setting.enc_fmtp.param[i].name,
+ &STR_FMTP_MODE_SET) == 0)
+ {
+ const char *p;
+ pj_size_t l;
+ pj_int8_t diff = 99;
+
+ p = pj_strbuf(&attr->setting.enc_fmtp.param[i].val);
+ l = pj_strlen(&attr->setting.enc_fmtp.param[i].val);
+
+ while (l--) {
+ if ((desc->pt==PJMEDIA_RTP_PT_AMR && *p>='0' && *p<='7') ||
+ (desc->pt==PJMEDIA_RTP_PT_AMRWB && *p>='0' && *p<='8'))
+ {
+ pj_int8_t tmp = (pj_int8_t)(*p - '0' - enc_mode);
+
+ if (PJ_ABS(diff) > PJ_ABS(tmp) ||
+ (PJ_ABS(diff) == PJ_ABS(tmp) && tmp > diff))
+ {
+ diff = tmp;
+ if (diff == 0) break;
+ }
+ }
+ ++p;
+ }
+
+ if (diff == 99)
+ return PJMEDIA_CODEC_EFAILED;
+
+ enc_mode = (pj_int8_t)(enc_mode + diff);
+
+ break;
+ }
+ }
+
+ s = PJ_POOL_ZALLOC_T(pool, amr_settings_t);
+ codec_data->codec_setting = s;
+
+ s->enc_mode = enc_mode;
+ if (s->enc_mode < 0)
+ return PJMEDIA_CODEC_EINMODE;
+
+ s->enc_setting.amr_nb = (pj_uint8_t)(desc->pt == PJMEDIA_RTP_PT_AMR);
+ s->enc_setting.octet_aligned = octet_align;
+ s->enc_setting.reorder = PJ_FALSE; /* Note this! passthrough codec
+ doesn't do sensitivity bits
+ reordering */
+ s->enc_setting.cmr = 15;
+
+ s->dec_setting.amr_nb = (pj_uint8_t)(desc->pt == PJMEDIA_RTP_PT_AMR);
+ s->dec_setting.octet_aligned = octet_align;
+ s->dec_setting.reorder = PJ_FALSE; /* Note this! passthrough codec
+ doesn't do sensitivity bits
+ reordering */
+
+ /* Return back bitrate info to application */
+ attr->info.avg_bps = s->enc_setting.amr_nb?
+ pjmedia_codec_amrnb_bitrates[s->enc_mode]:
+ pjmedia_codec_amrwb_bitrates[s->enc_mode];
+ }
+#endif
+
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC
+ /* Init iLBC settings */
+ if (desc->pt == PJMEDIA_RTP_PT_ILBC)
+ {
+ enum { DEFAULT_MODE = 30 };
+ static pj_str_t STR_MODE = {"mode", 4};
+ pj_uint16_t dec_fmtp_mode = DEFAULT_MODE,
+ enc_fmtp_mode = DEFAULT_MODE;
+
+ /* Get decoder mode */
+ for (i = 0; i < attr->setting.dec_fmtp.cnt; ++i) {
+ if (pj_stricmp(&attr->setting.dec_fmtp.param[i].name, &STR_MODE) == 0)
+ {
+ dec_fmtp_mode = (pj_uint16_t)
+ pj_strtoul(&attr->setting.dec_fmtp.param[i].val);
+ break;
+ }
+ }
+
+ /* Decoder mode must be set */
+ PJ_ASSERT_RETURN(dec_fmtp_mode == 20 || dec_fmtp_mode == 30,
+ PJMEDIA_CODEC_EINMODE);
+
+ /* Get encoder mode */
+ for (i = 0; i < attr->setting.enc_fmtp.cnt; ++i) {
+ if (pj_stricmp(&attr->setting.enc_fmtp.param[i].name, &STR_MODE) == 0)
+ {
+ enc_fmtp_mode = (pj_uint16_t)
+ pj_strtoul(&attr->setting.enc_fmtp.param[i].val);
+ break;
+ }
+ }
+
+ PJ_ASSERT_RETURN(enc_fmtp_mode==20 || enc_fmtp_mode==30,
+ PJMEDIA_CODEC_EINMODE);
+
+ /* Both sides of a bi-directional session MUST use the same "mode" value.
+ * In this point, possible values are only 20 or 30, so when encoder and
+ * decoder modes are not same, just use the default mode, it is 30.
+ */
+ if (enc_fmtp_mode != dec_fmtp_mode) {
+ enc_fmtp_mode = dec_fmtp_mode = DEFAULT_MODE;
+ PJ_LOG(4,(pool->obj_name,
+ "Normalized iLBC encoder and decoder modes to %d",
+ DEFAULT_MODE));
+ }
+
+ /* Update some attributes based on negotiated mode. */
+ attr->info.avg_bps = (dec_fmtp_mode == 30? 13333 : 15200);
+ attr->info.frm_ptime = dec_fmtp_mode;
+
+ /* Override average frame size */
+ codec_data->avg_frame_size = (dec_fmtp_mode == 30? 50 : 38);
+
+ /* Override samples per frame */
+ codec_data->samples_per_frame = (dec_fmtp_mode == 30? 240 : 160);
+ }
+#endif
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Close codec.
+ */
+static pj_status_t codec_close( pjmedia_codec *codec )
+{
+ PJ_UNUSED_ARG(codec);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Modify codec settings.
+ */
+static pj_status_t codec_modify( pjmedia_codec *codec,
+ const pjmedia_codec_param *attr )
+{
+ /* Not supported yet. */
+ PJ_UNUSED_ARG(codec);
+ PJ_UNUSED_ARG(attr);
+
+ return PJ_ENOTSUP;
+}
+
+/*
+ * Get frames in the packet.
+ */
+static pj_status_t codec_parse( pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[])
+{
+ codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+ struct codec_desc *desc = &codec_desc[codec_data->codec_idx];
+ unsigned count = 0;
+
+ PJ_ASSERT_RETURN(frame_cnt, PJ_EINVAL);
+
+ if (desc->parse != NULL) {
+ return desc->parse(codec_data, pkt, pkt_size, ts, frame_cnt, frames);
+ }
+
+ while (pkt_size >= codec_data->avg_frame_size && count < *frame_cnt) {
+ frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frames[count].buf = pkt;
+ frames[count].size = codec_data->avg_frame_size;
+ frames[count].timestamp.u64 = ts->u64 +
+ count * codec_data->samples_per_frame;
+
+ pkt = (pj_uint8_t*)pkt + codec_data->avg_frame_size;
+ pkt_size -= codec_data->avg_frame_size;
+
+ ++count;
+ }
+
+ if (pkt_size && count < *frame_cnt) {
+ frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frames[count].buf = pkt;
+ frames[count].size = pkt_size;
+ frames[count].timestamp.u64 = ts->u64 +
+ count * codec_data->samples_per_frame;
+ ++count;
+ }
+
+ *frame_cnt = count;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Encode frames.
+ */
+static pj_status_t codec_encode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+ struct codec_desc *desc = &codec_desc[codec_data->codec_idx];
+ const pjmedia_frame_ext *input_ = (const pjmedia_frame_ext*) input;
+
+ pj_assert(input && input->type == PJMEDIA_FRAME_TYPE_EXTENDED);
+
+ if (desc->pack != NULL) {
+ desc->pack(codec_data, input_, output_buf_len, output);
+ } else {
+ if (input_->subframe_cnt == 0) {
+ /* DTX */
+ output->buf = NULL;
+ output->size = 0;
+ output->type = PJMEDIA_FRAME_TYPE_NONE;
+ } else {
+ unsigned i;
+ pj_uint8_t *p = output->buf;
+
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->size = 0;
+
+ for (i = 0; i < input_->subframe_cnt; ++i) {
+ pjmedia_frame_ext_subframe *sf;
+ unsigned sf_len;
+
+ sf = pjmedia_frame_ext_get_subframe(input_, i);
+ pj_assert(sf);
+
+ sf_len = (sf->bitlen + 7) >> 3;
+
+ pj_memcpy(p, sf->data, sf_len);
+ p += sf_len;
+ output->size += sf_len;
+
+ /* If there is SID or DTX frame, break the loop. */
+ if (desc->pt == PJMEDIA_RTP_PT_G729 &&
+ sf_len < codec_data->avg_frame_size)
+ {
+ break;
+ }
+
+ }
+ }
+ }
+
+ output->timestamp = input->timestamp;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Decode frame.
+ */
+static pj_status_t codec_decode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
+ struct codec_desc *desc = &codec_desc[codec_data->codec_idx];
+#endif
+ pjmedia_frame_ext *output_ = (pjmedia_frame_ext*) output;
+
+ pj_assert(input);
+ PJ_UNUSED_ARG(output_buf_len);
+
+#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
+ /* Need to rearrange the AMR bitstream, since the bitstream may not be
+ * started from bit 0 or may need to be reordered from sensitivity order
+ * into encoder bits order.
+ */
+ if (desc->pt == PJMEDIA_RTP_PT_AMR || desc->pt == PJMEDIA_RTP_PT_AMRWB) {
+ pjmedia_frame input_;
+ pjmedia_codec_amr_pack_setting *setting;
+
+ setting = &((amr_settings_t*)codec_data->codec_setting)->dec_setting;
+
+ input_ = *input;
+ pjmedia_codec_amr_predecode(input, setting, &input_);
+
+ pjmedia_frame_ext_append_subframe(output_, input_.buf,
+ (pj_uint16_t)(input_.size << 3),
+ (pj_uint16_t)codec_data->samples_per_frame);
+ output->timestamp = input->timestamp;
+
+ return PJ_SUCCESS;
+ }
+#endif
+
+ pjmedia_frame_ext_append_subframe(output_, input->buf,
+ (pj_uint16_t)(input->size << 3),
+ (pj_uint16_t)codec_data->samples_per_frame);
+ output->timestamp = input->timestamp;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Recover lost frame.
+ */
+static pj_status_t codec_recover( pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ codec_private_t *codec_data = (codec_private_t*) codec->codec_data;
+ pjmedia_frame_ext *output_ = (pjmedia_frame_ext*) output;
+
+ PJ_UNUSED_ARG(output_buf_len);
+
+ pjmedia_frame_ext_append_subframe(output_, NULL, 0,
+ (pj_uint16_t)codec_data->samples_per_frame);
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
+
diff --git a/pjmedia/src/pjmedia-codec/speex_codec.c b/pjmedia/src/pjmedia-codec/speex_codec.c
new file mode 100644
index 0000000..4623ef5
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/speex_codec.c
@@ -0,0 +1,997 @@
+/* $Id: speex_codec.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia-codec/speex.h>
+#include <pjmedia/codec.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/endpoint.h>
+#include <pjmedia/port.h>
+#include <speex/speex.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/os.h>
+
+/*
+ * Only build this file if PJMEDIA_HAS_SPEEX_CODEC != 0
+ */
+#if defined(PJMEDIA_HAS_SPEEX_CODEC) && PJMEDIA_HAS_SPEEX_CODEC!=0
+
+
+#define THIS_FILE "speex_codec.c"
+
+/* Prototypes for Speex factory */
+static pj_status_t spx_test_alloc( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id );
+static pj_status_t spx_default_attr( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr );
+static pj_status_t spx_enum_codecs( pjmedia_codec_factory *factory,
+ unsigned *count,
+ pjmedia_codec_info codecs[]);
+static pj_status_t spx_alloc_codec( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec);
+static pj_status_t spx_dealloc_codec( pjmedia_codec_factory *factory,
+ pjmedia_codec *codec );
+
+/* Prototypes for Speex implementation. */
+static pj_status_t spx_codec_init( pjmedia_codec *codec,
+ pj_pool_t *pool );
+static pj_status_t spx_codec_open( pjmedia_codec *codec,
+ pjmedia_codec_param *attr );
+static pj_status_t spx_codec_close( pjmedia_codec *codec );
+static pj_status_t spx_codec_modify(pjmedia_codec *codec,
+ const pjmedia_codec_param *attr );
+static pj_status_t spx_codec_parse( pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[]);
+static pj_status_t spx_codec_encode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+static pj_status_t spx_codec_decode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+static pj_status_t spx_codec_recover(pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+
+/* Definition for Speex codec operations. */
+static pjmedia_codec_op spx_op =
+{
+ &spx_codec_init,
+ &spx_codec_open,
+ &spx_codec_close,
+ &spx_codec_modify,
+ &spx_codec_parse,
+ &spx_codec_encode,
+ &spx_codec_decode,
+ &spx_codec_recover
+};
+
+/* Definition for Speex codec factory operations. */
+static pjmedia_codec_factory_op spx_factory_op =
+{
+ &spx_test_alloc,
+ &spx_default_attr,
+ &spx_enum_codecs,
+ &spx_alloc_codec,
+ &spx_dealloc_codec,
+ &pjmedia_codec_speex_deinit
+};
+
+/* Index to Speex parameter. */
+enum
+{
+ PARAM_NB, /* Index for narrowband parameter. */
+ PARAM_WB, /* Index for wideband parameter. */
+ PARAM_UWB, /* Index for ultra-wideband parameter */
+};
+
+/* Speex default parameter */
+struct speex_param
+{
+ int enabled; /* Is this mode enabled? */
+ const SpeexMode *mode; /* Speex mode. */
+ int pt; /* Payload type. */
+ unsigned clock_rate; /* Default sampling rate to be used.*/
+ int quality; /* Default encoder quality. */
+ int complexity; /* Default encoder complexity. */
+ int samples_per_frame; /* Samples per frame. */
+ int framesize; /* Frame size for current mode. */
+ int bitrate; /* Bit rate for current mode. */
+ int max_bitrate; /* Max bit rate for current mode. */
+};
+
+/* Speex factory */
+static struct spx_factory
+{
+ pjmedia_codec_factory base;
+ pjmedia_endpt *endpt;
+ pj_pool_t *pool;
+ pj_mutex_t *mutex;
+ pjmedia_codec codec_list;
+ struct speex_param speex_param[3];
+
+} spx_factory;
+
+/* Speex codec private data. */
+struct spx_private
+{
+ int param_id; /**< Index to speex param. */
+
+ void *enc; /**< Encoder state. */
+ SpeexBits enc_bits; /**< Encoder bits. */
+ void *dec; /**< Decoder state. */
+ SpeexBits dec_bits; /**< Decoder bits. */
+};
+
+
+/*
+ * Get codec bitrate and frame size.
+ */
+static pj_status_t get_speex_info( struct speex_param *p )
+{
+ void *state;
+ int tmp;
+
+ /* Create temporary encoder */
+ state = speex_encoder_init(p->mode);
+ if (!state)
+ return PJMEDIA_CODEC_EFAILED;
+
+ /* Set the quality */
+ if (p->quality != -1)
+ speex_encoder_ctl(state, SPEEX_SET_QUALITY, &p->quality);
+
+ /* Sampling rate. */
+ speex_encoder_ctl(state, SPEEX_SET_SAMPLING_RATE, &p->clock_rate);
+
+ /* VAD off to have max bitrate */
+ tmp = 0;
+ speex_encoder_ctl(state, SPEEX_SET_VAD, &tmp);
+
+ /* Complexity. */
+ if (p->complexity != -1)
+ speex_encoder_ctl(state, SPEEX_SET_COMPLEXITY, &p->complexity);
+
+ /* Now get the frame size */
+ speex_encoder_ctl(state, SPEEX_GET_FRAME_SIZE, &p->samples_per_frame);
+
+ /* Now get the average bitrate */
+ speex_encoder_ctl(state, SPEEX_GET_BITRATE, &p->bitrate);
+
+ /* Calculate framesize. */
+ p->framesize = p->bitrate * 20 / 1000;
+
+ /* Now get the maximum bitrate by using maximum quality (=10) */
+ tmp = 10;
+ speex_encoder_ctl(state, SPEEX_SET_QUALITY, &tmp);
+ speex_encoder_ctl(state, SPEEX_GET_BITRATE, &p->max_bitrate);
+
+ /* Destroy encoder. */
+ speex_encoder_destroy(state);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Initialize and register Speex codec factory to pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_speex_init( pjmedia_endpt *endpt,
+ unsigned options,
+ int quality,
+ int complexity )
+{
+ pjmedia_codec_mgr *codec_mgr;
+ unsigned i;
+ pj_status_t status;
+
+ if (spx_factory.pool != NULL) {
+ /* Already initialized. */
+ return PJ_SUCCESS;
+ }
+
+ /* Get defaults */
+ if (quality < 0) quality = PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY;
+ if (complexity < 0) complexity = PJMEDIA_CODEC_SPEEX_DEFAULT_COMPLEXITY;
+
+ /* Validate quality & complexity */
+ PJ_ASSERT_RETURN(quality >= 0 && quality <= 10, PJ_EINVAL);
+ PJ_ASSERT_RETURN(complexity >= 1 && complexity <= 10, PJ_EINVAL);
+
+ /* Create Speex codec factory. */
+ spx_factory.base.op = &spx_factory_op;
+ spx_factory.base.factory_data = NULL;
+ spx_factory.endpt = endpt;
+
+ spx_factory.pool = pjmedia_endpt_create_pool(endpt, "speex",
+ 4000, 4000);
+ if (!spx_factory.pool)
+ return PJ_ENOMEM;
+
+ pj_list_init(&spx_factory.codec_list);
+
+ /* Create mutex. */
+ status = pj_mutex_create_simple(spx_factory.pool, "speex",
+ &spx_factory.mutex);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Initialize default Speex parameter. */
+ spx_factory.speex_param[PARAM_NB].enabled =
+ ((options & PJMEDIA_SPEEX_NO_NB) == 0);
+ spx_factory.speex_param[PARAM_NB].pt = PJMEDIA_RTP_PT_SPEEX_NB;
+ spx_factory.speex_param[PARAM_NB].mode = speex_lib_get_mode(SPEEX_MODEID_NB);
+ spx_factory.speex_param[PARAM_NB].clock_rate = 8000;
+ spx_factory.speex_param[PARAM_NB].quality = quality;
+ spx_factory.speex_param[PARAM_NB].complexity = complexity;
+
+ spx_factory.speex_param[PARAM_WB].enabled =
+ ((options & PJMEDIA_SPEEX_NO_WB) == 0);
+ spx_factory.speex_param[PARAM_WB].pt = PJMEDIA_RTP_PT_SPEEX_WB;
+ spx_factory.speex_param[PARAM_WB].mode = speex_lib_get_mode(SPEEX_MODEID_WB);
+ spx_factory.speex_param[PARAM_WB].clock_rate = 16000;
+ spx_factory.speex_param[PARAM_WB].quality = quality;
+ spx_factory.speex_param[PARAM_WB].complexity = complexity;
+
+ spx_factory.speex_param[PARAM_UWB].enabled =
+ ((options & PJMEDIA_SPEEX_NO_UWB) == 0);
+ spx_factory.speex_param[PARAM_UWB].pt = PJMEDIA_RTP_PT_SPEEX_UWB;
+ spx_factory.speex_param[PARAM_UWB].mode = speex_lib_get_mode(SPEEX_MODEID_UWB);
+ spx_factory.speex_param[PARAM_UWB].clock_rate = 32000;
+ spx_factory.speex_param[PARAM_UWB].quality = quality;
+ spx_factory.speex_param[PARAM_UWB].complexity = complexity;
+
+ /* Somehow quality <=4 is broken in linux. */
+ if (quality <= 4 && quality >= 0) {
+ PJ_LOG(5,(THIS_FILE, "Adjusting quality to 5 for uwb"));
+ spx_factory.speex_param[PARAM_UWB].quality = 5;
+ }
+
+ /* Get codec framesize and avg bitrate for each mode. */
+ for (i=0; i<PJ_ARRAY_SIZE(spx_factory.speex_param); ++i) {
+ status = get_speex_info(&spx_factory.speex_param[i]);
+ }
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
+ if (!codec_mgr) {
+ status = PJ_EINVALIDOP;
+ goto on_error;
+ }
+
+ /* Register codec factory to endpoint. */
+ status = pjmedia_codec_mgr_register_factory(codec_mgr,
+ &spx_factory.base);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Done. */
+ return PJ_SUCCESS;
+
+on_error:
+ pj_pool_release(spx_factory.pool);
+ spx_factory.pool = NULL;
+ return status;
+}
+
+
+/*
+ * Initialize with default settings.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_speex_init_default(pjmedia_endpt *endpt)
+{
+ return pjmedia_codec_speex_init(endpt, 0, -1, -1);
+}
+
+/*
+ * Change the settings of Speex codec.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_speex_set_param(unsigned clock_rate,
+ int quality,
+ int complexity)
+{
+ unsigned i;
+
+ /* Get defaults */
+ if (quality < 0) quality = PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY;
+ if (complexity < 0) complexity = PJMEDIA_CODEC_SPEEX_DEFAULT_COMPLEXITY;
+
+ /* Validate quality & complexity */
+ PJ_ASSERT_RETURN(quality >= 0 && quality <= 10, PJ_EINVAL);
+ PJ_ASSERT_RETURN(complexity >= 1 && complexity <= 10, PJ_EINVAL);
+
+ /* Apply the settings */
+ for (i=0; i<PJ_ARRAY_SIZE(spx_factory.speex_param); ++i) {
+ if (spx_factory.speex_param[i].clock_rate == clock_rate) {
+ pj_status_t status;
+
+ spx_factory.speex_param[i].quality = quality;
+ spx_factory.speex_param[i].complexity = complexity;
+
+ /* Somehow quality<=4 is broken in linux. */
+ if (i == PARAM_UWB && quality <= 4 && quality >= 0) {
+ PJ_LOG(5,(THIS_FILE, "Adjusting quality to 5 for uwb"));
+ spx_factory.speex_param[PARAM_UWB].quality = 5;
+ }
+
+ status = get_speex_info(&spx_factory.speex_param[i]);
+
+ return status;
+ }
+ }
+
+ return PJ_EINVAL;
+}
+
+/*
+ * Unregister Speex codec factory from pjmedia endpoint and deinitialize
+ * the Speex codec library.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_speex_deinit(void)
+{
+ pjmedia_codec_mgr *codec_mgr;
+ pj_status_t status;
+
+ if (spx_factory.pool == NULL) {
+ /* Already deinitialized */
+ return PJ_SUCCESS;
+ }
+
+ pj_mutex_lock(spx_factory.mutex);
+
+ /* We don't want to deinit if there's outstanding codec. */
+ /* This is silly, as we'll always have codec in the list if
+ we ever allocate a codec! A better behavior maybe is to
+ deallocate all codecs in the list.
+ if (!pj_list_empty(&spx_factory.codec_list)) {
+ pj_mutex_unlock(spx_factory.mutex);
+ return PJ_EBUSY;
+ }
+ */
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(spx_factory.endpt);
+ if (!codec_mgr) {
+ pj_pool_release(spx_factory.pool);
+ spx_factory.pool = NULL;
+ return PJ_EINVALIDOP;
+ }
+
+ /* Unregister Speex codec factory. */
+ status = pjmedia_codec_mgr_unregister_factory(codec_mgr,
+ &spx_factory.base);
+
+ /* Destroy mutex. */
+ pj_mutex_destroy(spx_factory.mutex);
+
+ /* Destroy pool. */
+ pj_pool_release(spx_factory.pool);
+ spx_factory.pool = NULL;
+
+ return status;
+}
+
+/*
+ * Check if factory can allocate the specified codec.
+ */
+static pj_status_t spx_test_alloc( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *info )
+{
+ const pj_str_t speex_tag = { "speex", 5};
+ unsigned i;
+
+ PJ_UNUSED_ARG(factory);
+
+ /* Type MUST be audio. */
+ if (info->type != PJMEDIA_TYPE_AUDIO)
+ return PJMEDIA_CODEC_EUNSUP;
+
+ /* Check encoding name. */
+ if (pj_stricmp(&info->encoding_name, &speex_tag) != 0)
+ return PJMEDIA_CODEC_EUNSUP;
+
+ /* Check clock-rate */
+ for (i=0; i<PJ_ARRAY_SIZE(spx_factory.speex_param); ++i) {
+ if (info->clock_rate == spx_factory.speex_param[i].clock_rate) {
+ /* Okay, let's Speex! */
+ return PJ_SUCCESS;
+ }
+ }
+
+
+ /* Unsupported, or mode is disabled. */
+ return PJMEDIA_CODEC_EUNSUP;
+}
+
+/*
+ * Generate default attribute.
+ */
+static pj_status_t spx_default_attr (pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr )
+{
+
+ PJ_ASSERT_RETURN(factory==&spx_factory.base, PJ_EINVAL);
+
+ pj_bzero(attr, sizeof(pjmedia_codec_param));
+ attr->info.pt = (pj_uint8_t)id->pt;
+ attr->info.channel_cnt = 1;
+
+ if (id->clock_rate <= 8000) {
+ attr->info.clock_rate = spx_factory.speex_param[PARAM_NB].clock_rate;
+ attr->info.avg_bps = spx_factory.speex_param[PARAM_NB].bitrate;
+ attr->info.max_bps = spx_factory.speex_param[PARAM_NB].max_bitrate;
+
+ } else if (id->clock_rate <= 16000) {
+ attr->info.clock_rate = spx_factory.speex_param[PARAM_WB].clock_rate;
+ attr->info.avg_bps = spx_factory.speex_param[PARAM_WB].bitrate;
+ attr->info.max_bps = spx_factory.speex_param[PARAM_WB].max_bitrate;
+
+ } else {
+ /* Wow.. somebody is doing ultra-wideband. Cool...! */
+ attr->info.clock_rate = spx_factory.speex_param[PARAM_UWB].clock_rate;
+ attr->info.avg_bps = spx_factory.speex_param[PARAM_UWB].bitrate;
+ attr->info.max_bps = spx_factory.speex_param[PARAM_UWB].max_bitrate;
+ }
+
+ attr->info.pcm_bits_per_sample = 16;
+ attr->info.frm_ptime = 20;
+ attr->info.pt = (pj_uint8_t)id->pt;
+
+ attr->setting.frm_per_pkt = 1;
+
+ /* Default flags. */
+ attr->setting.cng = 1;
+ attr->setting.plc = 1;
+ attr->setting.penh =1 ;
+ attr->setting.vad = 1;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Enum codecs supported by this factory (i.e. only Speex!).
+ */
+static pj_status_t spx_enum_codecs(pjmedia_codec_factory *factory,
+ unsigned *count,
+ pjmedia_codec_info codecs[])
+{
+ unsigned max;
+ int i; /* Must be signed */
+
+ PJ_UNUSED_ARG(factory);
+ PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL);
+
+ max = *count;
+ *count = 0;
+
+ /*
+ * We return three codecs here, and in this order:
+ * - ultra-wideband, wideband, and narrowband.
+ */
+ for (i=PJ_ARRAY_SIZE(spx_factory.speex_param)-1; i>=0 && *count<max; --i) {
+
+ if (!spx_factory.speex_param[i].enabled)
+ continue;
+
+ pj_bzero(&codecs[*count], sizeof(pjmedia_codec_info));
+ codecs[*count].encoding_name = pj_str("speex");
+ codecs[*count].pt = spx_factory.speex_param[i].pt;
+ codecs[*count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[*count].clock_rate = spx_factory.speex_param[i].clock_rate;
+ codecs[*count].channel_cnt = 1;
+
+ ++*count;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Allocate a new Speex codec instance.
+ */
+static pj_status_t spx_alloc_codec( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec)
+{
+ pjmedia_codec *codec;
+ struct spx_private *spx;
+
+ PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory == &spx_factory.base, PJ_EINVAL);
+
+
+ pj_mutex_lock(spx_factory.mutex);
+
+ /* Get free nodes, if any. */
+ if (!pj_list_empty(&spx_factory.codec_list)) {
+ codec = spx_factory.codec_list.next;
+ pj_list_erase(codec);
+ } else {
+ codec = PJ_POOL_ZALLOC_T(spx_factory.pool, pjmedia_codec);
+ PJ_ASSERT_RETURN(codec != NULL, PJ_ENOMEM);
+ codec->op = &spx_op;
+ codec->factory = factory;
+ codec->codec_data = pj_pool_alloc(spx_factory.pool,
+ sizeof(struct spx_private));
+ }
+
+ pj_mutex_unlock(spx_factory.mutex);
+
+ spx = (struct spx_private*) codec->codec_data;
+ spx->enc = NULL;
+ spx->dec = NULL;
+
+ if (id->clock_rate <= 8000)
+ spx->param_id = PARAM_NB;
+ else if (id->clock_rate <= 16000)
+ spx->param_id = PARAM_WB;
+ else
+ spx->param_id = PARAM_UWB;
+
+ *p_codec = codec;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Free codec.
+ */
+static pj_status_t spx_dealloc_codec( pjmedia_codec_factory *factory,
+ pjmedia_codec *codec )
+{
+ struct spx_private *spx;
+
+ PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory == &spx_factory.base, PJ_EINVAL);
+
+ /* Close codec, if it's not closed. */
+ spx = (struct spx_private*) codec->codec_data;
+ if (spx->enc != NULL || spx->dec != NULL) {
+ spx_codec_close(codec);
+ }
+
+ /* Put in the free list. */
+ pj_mutex_lock(spx_factory.mutex);
+ pj_list_push_front(&spx_factory.codec_list, codec);
+ pj_mutex_unlock(spx_factory.mutex);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Init codec.
+ */
+static pj_status_t spx_codec_init( pjmedia_codec *codec,
+ pj_pool_t *pool )
+{
+ PJ_UNUSED_ARG(codec);
+ PJ_UNUSED_ARG(pool);
+ return PJ_SUCCESS;
+}
+
+/*
+ * Open codec.
+ */
+static pj_status_t spx_codec_open( pjmedia_codec *codec,
+ pjmedia_codec_param *attr )
+{
+ struct spx_private *spx;
+ int id, tmp;
+
+ spx = (struct spx_private*) codec->codec_data;
+ id = spx->param_id;
+
+ /*
+ * Create and initialize encoder.
+ */
+ spx->enc = speex_encoder_init(spx_factory.speex_param[id].mode);
+ if (!spx->enc)
+ return PJMEDIA_CODEC_EFAILED;
+ speex_bits_init(&spx->enc_bits);
+
+ /* Set the quality*/
+ if (spx_factory.speex_param[id].quality != -1) {
+ speex_encoder_ctl(spx->enc, SPEEX_SET_QUALITY,
+ &spx_factory.speex_param[id].quality);
+ }
+
+ /* Sampling rate. */
+ tmp = attr->info.clock_rate;
+ speex_encoder_ctl(spx->enc, SPEEX_SET_SAMPLING_RATE,
+ &spx_factory.speex_param[id].clock_rate);
+
+ /* VAD */
+ tmp = (attr->setting.vad != 0);
+ speex_encoder_ctl(spx->enc, SPEEX_SET_VAD, &tmp);
+ speex_encoder_ctl(spx->enc, SPEEX_SET_DTX, &tmp);
+
+ /* Complexity */
+ if (spx_factory.speex_param[id].complexity != -1) {
+ speex_encoder_ctl(spx->enc, SPEEX_SET_COMPLEXITY,
+ &spx_factory.speex_param[id].complexity);
+ }
+
+ /*
+ * Create and initialize decoder.
+ */
+ spx->dec = speex_decoder_init(spx_factory.speex_param[id].mode);
+ if (!spx->dec) {
+ spx_codec_close(codec);
+ return PJMEDIA_CODEC_EFAILED;
+ }
+ speex_bits_init(&spx->dec_bits);
+
+ /* Sampling rate. */
+ speex_decoder_ctl(spx->dec, SPEEX_SET_SAMPLING_RATE,
+ &spx_factory.speex_param[id].clock_rate);
+
+ /* PENH */
+ tmp = attr->setting.penh;
+ speex_decoder_ctl(spx->dec, SPEEX_SET_ENH, &tmp);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Close codec.
+ */
+static pj_status_t spx_codec_close( pjmedia_codec *codec )
+{
+ struct spx_private *spx;
+
+ spx = (struct spx_private*) codec->codec_data;
+
+ /* Destroy encoder*/
+ if (spx->enc) {
+ speex_encoder_destroy( spx->enc );
+ spx->enc = NULL;
+ speex_bits_destroy( &spx->enc_bits );
+ }
+
+ /* Destroy decoder */
+ if (spx->dec) {
+ speex_decoder_destroy( spx->dec);
+ spx->dec = NULL;
+ speex_bits_destroy( &spx->dec_bits );
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Modify codec settings.
+ */
+static pj_status_t spx_codec_modify(pjmedia_codec *codec,
+ const pjmedia_codec_param *attr )
+{
+ struct spx_private *spx;
+ int tmp;
+
+ spx = (struct spx_private*) codec->codec_data;
+
+ /* VAD */
+ tmp = (attr->setting.vad != 0);
+ speex_encoder_ctl(spx->enc, SPEEX_SET_VAD, &tmp);
+ speex_encoder_ctl(spx->enc, SPEEX_SET_DTX, &tmp);
+
+ /* PENH */
+ tmp = attr->setting.penh;
+ speex_decoder_ctl(spx->dec, SPEEX_SET_ENH, &tmp);
+
+ return PJ_SUCCESS;
+}
+
+#if 0
+# define TRACE__(args) PJ_LOG(5,args)
+#else
+# define TRACE__(args)
+#endif
+
+#undef THIS_FUNC
+#define THIS_FUNC "speex_get_next_frame"
+
+#define NB_SUBMODES 16
+#define NB_SUBMODE_BITS 4
+
+#define SB_SUBMODES 8
+#define SB_SUBMODE_BITS 3
+
+/* This function will iterate frames & submodes in the Speex bits.
+ * Returns 0 if a frame found, otherwise returns -1.
+ */
+int speex_get_next_frame(SpeexBits *bits)
+{
+ static const int inband_skip_table[NB_SUBMODES] =
+ {1, 1, 4, 4, 4, 4, 4, 4, 8, 8, 16, 16, 32, 32, 64, 64 };
+ static const int wb_skip_table[SB_SUBMODES] =
+ {SB_SUBMODE_BITS+1, 36, 112, 192, 352, -1, -1, -1};
+
+ unsigned submode;
+ unsigned nb_count = 0;
+
+ while (speex_bits_remaining(bits) >= 5) {
+ unsigned wb_count = 0;
+ unsigned bit_ptr = bits->bitPtr;
+ unsigned char_ptr = bits->charPtr;
+
+ /* WB frame */
+ while ((speex_bits_remaining(bits) >= 4)
+ && speex_bits_unpack_unsigned(bits, 1))
+ {
+ int advance;
+
+ submode = speex_bits_unpack_unsigned(bits, 3);
+ advance = wb_skip_table[submode];
+ if (advance < 0) {
+ TRACE__((THIS_FUNC, "Invalid mode encountered. "
+ "The stream is corrupted."));
+ return -1;
+ }
+ TRACE__((THIS_FUNC, "WB layer skipped: %d bits", advance));
+ advance -= (SB_SUBMODE_BITS+1);
+ speex_bits_advance(bits, advance);
+
+ bit_ptr = bits->bitPtr;
+ char_ptr = bits->charPtr;
+
+ /* Consecutive subband frames may not exceed 2 frames */
+ if (++wb_count > 2)
+ return -1;
+ }
+
+ /* End of bits, return the frame */
+ if (speex_bits_remaining(bits) < 4) {
+ TRACE__((THIS_FUNC, "End of stream"));
+ return 0;
+ }
+
+ /* Stop iteration, return the frame */
+ if (nb_count > 0) {
+ bits->bitPtr = bit_ptr;
+ bits->charPtr = char_ptr;
+ return 0;
+ }
+
+ /* Get control bits */
+ submode = speex_bits_unpack_unsigned(bits, 4);
+ TRACE__((THIS_FUNC, "Control bits: %d at %d",
+ submode, bits->charPtr*8+bits->bitPtr));
+
+ if (submode == 15) {
+ TRACE__((THIS_FUNC, "Found submode: terminator"));
+ return -1;
+ } else if (submode == 14) {
+ /* in-band signal; next 4 bits contain signal id */
+ submode = speex_bits_unpack_unsigned(bits, 4);
+ TRACE__((THIS_FUNC, "Found submode: in-band %d bits",
+ inband_skip_table[submode]));
+ speex_bits_advance(bits, inband_skip_table[submode]);
+ } else if (submode == 13) {
+ /* user in-band; next 5 bits contain msg len */
+ submode = speex_bits_unpack_unsigned(bits, 5);
+ TRACE__((THIS_FUNC, "Found submode: user-band %d bytes", submode));
+ speex_bits_advance(bits, submode * 8);
+ } else if (submode > 8) {
+ TRACE__((THIS_FUNC, "Unknown sub-mode %d", submode));
+ return -1;
+ } else {
+ /* NB frame */
+ unsigned int advance = submode;
+ speex_mode_query(&speex_nb_mode, SPEEX_SUBMODE_BITS_PER_FRAME, &advance);
+ if (advance < 0) {
+ TRACE__((THIS_FUNC, "Invalid mode encountered. "
+ "The stream is corrupted."));
+ return -1;
+ }
+ TRACE__((THIS_FUNC, "Submode %d: %d bits", submode, advance));
+ advance -= (NB_SUBMODE_BITS+1);
+ speex_bits_advance(bits, advance);
+
+ ++nb_count;
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ * Get frames in the packet.
+ */
+static pj_status_t spx_codec_parse( pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[])
+{
+ struct spx_private *spx = (struct spx_private*) codec->codec_data;
+ unsigned samples_per_frame;
+ unsigned count = 0;
+ int char_ptr = 0;
+ int bit_ptr = 0;
+
+ samples_per_frame=spx_factory.speex_param[spx->param_id].samples_per_frame;
+
+ /* Copy the data into the speex bit-stream */
+ speex_bits_read_from(&spx->dec_bits, (char*)pkt, pkt_size);
+
+ while (speex_get_next_frame(&spx->dec_bits) == 0 &&
+ spx->dec_bits.charPtr != char_ptr)
+ {
+ frames[count].buf = (char*)pkt + char_ptr;
+ /* Bit info contains start bit offset of the frame */
+ frames[count].bit_info = bit_ptr;
+ frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frames[count].timestamp.u64 = ts->u64 + count * samples_per_frame;
+ frames[count].size = spx->dec_bits.charPtr - char_ptr;
+ if (spx->dec_bits.bitPtr)
+ ++frames[count].size;
+
+ bit_ptr = spx->dec_bits.bitPtr;
+ char_ptr = spx->dec_bits.charPtr;
+
+ ++count;
+ }
+
+ *frame_cnt = count;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Encode frames.
+ */
+static pj_status_t spx_codec_encode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ struct spx_private *spx;
+ unsigned samples_per_frame;
+ int tx = 0;
+ spx_int16_t *pcm_in = (spx_int16_t*)input->buf;
+ unsigned nsamples;
+
+ spx = (struct spx_private*) codec->codec_data;
+
+ if (input->type != PJMEDIA_FRAME_TYPE_AUDIO) {
+ output->size = 0;
+ output->buf = NULL;
+ output->timestamp = input->timestamp;
+ output->type = input->type;
+ return PJ_SUCCESS;
+ }
+
+ nsamples = input->size >> 1;
+ samples_per_frame=spx_factory.speex_param[spx->param_id].samples_per_frame;
+
+ PJ_ASSERT_RETURN(nsamples % samples_per_frame == 0,
+ PJMEDIA_CODEC_EPCMFRMINLEN);
+
+ /* Flush all the bits in the struct so we can encode a new frame */
+ speex_bits_reset(&spx->enc_bits);
+
+ /* Encode the frames */
+ while (nsamples >= samples_per_frame) {
+ tx += speex_encode_int(spx->enc, pcm_in, &spx->enc_bits);
+ pcm_in += samples_per_frame;
+ nsamples -= samples_per_frame;
+ }
+
+ /* Check if we need not to transmit the frame (DTX) */
+ if (tx == 0) {
+ output->buf = NULL;
+ output->size = 0;
+ output->timestamp.u64 = input->timestamp.u64;
+ output->type = PJMEDIA_FRAME_TYPE_NONE;
+ return PJ_SUCCESS;
+ }
+
+ /* Check size. */
+ pj_assert(speex_bits_nbytes(&spx->enc_bits) <= (int)output_buf_len);
+
+ /* Copy the bits to an array of char that can be written */
+ output->size = speex_bits_write(&spx->enc_bits,
+ (char*)output->buf, output_buf_len);
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->timestamp = input->timestamp;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Decode frame.
+ */
+static pj_status_t spx_codec_decode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ struct spx_private *spx;
+ unsigned samples_per_frame;
+
+ spx = (struct spx_private*) codec->codec_data;
+ samples_per_frame=spx_factory.speex_param[spx->param_id].samples_per_frame;
+
+ PJ_ASSERT_RETURN(output_buf_len >= samples_per_frame << 1,
+ PJMEDIA_CODEC_EPCMTOOSHORT);
+
+ if (input->type != PJMEDIA_FRAME_TYPE_AUDIO) {
+ pjmedia_zero_samples((pj_int16_t*)output->buf, samples_per_frame);
+ output->size = samples_per_frame << 1;
+ output->timestamp.u64 = input->timestamp.u64;
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ return PJ_SUCCESS;
+ }
+
+ /* Copy the data into the bit-stream struct */
+ speex_bits_read_from(&spx->dec_bits, (char*)input->buf, input->size);
+
+ /* Set Speex dec_bits pointer to the start bit of the frame */
+ speex_bits_advance(&spx->dec_bits, input->bit_info);
+
+ /* Decode the data */
+ speex_decode_int(spx->dec, &spx->dec_bits, (spx_int16_t*)output->buf);
+
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->size = samples_per_frame << 1;
+ output->timestamp.u64 = input->timestamp.u64;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Recover lost frame.
+ */
+static pj_status_t spx_codec_recover(pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ struct spx_private *spx;
+ unsigned count;
+
+ /* output_buf_len is unreferenced when building in Release mode */
+ PJ_UNUSED_ARG(output_buf_len);
+
+ spx = (struct spx_private*) codec->codec_data;
+
+ count = spx_factory.speex_param[spx->param_id].clock_rate * 20 / 1000;
+ pj_assert(count <= output_buf_len/2);
+
+ /* Recover packet loss */
+ speex_decode_int(spx->dec, NULL, (spx_int16_t*) output->buf);
+
+ output->size = count * 2;
+
+ return PJ_SUCCESS;
+}
+
+
+#endif /* PJMEDIA_HAS_SPEEX_CODEC */
diff --git a/pjmedia/src/pjmedia-videodev/avi_dev.c b/pjmedia/src/pjmedia-videodev/avi_dev.c
new file mode 100644
index 0000000..298fb53
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/avi_dev.c
@@ -0,0 +1,678 @@
+/* $Id: avi_dev.c 4086 2012-04-26 02:44:41Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia-videodev/videodev_imp.h>
+#include <pjmedia-videodev/avi_dev.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/rand.h>
+#include <pjmedia/vid_codec.h>
+
+#if defined(PJMEDIA_VIDEO_DEV_HAS_AVI) && PJMEDIA_VIDEO_DEV_HAS_AVI != 0 && \
+ defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+#define THIS_FILE "avi_dev.c"
+#define DRIVER_NAME "AVIDev"
+#define DEFAULT_CLOCK_RATE 90000
+#define DEFAULT_WIDTH 640
+#define DEFAULT_HEIGHT 480
+#define DEFAULT_FPS 25
+
+typedef struct avi_dev_strm avi_dev_strm;
+
+/* avi_ device info */
+struct avi_dev_info
+{
+ pjmedia_vid_dev_info info;
+
+ pj_pool_t *pool;
+ pj_str_t fpath;
+ pj_str_t title;
+ pjmedia_avi_streams *avi;
+ pjmedia_port *vid;
+ avi_dev_strm *strm;
+ pjmedia_vid_codec *codec;
+ pj_uint8_t *enc_buf;
+ pj_size_t enc_buf_size;
+};
+
+/* avi_ factory */
+struct avi_factory
+{
+ pjmedia_vid_dev_factory base;
+ pj_pool_t *pool;
+ pj_pool_factory *pf;
+
+ unsigned dev_count;
+ struct avi_dev_info *dev_info;
+};
+
+/* Video stream. */
+struct avi_dev_strm
+{
+ pjmedia_vid_dev_stream base; /**< Base stream */
+ pjmedia_vid_dev_param param; /**< Settings */
+ pj_pool_t *pool; /**< Memory pool. */
+ struct avi_dev_info *adi;
+
+ pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */
+ void *user_data; /**< Application data. */
+};
+
+
+/* Prototypes */
+static pj_status_t avi_factory_init(pjmedia_vid_dev_factory *f);
+static pj_status_t avi_factory_destroy(pjmedia_vid_dev_factory *f);
+static pj_status_t avi_factory_refresh(pjmedia_vid_dev_factory *f);
+static unsigned avi_factory_get_dev_count(pjmedia_vid_dev_factory *f);
+static pj_status_t avi_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_info *info);
+static pj_status_t avi_factory_default_param(pj_pool_t *pool,
+ pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_param *param);
+static pj_status_t avi_factory_create_stream(
+ pjmedia_vid_dev_factory *f,
+ pjmedia_vid_dev_param *param,
+ const pjmedia_vid_dev_cb *cb,
+ void *user_data,
+ pjmedia_vid_dev_stream **p_vid_strm);
+
+static pj_status_t avi_dev_strm_get_param(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_param *param);
+static pj_status_t avi_dev_strm_get_cap(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ void *value);
+static pj_status_t avi_dev_strm_set_cap(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ const void *value);
+static pj_status_t avi_dev_strm_get_frame(pjmedia_vid_dev_stream *strm,
+ pjmedia_frame *frame);
+static pj_status_t avi_dev_strm_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t avi_dev_strm_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t avi_dev_strm_destroy(pjmedia_vid_dev_stream *strm);
+
+static void reset_dev_info(struct avi_dev_info *adi);
+
+/* Operations */
+static pjmedia_vid_dev_factory_op factory_op =
+{
+ &avi_factory_init,
+ &avi_factory_destroy,
+ &avi_factory_get_dev_count,
+ &avi_factory_get_dev_info,
+ &avi_factory_default_param,
+ &avi_factory_create_stream,
+ &avi_factory_refresh
+};
+
+static pjmedia_vid_dev_stream_op stream_op =
+{
+ &avi_dev_strm_get_param,
+ &avi_dev_strm_get_cap,
+ &avi_dev_strm_set_cap,
+ &avi_dev_strm_start,
+ &avi_dev_strm_get_frame,
+ NULL,
+ &avi_dev_strm_stop,
+ &avi_dev_strm_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+
+/* API */
+PJ_DEF(pj_status_t) pjmedia_avi_dev_create_factory(
+ pj_pool_factory *pf,
+ unsigned max_dev,
+ pjmedia_vid_dev_factory **p_ret)
+{
+ struct avi_factory *cf;
+ pj_pool_t *pool;
+ pj_status_t status;
+
+ pool = pj_pool_create(pf, "avidevfc%p", 512, 512, NULL);
+ cf = PJ_POOL_ZALLOC_T(pool, struct avi_factory);
+ cf->pf = pf;
+ cf->pool = pool;
+ cf->dev_count = max_dev;
+ cf->base.op = &factory_op;
+
+ cf->dev_info = (struct avi_dev_info*)
+ pj_pool_calloc(cf->pool, cf->dev_count,
+ sizeof(struct avi_dev_info));
+
+ if (p_ret) {
+ *p_ret = &cf->base;
+ }
+
+ status = pjmedia_vid_register_factory(NULL, &cf->base);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ PJ_LOG(4, (THIS_FILE, "AVI dev factory created with %d virtual device(s)",
+ cf->dev_count));
+
+ return PJ_SUCCESS;
+}
+
+/* API: init factory */
+static pj_status_t avi_factory_init(pjmedia_vid_dev_factory *f)
+{
+ struct avi_factory *cf = (struct avi_factory*)f;
+ unsigned i;
+
+ for (i=0; i<cf->dev_count; ++i) {
+ reset_dev_info(&cf->dev_info[i]);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t avi_factory_destroy(pjmedia_vid_dev_factory *f)
+{
+ struct avi_factory *cf = (struct avi_factory*)f;
+ pj_pool_t *pool = cf->pool;
+
+ cf->pool = NULL;
+ pj_pool_release(pool);
+
+ return PJ_SUCCESS;
+}
+
+/* API: refresh the list of devices */
+static pj_status_t avi_factory_refresh(pjmedia_vid_dev_factory *f)
+{
+ PJ_UNUSED_ARG(f);
+ return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned avi_factory_get_dev_count(pjmedia_vid_dev_factory *f)
+{
+ struct avi_factory *cf = (struct avi_factory*)f;
+ return cf->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t avi_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_info *info)
+{
+ struct avi_factory *cf = (struct avi_factory*)f;
+
+ PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV);
+
+ pj_memcpy(info, &cf->dev_info[index].info, sizeof(*info));
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t avi_factory_default_param(pj_pool_t *pool,
+ pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_param *param)
+{
+ struct avi_factory *cf = (struct avi_factory*)f;
+ struct avi_dev_info *di = &cf->dev_info[index];
+
+ PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV);
+
+ PJ_UNUSED_ARG(pool);
+
+ pj_bzero(param, sizeof(*param));
+ param->dir = PJMEDIA_DIR_CAPTURE;
+ param->cap_id = index;
+ param->rend_id = PJMEDIA_VID_INVALID_DEV;
+ param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
+ param->clock_rate = DEFAULT_CLOCK_RATE;
+ pj_memcpy(&param->fmt, &di->info.fmt[0], sizeof(param->fmt));
+
+ return PJ_SUCCESS;
+}
+
+/* reset dev info */
+static void reset_dev_info(struct avi_dev_info *adi)
+{
+ /* Close avi streams */
+ if (adi->avi) {
+ unsigned i, cnt;
+
+ cnt = pjmedia_avi_streams_get_num_streams(adi->avi);
+ for (i=0; i<cnt; ++i) {
+ pjmedia_avi_stream *as;
+
+ as = pjmedia_avi_streams_get_stream(adi->avi, i);
+ if (as) {
+ pjmedia_port *port;
+ port = pjmedia_avi_stream_get_port(as);
+ pjmedia_port_destroy(port);
+ }
+ }
+ adi->avi = NULL;
+ }
+
+ if (adi->codec) {
+ pjmedia_vid_codec_close(adi->codec);
+ adi->codec = NULL;
+ }
+
+ if (adi->pool)
+ pj_pool_release(adi->pool);
+
+ pj_bzero(adi, sizeof(*adi));
+
+ /* Fill up with *dummy" device info */
+ pj_ansi_strncpy(adi->info.name, "AVI Player", sizeof(adi->info.name)-1);
+ pj_ansi_strncpy(adi->info.driver, DRIVER_NAME, sizeof(adi->info.driver)-1);
+ adi->info.dir = PJMEDIA_DIR_CAPTURE;
+ adi->info.has_callback = PJ_FALSE;
+}
+
+/* API: release resources */
+PJ_DEF(pj_status_t) pjmedia_avi_dev_free(pjmedia_vid_dev_index id)
+{
+ pjmedia_vid_dev_factory *f;
+ struct avi_factory *cf;
+ unsigned local_idx;
+ struct avi_dev_info *adi;
+ pj_status_t status;
+
+ /* Lookup the factory and local device index */
+ status = pjmedia_vid_dev_get_local_index(id, &f, &local_idx);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* The factory must be AVI factory */
+ PJ_ASSERT_RETURN(f->op->init == &avi_factory_init, PJMEDIA_EVID_INVDEV);
+ cf = (struct avi_factory*)f;
+
+ /* Device index should be valid */
+ PJ_ASSERT_RETURN(local_idx <= cf->dev_count, PJ_EBUG);
+ adi = &cf->dev_info[local_idx];
+
+ /* Cannot configure if stream is running */
+ if (adi->strm)
+ return PJ_EBUSY;
+
+ /* Reset */
+ reset_dev_info(adi);
+ return PJ_SUCCESS;
+}
+
+/* API: get param */
+PJ_DEF(pj_status_t) pjmedia_avi_dev_get_param(pjmedia_vid_dev_index id,
+ pjmedia_avi_dev_param *prm)
+{
+ pjmedia_vid_dev_factory *f;
+ struct avi_factory *cf;
+ unsigned local_idx;
+ struct avi_dev_info *adi;
+ pj_status_t status;
+
+ /* Lookup the factory and local device index */
+ status = pjmedia_vid_dev_get_local_index(id, &f, &local_idx);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* The factory must be factory */
+ PJ_ASSERT_RETURN(f->op->init == &avi_factory_init, PJMEDIA_EVID_INVDEV);
+ cf = (struct avi_factory*)f;
+
+ /* Device index should be valid */
+ PJ_ASSERT_RETURN(local_idx <= cf->dev_count, PJ_EBUG);
+ adi = &cf->dev_info[local_idx];
+
+ pj_bzero(prm, sizeof(*prm));
+ prm->path = adi->fpath;
+ prm->title = adi->title;
+ prm->avi_streams = adi->avi;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(void) pjmedia_avi_dev_param_default(pjmedia_avi_dev_param *p)
+{
+ pj_bzero(p, sizeof(*p));
+}
+
+/* API: configure the AVI */
+PJ_DEF(pj_status_t) pjmedia_avi_dev_alloc( pjmedia_vid_dev_factory *f,
+ pjmedia_avi_dev_param *p,
+ pjmedia_vid_dev_index *p_id)
+{
+ pjmedia_vid_dev_index id;
+ struct avi_factory *cf = (struct avi_factory*)f;
+ unsigned local_idx;
+ struct avi_dev_info *adi = NULL;
+ pjmedia_format avi_fmt;
+ const pjmedia_video_format_info *vfi;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(f && p && p_id, PJ_EINVAL);
+
+ if (p_id)
+ *p_id = PJMEDIA_VID_INVALID_DEV;
+
+ /* Get a free dev */
+ for (local_idx=0; local_idx<cf->dev_count; ++local_idx) {
+ if (cf->dev_info[local_idx].avi == NULL) {
+ adi = &cf->dev_info[local_idx];
+ break;
+ }
+ }
+
+ if (!adi)
+ return PJ_ETOOMANY;
+
+ /* Convert local ID to global id */
+ status = pjmedia_vid_dev_get_global_index(&cf->base, local_idx, &id);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Reset */
+ if (adi->pool) {
+ pj_pool_release(adi->pool);
+ }
+ pj_bzero(adi, sizeof(*adi));
+
+ /* Reinit */
+ PJ_ASSERT_RETURN(p->path.slen, PJ_EINVAL);
+ adi->pool = pj_pool_create(cf->pf, "avidi%p", 512, 512, NULL);
+
+
+ /* Open the AVI */
+ pj_strdup_with_null(adi->pool, &adi->fpath, &p->path);
+ status = pjmedia_avi_player_create_streams(adi->pool, adi->fpath.ptr, 0,
+ &adi->avi);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ adi->vid = pjmedia_avi_streams_get_stream_by_media(adi->avi, 0,
+ PJMEDIA_TYPE_VIDEO);
+ if (!adi->vid) {
+ status = PJMEDIA_EVID_BADFORMAT;
+ PJ_LOG(4,(THIS_FILE, "Error: cannot find video in AVI %s",
+ adi->fpath.ptr));
+ goto on_error;
+ }
+
+ pjmedia_format_copy(&avi_fmt, &adi->vid->info.fmt);
+ vfi = pjmedia_get_video_format_info(NULL, avi_fmt.id);
+ /* Check whether the frame is encoded. */
+ if (!vfi || vfi->bpp == 0) {
+ /* Yes, prepare codec */
+ const pjmedia_vid_codec_info *codec_info;
+ pjmedia_vid_codec_param codec_param;
+ pjmedia_video_apply_fmt_param vafp;
+
+ /* Lookup codec */
+ status = pjmedia_vid_codec_mgr_get_codec_info2(NULL,
+ avi_fmt.id,
+ &codec_info);
+ if (status != PJ_SUCCESS || !codec_info)
+ goto on_error;
+
+ status = pjmedia_vid_codec_mgr_get_default_param(NULL, codec_info,
+ &codec_param);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Open codec */
+ status = pjmedia_vid_codec_mgr_alloc_codec(NULL, codec_info,
+ &adi->codec);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pjmedia_vid_codec_init(adi->codec, adi->pool);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ codec_param.dir = PJMEDIA_DIR_DECODING;
+ codec_param.packing = PJMEDIA_VID_PACKING_WHOLE;
+ status = pjmedia_vid_codec_open(adi->codec, &codec_param);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Allocate buffer */
+ avi_fmt.id = codec_info->dec_fmt_id[0];
+ vfi = pjmedia_get_video_format_info(NULL, avi_fmt.id);
+ pj_bzero(&vafp, sizeof(vafp));
+ vafp.size = avi_fmt.det.vid.size;
+ status = vfi->apply_fmt(vfi, &vafp);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ adi->enc_buf = pj_pool_alloc(adi->pool, vafp.framebytes);
+ adi->enc_buf_size = vafp.framebytes;
+ }
+
+ /* Calculate title */
+ if (p->title.slen) {
+ pj_strdup_with_null(adi->pool, &adi->title, &p->title);
+ } else {
+ char *start = p->path.ptr + p->path.slen;
+ pj_str_t tmp;
+
+ while (start >= p->path.ptr) {
+ if (*start == '/' || *start == '\\')
+ break;
+ --start;
+ }
+ tmp.ptr = start + 1;
+ tmp.slen = p->path.ptr + p->path.slen - tmp.ptr;
+ pj_strdup_with_null(adi->pool, &adi->title, &tmp);
+ }
+
+ /* Init device info */
+ pj_ansi_strncpy(adi->info.name, adi->title.ptr, sizeof(adi->info.name)-1);
+ pj_ansi_strncpy(adi->info.driver, DRIVER_NAME, sizeof(adi->info.driver)-1);
+ adi->info.dir = PJMEDIA_DIR_CAPTURE;
+ adi->info.has_callback = PJ_FALSE;
+
+ adi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT;
+ adi->info.fmt_cnt = 1;
+ pjmedia_format_copy(&adi->info.fmt[0], &avi_fmt);
+
+ /* Set out vars */
+ if (p_id)
+ *p_id = id;
+ p->avi_streams = adi->avi;
+ if (p->title.slen == 0)
+ p->title = adi->title;
+
+ return PJ_SUCCESS;
+
+on_error:
+ if (adi->codec) {
+ pjmedia_vid_codec_close(adi->codec);
+ adi->codec = NULL;
+ }
+ if (adi->pool) {
+ pj_pool_release(adi->pool);
+ adi->pool = NULL;
+ }
+ pjmedia_avi_dev_free(id);
+ return status;
+}
+
+
+/* API: create stream */
+static pj_status_t avi_factory_create_stream(
+ pjmedia_vid_dev_factory *f,
+ pjmedia_vid_dev_param *param,
+ const pjmedia_vid_dev_cb *cb,
+ void *user_data,
+ pjmedia_vid_dev_stream **p_vid_strm)
+{
+ struct avi_factory *cf = (struct avi_factory*)f;
+ pj_pool_t *pool = NULL;
+ struct avi_dev_info *adi;
+ struct avi_dev_strm *strm;
+
+ PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL);
+ PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO &&
+ param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO &&
+ param->dir == PJMEDIA_DIR_CAPTURE,
+ PJ_EINVAL);
+
+ /* Device must have been configured with pjmedia_avi_dev_set_param() */
+ adi = &cf->dev_info[param->cap_id];
+ PJ_ASSERT_RETURN(adi->avi != NULL, PJ_EINVALIDOP);
+
+ /* Cannot create while stream is already active */
+ PJ_ASSERT_RETURN(adi->strm==NULL, PJ_EINVALIDOP);
+
+ /* Create and initialize basic stream descriptor */
+ pool = pj_pool_create(cf->pf, "avidev%p", 512, 512, NULL);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ strm = PJ_POOL_ZALLOC_T(pool, struct avi_dev_strm);
+ pj_memcpy(&strm->param, param, sizeof(*param));
+ strm->pool = pool;
+ pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
+ strm->user_data = user_data;
+ strm->adi = adi;
+
+ pjmedia_format_copy(&param->fmt, &adi->info.fmt[0]);
+
+ /* Done */
+ strm->base.op = &stream_op;
+ adi->strm = strm;
+ *p_vid_strm = &strm->base;
+
+ return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t avi_dev_strm_get_param(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_param *pi)
+{
+ struct avi_dev_strm *strm = (struct avi_dev_strm*)s;
+
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t avi_dev_strm_get_cap(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_cap cap,
+ void *pval)
+{
+ struct avi_dev_strm *strm = (struct avi_dev_strm*)s;
+
+ PJ_UNUSED_ARG(strm);
+ PJ_UNUSED_ARG(cap);
+ PJ_UNUSED_ARG(pval);
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ return PJMEDIA_EVID_INVCAP;
+}
+
+/* API: set capability */
+static pj_status_t avi_dev_strm_set_cap(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_cap cap,
+ const void *pval)
+{
+ struct avi_dev_strm *strm = (struct avi_dev_strm*)s;
+
+ PJ_UNUSED_ARG(strm);
+ PJ_UNUSED_ARG(cap);
+ PJ_UNUSED_ARG(pval);
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ return PJMEDIA_EVID_INVCAP;
+}
+
+/* API: Get frame from stream */
+static pj_status_t avi_dev_strm_get_frame(pjmedia_vid_dev_stream *strm,
+ pjmedia_frame *frame)
+{
+ struct avi_dev_strm *stream = (struct avi_dev_strm*)strm;
+
+ if (stream->adi->codec) {
+ pjmedia_frame enc_frame;
+ pj_status_t status;
+
+ enc_frame.buf = stream->adi->enc_buf;
+ enc_frame.size = stream->adi->enc_buf_size;
+ status = pjmedia_port_get_frame(stream->adi->vid, &enc_frame);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ return pjmedia_vid_codec_decode(stream->adi->codec, 1, &enc_frame,
+ frame->size, frame);
+ } else {
+ return pjmedia_port_get_frame(stream->adi->vid, frame);
+ }
+}
+
+/* API: Start stream. */
+static pj_status_t avi_dev_strm_start(pjmedia_vid_dev_stream *strm)
+{
+ struct avi_dev_strm *stream = (struct avi_dev_strm*)strm;
+
+ PJ_UNUSED_ARG(stream);
+
+ PJ_LOG(4, (THIS_FILE, "Starting avi video stream"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t avi_dev_strm_stop(pjmedia_vid_dev_stream *strm)
+{
+ struct avi_dev_strm *stream = (struct avi_dev_strm*)strm;
+
+ PJ_UNUSED_ARG(stream);
+
+ PJ_LOG(4, (THIS_FILE, "Stopping avi video stream"));
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t avi_dev_strm_destroy(pjmedia_vid_dev_stream *strm)
+{
+ struct avi_dev_strm *stream = (struct avi_dev_strm*)strm;
+
+ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+ avi_dev_strm_stop(strm);
+
+ stream->adi->strm = NULL;
+ stream->adi = NULL;
+ pj_pool_release(stream->pool);
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_VIDEO_DEV_HAS_AVI */
diff --git a/pjmedia/src/pjmedia-videodev/colorbar_dev.c b/pjmedia/src/pjmedia-videodev/colorbar_dev.c
new file mode 100644
index 0000000..a3bb4eb
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/colorbar_dev.c
@@ -0,0 +1,631 @@
+/* $Id: colorbar_dev.c 4158 2012-06-06 09:56:14Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia-videodev/videodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/rand.h>
+
+
+#if defined(PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC) && \
+ PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC != 0
+
+
+#define THIS_FILE "colorbar_dev.c"
+#define DEFAULT_CLOCK_RATE 90000
+#define DEFAULT_WIDTH 352 //640
+#define DEFAULT_HEIGHT 288 //480
+#define DEFAULT_FPS 25
+
+/* cbar_ device info */
+struct cbar_dev_info
+{
+ pjmedia_vid_dev_info info;
+};
+
+/* cbar_ factory */
+struct cbar_factory
+{
+ pjmedia_vid_dev_factory base;
+ pj_pool_t *pool;
+ pj_pool_factory *pf;
+
+ unsigned dev_count;
+ struct cbar_dev_info *dev_info;
+};
+
+struct cbar_fmt_info {
+ pjmedia_format_id fmt_id; /* Format ID */
+
+ /* Info for packed formats. */
+ unsigned c_offset[3]; /* Color component offset,
+ in bytes */
+ unsigned c_stride[3]; /* Color component stride,
+ or distance between two
+ consecutive same color
+ components, in bytes */
+};
+
+/* Colorbar video source supports */
+static struct cbar_fmt_info cbar_fmts[] =
+{
+ /* Packed formats */
+ { PJMEDIA_FORMAT_YUY2, {0, 1, 3}, {2, 4, 4} },
+ { PJMEDIA_FORMAT_UYVY, {1, 0, 2}, {2, 4, 4} },
+ { PJMEDIA_FORMAT_YVYU, {0, 3, 1}, {2, 4, 4} },
+ { PJMEDIA_FORMAT_RGBA, {0, 1, 2}, {4, 4, 4} },
+ { PJMEDIA_FORMAT_RGB24, {0, 1, 2}, {3, 3, 3} },
+ { PJMEDIA_FORMAT_BGRA, {2, 1, 0}, {4, 4, 4} },
+
+ /* Planar formats */
+ { PJMEDIA_FORMAT_YV12 },
+ { PJMEDIA_FORMAT_I420 },
+ { PJMEDIA_FORMAT_I422 },
+ { PJMEDIA_FORMAT_I420JPEG },
+ { PJMEDIA_FORMAT_I422JPEG },
+};
+
+/* Video stream. */
+struct cbar_stream
+{
+ pjmedia_vid_dev_stream base; /**< Base stream */
+ pjmedia_vid_dev_param param; /**< Settings */
+ pj_pool_t *pool; /**< Memory pool. */
+
+ pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */
+ void *user_data; /**< Application data. */
+
+ const struct cbar_fmt_info *cbfi;
+ const pjmedia_video_format_info *vfi;
+ pjmedia_video_apply_fmt_param vafp;
+ pj_uint8_t *first_line[PJMEDIA_MAX_VIDEO_PLANES];
+ pj_timestamp ts;
+ unsigned ts_inc;
+};
+
+
+/* Prototypes */
+static pj_status_t cbar_factory_init(pjmedia_vid_dev_factory *f);
+static pj_status_t cbar_factory_destroy(pjmedia_vid_dev_factory *f);
+static pj_status_t cbar_factory_refresh(pjmedia_vid_dev_factory *f);
+static unsigned cbar_factory_get_dev_count(pjmedia_vid_dev_factory *f);
+static pj_status_t cbar_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_info *info);
+static pj_status_t cbar_factory_default_param(pj_pool_t *pool,
+ pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_param *param);
+static pj_status_t cbar_factory_create_stream(
+ pjmedia_vid_dev_factory *f,
+ pjmedia_vid_dev_param *param,
+ const pjmedia_vid_dev_cb *cb,
+ void *user_data,
+ pjmedia_vid_dev_stream **p_vid_strm);
+
+static pj_status_t cbar_stream_get_param(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_param *param);
+static pj_status_t cbar_stream_get_cap(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ void *value);
+static pj_status_t cbar_stream_set_cap(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ const void *value);
+static pj_status_t cbar_stream_get_frame(pjmedia_vid_dev_stream *strm,
+ pjmedia_frame *frame);
+static pj_status_t cbar_stream_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t cbar_stream_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t cbar_stream_destroy(pjmedia_vid_dev_stream *strm);
+
+/* Operations */
+static pjmedia_vid_dev_factory_op factory_op =
+{
+ &cbar_factory_init,
+ &cbar_factory_destroy,
+ &cbar_factory_get_dev_count,
+ &cbar_factory_get_dev_info,
+ &cbar_factory_default_param,
+ &cbar_factory_create_stream,
+ &cbar_factory_refresh
+};
+
+static pjmedia_vid_dev_stream_op stream_op =
+{
+ &cbar_stream_get_param,
+ &cbar_stream_get_cap,
+ &cbar_stream_set_cap,
+ &cbar_stream_start,
+ &cbar_stream_get_frame,
+ NULL,
+ &cbar_stream_stop,
+ &cbar_stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init cbar_ video driver.
+ */
+pjmedia_vid_dev_factory* pjmedia_cbar_factory(pj_pool_factory *pf)
+{
+ struct cbar_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "cbar video", 512, 512, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct cbar_factory);
+ f->pf = pf;
+ f->pool = pool;
+ f->base.op = &factory_op;
+
+ return &f->base;
+}
+
+
+/* API: init factory */
+static pj_status_t cbar_factory_init(pjmedia_vid_dev_factory *f)
+{
+ struct cbar_factory *cf = (struct cbar_factory*)f;
+ struct cbar_dev_info *ddi;
+ unsigned i;
+
+ cf->dev_count = 1;
+ cf->dev_info = (struct cbar_dev_info*)
+ pj_pool_calloc(cf->pool, cf->dev_count,
+ sizeof(struct cbar_dev_info));
+
+ ddi = &cf->dev_info[0];
+ pj_bzero(ddi, sizeof(*ddi));
+ pj_ansi_strncpy(ddi->info.name, "Colorbar generator",
+ sizeof(ddi->info.name));
+ ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0';
+ pj_ansi_strncpy(ddi->info.driver, "Colorbar", sizeof(ddi->info.driver));
+ ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0';
+ ddi->info.dir = PJMEDIA_DIR_CAPTURE;
+ ddi->info.has_callback = PJ_FALSE;
+
+ ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT;
+ ddi->info.fmt_cnt = sizeof(cbar_fmts)/sizeof(cbar_fmts[0]);
+ for (i = 0; i < ddi->info.fmt_cnt; i++) {
+ pjmedia_format *fmt = &ddi->info.fmt[i];
+ pjmedia_format_init_video(fmt, cbar_fmts[i].fmt_id,
+ DEFAULT_WIDTH, DEFAULT_HEIGHT,
+ DEFAULT_FPS, 1);
+ }
+
+ PJ_LOG(4, (THIS_FILE, "Colorbar video src initialized with %d device(s):",
+ cf->dev_count));
+ for (i = 0; i < cf->dev_count; i++) {
+ PJ_LOG(4, (THIS_FILE, "%2d: %s", i, cf->dev_info[i].info.name));
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t cbar_factory_destroy(pjmedia_vid_dev_factory *f)
+{
+ struct cbar_factory *cf = (struct cbar_factory*)f;
+ pj_pool_t *pool = cf->pool;
+
+ cf->pool = NULL;
+ pj_pool_release(pool);
+
+ return PJ_SUCCESS;
+}
+
+/* API: refresh the list of devices */
+static pj_status_t cbar_factory_refresh(pjmedia_vid_dev_factory *f)
+{
+ PJ_UNUSED_ARG(f);
+ return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned cbar_factory_get_dev_count(pjmedia_vid_dev_factory *f)
+{
+ struct cbar_factory *cf = (struct cbar_factory*)f;
+ return cf->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t cbar_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_info *info)
+{
+ struct cbar_factory *cf = (struct cbar_factory*)f;
+
+ PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV);
+
+ pj_memcpy(info, &cf->dev_info[index].info, sizeof(*info));
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t cbar_factory_default_param(pj_pool_t *pool,
+ pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_param *param)
+{
+ struct cbar_factory *cf = (struct cbar_factory*)f;
+ struct cbar_dev_info *di = &cf->dev_info[index];
+
+ PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV);
+
+ PJ_UNUSED_ARG(pool);
+
+ pj_bzero(param, sizeof(*param));
+ param->dir = PJMEDIA_DIR_CAPTURE;
+ param->cap_id = index;
+ param->rend_id = PJMEDIA_VID_INVALID_DEV;
+ param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
+ param->clock_rate = DEFAULT_CLOCK_RATE;
+ pj_memcpy(&param->fmt, &di->info.fmt[0], sizeof(param->fmt));
+
+ return PJ_SUCCESS;
+}
+
+static const struct cbar_fmt_info* get_cbar_fmt_info(pjmedia_format_id id)
+{
+ unsigned i;
+
+ for (i = 0; i < sizeof(cbar_fmts)/sizeof(cbar_fmts[0]); i++) {
+ if (cbar_fmts[i].fmt_id == id)
+ return &cbar_fmts[i];
+ }
+
+ return NULL;
+}
+
+static void fill_first_line(pj_uint8_t *first_lines[],
+ const struct cbar_fmt_info *cbfi,
+ const pjmedia_video_format_info *vfi,
+ const pjmedia_video_apply_fmt_param *vafp)
+{
+ typedef pj_uint8_t color_comp_t[3];
+ color_comp_t rgb_colors[] =
+ {
+ {255,255,255}, {255,255,0}, {0,255,255}, {0,255,0},
+ {255,0,255}, {255,0,0}, {0,0,255}, {0,0,0}
+ };
+ color_comp_t yuv_colors[] =
+ {
+ //{235,128,128}, {162,44,142}, {131,156,44}, {112,72,58},
+ //{84,184,198}, {65,100,212}, {35,212,114}, {16,128,128}
+ {235,128,128}, {210,16,146}, {170,166,16}, {145,54,34},
+ {106,202,222}, {81,90,240}, {41,240,110}, {16,128,128}
+ };
+
+ unsigned i, j, k;
+
+ if (vfi->plane_cnt == 1) {
+ /* Packed */
+
+ for (i = 0; i < 8; ++i) {
+ /* iterate bars */
+ for (j = 0; j < 3; ++j) {
+ /* iterate color components */
+ pj_uint8_t *p = NULL, c;
+ unsigned bar_width, inc_p;
+
+ if (vfi->color_model == PJMEDIA_COLOR_MODEL_RGB)
+ c = rgb_colors[i][j];
+ else
+ c = yuv_colors[i][j];
+
+ bar_width = vafp->size.w/8;
+ bar_width /= (cbfi->c_stride[j] * 8 / vfi->bpp);
+ inc_p = cbfi->c_stride[j];
+ p = first_lines[0] + bar_width*i*inc_p + cbfi->c_offset[j];
+
+ /* draw this color */
+ for (k = 0; k < bar_width; ++k) {
+ *p = c;
+ p += inc_p;
+ }
+ }
+ }
+
+ } else if (vfi->plane_cnt == 3) {
+
+ for (i = 0; i < 8; ++i) {
+ /* iterate bars */
+ for (j = 0; j < 3; ++j) {
+ /* iterate planes/color components */
+ pj_uint8_t *p = NULL, c;
+ unsigned bar_width;
+
+ if (vfi->color_model == PJMEDIA_COLOR_MODEL_RGB)
+ c = rgb_colors[i][j];
+ else {
+ if (vfi->id == PJMEDIA_FORMAT_YV12 && j > 0)
+ c = yuv_colors[i][3-j];
+ else
+ c = yuv_colors[i][j];
+ }
+
+ bar_width = vafp->strides[j]/8;
+ p = first_lines[j] + bar_width*i;
+
+ /* draw this plane/color */
+ for (k = 0; k < bar_width; ++k)
+ *p++ = c;
+ }
+ }
+ }
+}
+
+/* API: create stream */
+static pj_status_t cbar_factory_create_stream(
+ pjmedia_vid_dev_factory *f,
+ pjmedia_vid_dev_param *param,
+ const pjmedia_vid_dev_cb *cb,
+ void *user_data,
+ pjmedia_vid_dev_stream **p_vid_strm)
+{
+ struct cbar_factory *cf = (struct cbar_factory*)f;
+ pj_pool_t *pool;
+ struct cbar_stream *strm;
+ const pjmedia_video_format_detail *vfd;
+ const pjmedia_video_format_info *vfi;
+ pjmedia_video_apply_fmt_param vafp;
+ const struct cbar_fmt_info *cbfi;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL);
+ PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO &&
+ param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO &&
+ param->dir == PJMEDIA_DIR_CAPTURE,
+ PJ_EINVAL);
+
+ pj_bzero(&vafp, sizeof(vafp));
+
+ vfd = pjmedia_format_get_video_format_detail(&param->fmt, PJ_TRUE);
+ vfi = pjmedia_get_video_format_info(NULL, param->fmt.id);
+ cbfi = get_cbar_fmt_info(param->fmt.id);
+ if (!vfi || !cbfi)
+ return PJMEDIA_EVID_BADFORMAT;
+
+ vafp.size = param->fmt.det.vid.size;
+ if (vfi->apply_fmt(vfi, &vafp) != PJ_SUCCESS)
+ return PJMEDIA_EVID_BADFORMAT;
+
+ /* Create and Initialize stream descriptor */
+ pool = pj_pool_create(cf->pf, "cbar-dev", 512, 512, NULL);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ strm = PJ_POOL_ZALLOC_T(pool, struct cbar_stream);
+ pj_memcpy(&strm->param, param, sizeof(*param));
+ strm->pool = pool;
+ pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
+ strm->user_data = user_data;
+ strm->vfi = vfi;
+ strm->cbfi = cbfi;
+ pj_memcpy(&strm->vafp, &vafp, sizeof(vafp));
+ strm->ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1);
+
+ for (i = 0; i < vfi->plane_cnt; ++i) {
+ strm->first_line[i] = pj_pool_alloc(pool, vafp.strides[i]);
+ pj_memset(strm->first_line[i], 255, vafp.strides[i]);
+ }
+
+ fill_first_line(strm->first_line, strm->cbfi, vfi, &strm->vafp);
+
+ /* Apply the remaining settings */
+/* if (param->flags & PJMEDIA_VID_DEV_CAP_INPUT_SCALE) {
+ cbar_stream_set_cap(&strm->base,
+ PJMEDIA_VID_DEV_CAP_INPUT_SCALE,
+ &param->fmt);
+ }
+*/
+ /* Done */
+ strm->base.op = &stream_op;
+ *p_vid_strm = &strm->base;
+
+ return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t cbar_stream_get_param(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_param *pi)
+{
+ struct cbar_stream *strm = (struct cbar_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+/* if (cbar_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_INPUT_SCALE,
+ &pi->fmt.info_size) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_VID_DEV_CAP_INPUT_SCALE;
+ }
+*/
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t cbar_stream_get_cap(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_cap cap,
+ void *pval)
+{
+ struct cbar_stream *strm = (struct cbar_stream*)s;
+
+ PJ_UNUSED_ARG(strm);
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE)
+ {
+ return PJMEDIA_EVID_INVCAP;
+// return PJ_SUCCESS;
+ } else {
+ return PJMEDIA_EVID_INVCAP;
+ }
+}
+
+/* API: set capability */
+static pj_status_t cbar_stream_set_cap(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_cap cap,
+ const void *pval)
+{
+ struct cbar_stream *strm = (struct cbar_stream*)s;
+
+ PJ_UNUSED_ARG(strm);
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE)
+ {
+ return PJ_SUCCESS;
+ }
+
+ return PJMEDIA_EVID_INVCAP;
+}
+
+static pj_status_t spectrum_run(struct cbar_stream *d, pj_uint8_t *p,
+ pj_size_t size)
+{
+ unsigned i;
+ pj_uint8_t *ptr = p;
+ pj_time_val tv;
+
+ PJ_UNUSED_ARG(size);
+
+ /* Subsequent lines */
+ for (i=0; i<d->vfi->plane_cnt; ++i) {
+ pj_uint8_t *plane_end;
+
+ plane_end = ptr + d->vafp.plane_bytes[i];
+ while (ptr < plane_end) {
+ pj_memcpy(ptr, d->first_line[i], d->vafp.strides[i]);
+ ptr += d->vafp.strides[i];
+ }
+ }
+
+ /* blinking dot */
+ pj_gettimeofday(&tv);
+ if (tv.msec < 660) {
+ enum { DOT_SIZE = 8 };
+ pj_uint8_t dot_clr_rgb[3] = {255, 255, 255};
+ pj_uint8_t dot_clr_yuv[3] = {235, 128, 128};
+
+ if (d->vfi->plane_cnt == 1) {
+ for (i = 0; i < 3; ++i) {
+ pj_uint8_t *ptr;
+ unsigned j, k, inc_ptr;
+ pj_size_t dot_size = DOT_SIZE;
+
+ dot_size /= (d->cbfi->c_stride[i] * 8 / d->vfi->bpp);
+ inc_ptr = d->cbfi->c_stride[i];
+ for (j = 0; j < dot_size; ++j) {
+ ptr = p + d->vafp.strides[0]*(dot_size+j+1) -
+ 2*dot_size*inc_ptr + d->cbfi->c_offset[i];
+ for (k = 0; k < dot_size; ++k) {
+ if (d->vfi->color_model == PJMEDIA_COLOR_MODEL_RGB)
+ *ptr = dot_clr_rgb[i];
+ else
+ *ptr = dot_clr_yuv[i];
+ ptr += inc_ptr;
+ }
+ }
+ }
+ } else {
+ pj_size_t offset_p = 0;
+
+ for (i = 0; i < 3; ++i) {
+ pj_uint8_t *ptr, c;
+ unsigned j;
+ pj_size_t dot_size = DOT_SIZE;
+
+ if (d->vfi->color_model == PJMEDIA_COLOR_MODEL_RGB)
+ c = dot_clr_rgb[i];
+ else
+ c = dot_clr_yuv[i];
+
+ dot_size /= (d->vafp.size.w / d->vafp.strides[i]);
+ ptr = p + offset_p + d->vafp.strides[i]*(dot_size+1) -
+ 2*dot_size;
+ for (j = 0; j < dot_size; ++j) {
+ pj_memset(ptr, c, dot_size);
+ ptr += d->vafp.strides[i];
+ }
+ offset_p += d->vafp.plane_bytes[i];
+ }
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: Get frame from stream */
+static pj_status_t cbar_stream_get_frame(pjmedia_vid_dev_stream *strm,
+ pjmedia_frame *frame)
+{
+ struct cbar_stream *stream = (struct cbar_stream*)strm;
+
+ frame->type = PJMEDIA_FRAME_TYPE_VIDEO;
+ frame->bit_info = 0;
+ frame->timestamp = stream->ts;
+ stream->ts.u64 += stream->ts_inc;
+ return spectrum_run(stream, frame->buf, frame->size);
+}
+
+/* API: Start stream. */
+static pj_status_t cbar_stream_start(pjmedia_vid_dev_stream *strm)
+{
+ struct cbar_stream *stream = (struct cbar_stream*)strm;
+
+ PJ_UNUSED_ARG(stream);
+
+ PJ_LOG(4, (THIS_FILE, "Starting cbar video stream"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t cbar_stream_stop(pjmedia_vid_dev_stream *strm)
+{
+ struct cbar_stream *stream = (struct cbar_stream*)strm;
+
+ PJ_UNUSED_ARG(stream);
+
+ PJ_LOG(4, (THIS_FILE, "Stopping cbar video stream"));
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t cbar_stream_destroy(pjmedia_vid_dev_stream *strm)
+{
+ struct cbar_stream *stream = (struct cbar_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+ cbar_stream_stop(strm);
+
+ pj_pool_release(stream->pool);
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC */
diff --git a/pjmedia/src/pjmedia-videodev/dshow_dev.c b/pjmedia/src/pjmedia-videodev/dshow_dev.c
new file mode 100644
index 0000000..6fb7c0b
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/dshow_dev.c
@@ -0,0 +1,1062 @@
+/* $Id: dshow_dev.c 3953 2012-02-16 08:49:33Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia-videodev/videodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/unicode.h>
+
+
+#if defined(PJMEDIA_VIDEO_DEV_HAS_DSHOW) && PJMEDIA_VIDEO_DEV_HAS_DSHOW != 0
+
+
+#ifdef _MSC_VER
+# pragma warning(push, 3)
+#endif
+
+#include <windows.h>
+#define COBJMACROS
+#include <DShow.h>
+
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+
+#pragma comment(lib, "Strmiids.lib")
+#pragma comment(lib, "Rpcrt4.lib")
+#pragma comment(lib, "Quartz.lib")
+
+#define THIS_FILE "dshow_dev.c"
+#define DEFAULT_CLOCK_RATE 90000
+#define DEFAULT_WIDTH 640
+#define DEFAULT_HEIGHT 480
+#define DEFAULT_FPS 25
+
+/* Temporarily disable DirectShow renderer (VMR) */
+#define HAS_VMR 0
+
+typedef void (*input_callback)(void *user_data, IMediaSample *pMediaSample);
+typedef struct NullRenderer NullRenderer;
+IBaseFilter* NullRenderer_Create(input_callback input_cb,
+ void *user_data);
+typedef struct SourceFilter SourceFilter;
+IBaseFilter* SourceFilter_Create(SourceFilter **pSrc);
+HRESULT SourceFilter_Deliver(SourceFilter *src, void *buf, long size);
+void SourceFilter_SetMediaType(SourceFilter *src, AM_MEDIA_TYPE *pmt);
+
+typedef struct dshow_fmt_info
+{
+ pjmedia_format_id pjmedia_format;
+ const GUID *dshow_format;
+} dshow_fmt_info;
+
+static dshow_fmt_info dshow_fmts[] =
+{
+ {PJMEDIA_FORMAT_YUY2, &MEDIASUBTYPE_YUY2} ,
+ {PJMEDIA_FORMAT_RGB24, &MEDIASUBTYPE_RGB24} ,
+ {PJMEDIA_FORMAT_RGB32, &MEDIASUBTYPE_RGB32} ,
+ {PJMEDIA_FORMAT_IYUV, &MEDIASUBTYPE_IYUV} ,
+};
+
+/* dshow_ device info */
+struct dshow_dev_info
+{
+ pjmedia_vid_dev_info info;
+ unsigned dev_id;
+ WCHAR display_name[192];
+};
+
+/* dshow_ factory */
+struct dshow_factory
+{
+ pjmedia_vid_dev_factory base;
+ pj_pool_t *pool;
+ pj_pool_t *dev_pool;
+ pj_pool_factory *pf;
+
+ unsigned dev_count;
+ struct dshow_dev_info *dev_info;
+};
+
+/* Video stream. */
+struct dshow_stream
+{
+ pjmedia_vid_dev_stream base; /**< Base stream */
+ pjmedia_vid_dev_param param; /**< Settings */
+ pj_pool_t *pool; /**< Memory pool. */
+
+ pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */
+ void *user_data; /**< Application data. */
+
+ pj_bool_t quit_flag;
+ pj_bool_t rend_thread_exited;
+ pj_bool_t cap_thread_exited;
+ pj_bool_t cap_thread_initialized;
+ pj_thread_desc cap_thread_desc;
+ pj_thread_t *cap_thread;
+ void *frm_buf;
+ unsigned frm_buf_size;
+
+ struct dshow_graph
+ {
+ IFilterGraph *filter_graph;
+ IMediaFilter *media_filter;
+ SourceFilter *csource_filter;
+ IBaseFilter *source_filter;
+ IBaseFilter *rend_filter;
+ AM_MEDIA_TYPE *mediatype;
+ } dgraph;
+
+ pj_timestamp cap_ts;
+ unsigned cap_ts_inc;
+};
+
+
+/* Prototypes */
+static pj_status_t dshow_factory_init(pjmedia_vid_dev_factory *f);
+static pj_status_t dshow_factory_destroy(pjmedia_vid_dev_factory *f);
+static pj_status_t dshow_factory_refresh(pjmedia_vid_dev_factory *f);
+static unsigned dshow_factory_get_dev_count(pjmedia_vid_dev_factory *f);
+static pj_status_t dshow_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_info *info);
+static pj_status_t dshow_factory_default_param(pj_pool_t *pool,
+ pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_param *param);
+static pj_status_t dshow_factory_create_stream(
+ pjmedia_vid_dev_factory *f,
+ pjmedia_vid_dev_param *param,
+ const pjmedia_vid_dev_cb *cb,
+ void *user_data,
+ pjmedia_vid_dev_stream **p_vid_strm);
+
+static pj_status_t dshow_stream_get_param(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_param *param);
+static pj_status_t dshow_stream_get_cap(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ void *value);
+static pj_status_t dshow_stream_set_cap(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ const void *value);
+static pj_status_t dshow_stream_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t dshow_stream_put_frame(pjmedia_vid_dev_stream *strm,
+ const pjmedia_frame *frame);
+static pj_status_t dshow_stream_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t dshow_stream_destroy(pjmedia_vid_dev_stream *strm);
+
+/* Operations */
+static pjmedia_vid_dev_factory_op factory_op =
+{
+ &dshow_factory_init,
+ &dshow_factory_destroy,
+ &dshow_factory_get_dev_count,
+ &dshow_factory_get_dev_info,
+ &dshow_factory_default_param,
+ &dshow_factory_create_stream,
+ &dshow_factory_refresh
+};
+
+static pjmedia_vid_dev_stream_op stream_op =
+{
+ &dshow_stream_get_param,
+ &dshow_stream_get_cap,
+ &dshow_stream_set_cap,
+ &dshow_stream_start,
+ NULL,
+ &dshow_stream_put_frame,
+ &dshow_stream_stop,
+ &dshow_stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init dshow_ video driver.
+ */
+pjmedia_vid_dev_factory* pjmedia_dshow_factory(pj_pool_factory *pf)
+{
+ struct dshow_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "dshow video", 1000, 1000, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct dshow_factory);
+ f->pf = pf;
+ f->pool = pool;
+ f->base.op = &factory_op;
+
+ return &f->base;
+}
+
+/* API: init factory */
+static pj_status_t dshow_factory_init(pjmedia_vid_dev_factory *f)
+{
+ HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+ if (hr == RPC_E_CHANGED_MODE) {
+ PJ_LOG(4,(THIS_FILE, "Failed initializing DShow: "
+ "COM library already initialized with "
+ "incompatible concurrency model"));
+ return PJMEDIA_EVID_INIT;
+ }
+
+ return dshow_factory_refresh(f);
+}
+
+/* API: destroy factory */
+static pj_status_t dshow_factory_destroy(pjmedia_vid_dev_factory *f)
+{
+ struct dshow_factory *df = (struct dshow_factory*)f;
+ pj_pool_t *pool = df->pool;
+
+ df->pool = NULL;
+ if (df->dev_pool)
+ pj_pool_release(df->dev_pool);
+ if (pool)
+ pj_pool_release(pool);
+
+ CoUninitialize();
+
+ return PJ_SUCCESS;
+}
+
+static HRESULT get_cap_device(struct dshow_factory *df,
+ unsigned id,
+ IBaseFilter **filter)
+{
+ IBindCtx *pbc;
+ HRESULT hr;
+
+ hr = CreateBindCtx(0, &pbc);
+ if (SUCCEEDED (hr)) {
+ IMoniker *moniker;
+ DWORD pchEaten;
+
+ hr = MkParseDisplayName(pbc, df->dev_info[id].display_name,
+ &pchEaten, &moniker);
+ if (SUCCEEDED(hr)) {
+ hr = IMoniker_BindToObject(moniker, pbc, NULL,
+ &IID_IBaseFilter,
+ (LPVOID *)filter);
+ IMoniker_Release(moniker);
+ }
+ IBindCtx_Release(pbc);
+ }
+
+ return hr;
+}
+
+static void enum_dev_cap(IBaseFilter *filter,
+ pjmedia_dir dir,
+ const GUID *dshow_fmt,
+ AM_MEDIA_TYPE **pMediatype,
+ IPin **pSrcpin,
+ pj_bool_t *sup_fmt)
+{
+ IEnumPins *pEnum;
+ AM_MEDIA_TYPE *mediatype = NULL;
+ HRESULT hr;
+
+ if (pSrcpin)
+ *pSrcpin = NULL;
+ hr = IBaseFilter_EnumPins(filter, &pEnum);
+ if (SUCCEEDED(hr)) {
+ /* Loop through all the pins. */
+ IPin *pPin = NULL;
+
+ while (IEnumPins_Next(pEnum, 1, &pPin, NULL) == S_OK) {
+ PIN_DIRECTION pindirtmp;
+
+ hr = IPin_QueryDirection(pPin, &pindirtmp);
+ if (hr != S_OK || pindirtmp != PINDIR_OUTPUT) {
+ if (SUCCEEDED(hr))
+ IPin_Release(pPin);
+ continue;
+ }
+
+ if (dir == PJMEDIA_DIR_CAPTURE) {
+ IAMStreamConfig *streamcaps;
+
+ hr = IPin_QueryInterface(pPin, &IID_IAMStreamConfig,
+ (LPVOID *)&streamcaps);
+ if (SUCCEEDED(hr)) {
+ VIDEO_STREAM_CONFIG_CAPS vscc;
+ int i, isize, icount;
+
+ IAMStreamConfig_GetNumberOfCapabilities(streamcaps,
+ &icount, &isize);
+
+ for (i = 0; i < icount; i++) {
+ unsigned j, nformat;
+ RPC_STATUS rpcstatus, rpcstatus2;
+
+ hr = IAMStreamConfig_GetStreamCaps(streamcaps, i,
+ &mediatype,
+ (BYTE *)&vscc);
+ if (FAILED (hr))
+ continue;
+
+ nformat = (dshow_fmt? 1:
+ sizeof(dshow_fmts)/sizeof(dshow_fmts[0]));
+ for (j = 0; j < nformat; j++) {
+ const GUID *dshow_format = dshow_fmt;
+
+ if (!dshow_format)
+ dshow_format = dshow_fmts[j].dshow_format;
+ if (UuidCompare(&mediatype->subtype,
+ (UUID*)dshow_format,
+ &rpcstatus) == 0 &&
+ rpcstatus == RPC_S_OK &&
+ UuidCompare(&mediatype->formattype,
+ (UUID*)&FORMAT_VideoInfo,
+ &rpcstatus2) == 0 &&
+ rpcstatus2 == RPC_S_OK)
+ {
+ if (sup_fmt)
+ sup_fmt[j] = PJ_TRUE;
+ if (pSrcpin) {
+ *pSrcpin = pPin;
+ *pMediatype = mediatype;
+ }
+ }
+ }
+ if (pSrcpin && *pSrcpin)
+ break;
+ }
+ IAMStreamConfig_Release(streamcaps);
+ }
+ } else {
+ *pSrcpin = pPin;
+ }
+ if (pSrcpin && *pSrcpin)
+ break;
+ IPin_Release(pPin);
+ }
+ IEnumPins_Release(pEnum);
+ }
+}
+
+/* API: refresh the list of devices */
+static pj_status_t dshow_factory_refresh(pjmedia_vid_dev_factory *f)
+{
+ struct dshow_factory *df = (struct dshow_factory*)f;
+ struct dshow_dev_info *ddi;
+ int dev_count = 0;
+ unsigned c;
+ ICreateDevEnum *dev_enum = NULL;
+ IEnumMoniker *enum_cat = NULL;
+ IMoniker *moniker = NULL;
+ HRESULT hr;
+ ULONG fetched;
+
+ if (df->dev_pool) {
+ pj_pool_release(df->dev_pool);
+ df->dev_pool = NULL;
+ }
+
+ df->dev_count = 0;
+ df->dev_pool = pj_pool_create(df->pf, "dshow video", 500, 500, NULL);
+
+ hr = CoCreateInstance(&CLSID_SystemDeviceEnum, NULL,
+ CLSCTX_INPROC_SERVER, &IID_ICreateDevEnum,
+ (void**)&dev_enum);
+ if (FAILED(hr) ||
+ ICreateDevEnum_CreateClassEnumerator(dev_enum,
+ &CLSID_VideoInputDeviceCategory, &enum_cat, 0) != S_OK)
+ {
+ PJ_LOG(4,(THIS_FILE, "Windows found no video input devices"));
+ if (dev_enum)
+ ICreateDevEnum_Release(dev_enum);
+ dev_count = 0;
+ } else {
+ while (IEnumMoniker_Next(enum_cat, 1, &moniker, &fetched) == S_OK) {
+ dev_count++;
+ }
+ }
+
+ /* Add renderer device */
+ dev_count += 1;
+ df->dev_info = (struct dshow_dev_info*)
+ pj_pool_calloc(df->dev_pool, dev_count,
+ sizeof(struct dshow_dev_info));
+
+ if (dev_count > 1) {
+ IEnumMoniker_Reset(enum_cat);
+ while (IEnumMoniker_Next(enum_cat, 1, &moniker, &fetched) == S_OK) {
+ IPropertyBag *prop_bag;
+
+ hr = IMoniker_BindToStorage(moniker, 0, 0, &IID_IPropertyBag,
+ (void**)&prop_bag);
+ if (SUCCEEDED(hr)) {
+ VARIANT var_name;
+
+ VariantInit(&var_name);
+ hr = IPropertyBag_Read(prop_bag, L"FriendlyName",
+ &var_name, NULL);
+ if (SUCCEEDED(hr) && var_name.bstrVal) {
+ WCHAR *wszDisplayName = NULL;
+ IBaseFilter *filter;
+
+ ddi = &df->dev_info[df->dev_count++];
+ pj_bzero(ddi, sizeof(*ddi));
+ pj_unicode_to_ansi(var_name.bstrVal,
+ wcslen(var_name.bstrVal),
+ ddi->info.name,
+ sizeof(ddi->info.name));
+
+ hr = IMoniker_GetDisplayName(moniker, NULL, NULL,
+ &wszDisplayName);
+ if (hr == S_OK && wszDisplayName) {
+ pj_memcpy(ddi->display_name, wszDisplayName,
+ (wcslen(wszDisplayName)+1) * sizeof(WCHAR));
+ CoTaskMemFree(wszDisplayName);
+ }
+
+ strncpy(ddi->info.driver, "dshow",
+ sizeof(ddi->info.driver));
+ ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0';
+ ddi->info.dir = PJMEDIA_DIR_CAPTURE;
+ ddi->info.has_callback = PJ_TRUE;
+
+ /* Set the device capabilities here */
+ ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT;
+
+ hr = get_cap_device(df, df->dev_count-1, &filter);
+ if (SUCCEEDED(hr)) {
+ unsigned j;
+ pj_bool_t sup_fmt[sizeof(dshow_fmts)/sizeof(dshow_fmts[0])];
+
+ pj_bzero(sup_fmt, sizeof(sup_fmt));
+ enum_dev_cap(filter, ddi->info.dir, NULL, NULL, NULL, sup_fmt);
+
+ ddi->info.fmt_cnt = 0;
+ for (j = 0;
+ j < sizeof(dshow_fmts)/sizeof(dshow_fmts[0]);
+ j++)
+ {
+ if (!sup_fmt[j])
+ continue;
+ pjmedia_format_init_video(
+ &ddi->info.fmt[ddi->info.fmt_cnt++],
+ dshow_fmts[j].pjmedia_format,
+ DEFAULT_WIDTH, DEFAULT_HEIGHT,
+ DEFAULT_FPS, 1);
+ }
+ }
+ }
+ VariantClear(&var_name);
+
+ IPropertyBag_Release(prop_bag);
+ }
+ IMoniker_Release(moniker);
+ }
+
+ IEnumMoniker_Release(enum_cat);
+ ICreateDevEnum_Release(dev_enum);
+ }
+
+#if HAS_VMR
+ ddi = &df->dev_info[df->dev_count++];
+ pj_bzero(ddi, sizeof(*ddi));
+ pj_ansi_strncpy(ddi->info.name, "Video Mixing Renderer",
+ sizeof(ddi->info.name));
+ ddi->info.name[sizeof(ddi->info.name)-1] = '\0';
+ pj_ansi_strncpy(ddi->info.driver, "dshow", sizeof(ddi->info.driver));
+ ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0';
+ ddi->info.dir = PJMEDIA_DIR_RENDER;
+ ddi->info.has_callback = PJ_FALSE;
+ ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT;
+// TODO:
+// ddi->info.caps |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
+
+ ddi->info.fmt_cnt = 1;
+ pjmedia_format_init_video(&ddi->info.fmt[0], dshow_fmts[0].pjmedia_format,
+ DEFAULT_WIDTH, DEFAULT_HEIGHT,
+ DEFAULT_FPS, 1);
+#endif
+
+ PJ_LOG(4, (THIS_FILE, "DShow has %d devices:",
+ df->dev_count));
+ for (c = 0; c < df->dev_count; ++c) {
+ PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (%s)",
+ c,
+ df->dev_info[c].info.name,
+ df->dev_info[c].info.dir & PJMEDIA_DIR_CAPTURE ?
+ "capture" : "render"));
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned dshow_factory_get_dev_count(pjmedia_vid_dev_factory *f)
+{
+ struct dshow_factory *df = (struct dshow_factory*)f;
+ return df->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t dshow_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_info *info)
+{
+ struct dshow_factory *df = (struct dshow_factory*)f;
+
+ PJ_ASSERT_RETURN(index < df->dev_count, PJMEDIA_EVID_INVDEV);
+
+ pj_memcpy(info, &df->dev_info[index].info, sizeof(*info));
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t dshow_factory_default_param(pj_pool_t *pool,
+ pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_param *param)
+{
+ struct dshow_factory *df = (struct dshow_factory*)f;
+ struct dshow_dev_info *di = &df->dev_info[index];
+
+ PJ_ASSERT_RETURN(index < df->dev_count, PJMEDIA_EVID_INVDEV);
+
+ PJ_UNUSED_ARG(pool);
+
+ pj_bzero(param, sizeof(*param));
+ if (di->info.dir & PJMEDIA_DIR_CAPTURE) {
+ param->dir = PJMEDIA_DIR_CAPTURE;
+ param->cap_id = index;
+ param->rend_id = PJMEDIA_VID_INVALID_DEV;
+ } else if (di->info.dir & PJMEDIA_DIR_RENDER) {
+ param->dir = PJMEDIA_DIR_RENDER;
+ param->rend_id = index;
+ param->cap_id = PJMEDIA_VID_INVALID_DEV;
+ } else {
+ return PJMEDIA_EVID_INVDEV;
+ }
+
+ /* Set the device capabilities here */
+ param->clock_rate = DEFAULT_CLOCK_RATE;
+ param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
+
+ pjmedia_format_copy(&param->fmt, &di->info.fmt[0]);
+
+ return PJ_SUCCESS;
+}
+
+static void input_cb(void *user_data, IMediaSample *pMediaSample)
+{
+ struct dshow_stream *strm = (struct dshow_stream*)user_data;
+ pjmedia_frame frame = {0};
+
+ if (strm->quit_flag) {
+ strm->cap_thread_exited = PJ_TRUE;
+ return;
+ }
+
+ if (strm->cap_thread_initialized == 0 || !pj_thread_is_registered())
+ {
+ pj_status_t status;
+
+ status = pj_thread_register("ds_cap", strm->cap_thread_desc,
+ &strm->cap_thread);
+ if (status != PJ_SUCCESS)
+ return;
+ strm->cap_thread_initialized = 1;
+ PJ_LOG(5,(THIS_FILE, "Capture thread started"));
+ }
+
+ frame.type = PJMEDIA_FRAME_TYPE_VIDEO;
+ IMediaSample_GetPointer(pMediaSample, (BYTE **)&frame.buf);
+ frame.size = IMediaSample_GetActualDataLength(pMediaSample);
+ frame.bit_info = 0;
+ frame.timestamp = strm->cap_ts;
+ strm->cap_ts.u64 += strm->cap_ts_inc;
+
+ if (strm->frm_buf_size) {
+ unsigned i, stride;
+ BYTE *src_buf, *dst_buf;
+ pjmedia_video_format_detail *vfd;
+
+ /* Image is bottom-up, convert it to top-down. */
+ src_buf = dst_buf = (BYTE *)frame.buf;
+ stride = strm->frm_buf_size;
+ vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt,
+ PJ_TRUE);
+ src_buf += (vfd->size.h - 1) * stride;
+
+ for (i = vfd->size.h / 2; i > 0; i--) {
+ memcpy(strm->frm_buf, dst_buf, stride);
+ memcpy(dst_buf, src_buf, stride);
+ memcpy(src_buf, strm->frm_buf, stride);
+ dst_buf += stride;
+ src_buf -= stride;
+ }
+ }
+
+ if (strm->vid_cb.capture_cb)
+ (*strm->vid_cb.capture_cb)(&strm->base, strm->user_data, &frame);
+}
+
+/* API: Put frame from stream */
+static pj_status_t dshow_stream_put_frame(pjmedia_vid_dev_stream *strm,
+ const pjmedia_frame *frame)
+{
+ struct dshow_stream *stream = (struct dshow_stream*)strm;
+ HRESULT hr;
+
+ if (stream->quit_flag) {
+ stream->rend_thread_exited = PJ_TRUE;
+ return PJ_SUCCESS;
+ }
+
+ hr = SourceFilter_Deliver(stream->dgraph.csource_filter,
+ frame->buf, frame->size);
+ if (FAILED(hr))
+ return hr;
+
+ return PJ_SUCCESS;
+}
+
+static dshow_fmt_info* get_dshow_format_info(pjmedia_format_id id)
+{
+ unsigned i;
+
+ for (i = 0; i < sizeof(dshow_fmts)/sizeof(dshow_fmts[0]); i++) {
+ if (dshow_fmts[i].pjmedia_format == id)
+ return &dshow_fmts[i];
+ }
+
+ return NULL;
+}
+
+static pj_status_t create_filter_graph(pjmedia_dir dir,
+ unsigned id,
+ pj_bool_t use_def_size,
+ pj_bool_t use_def_fps,
+ struct dshow_factory *df,
+ struct dshow_stream *strm,
+ struct dshow_graph *graph)
+{
+ HRESULT hr;
+ IEnumPins *pEnum;
+ IPin *srcpin = NULL;
+ IPin *sinkpin = NULL;
+ AM_MEDIA_TYPE *mediatype= NULL, mtype;
+ VIDEOINFOHEADER *video_info, *vi = NULL;
+ pjmedia_video_format_detail *vfd;
+ const pjmedia_video_format_info *vfi;
+
+ vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(),
+ strm->param.fmt.id);
+ if (!vfi)
+ return PJMEDIA_EVID_BADFORMAT;
+
+ hr = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC,
+ &IID_IFilterGraph, (LPVOID *)&graph->filter_graph);
+ if (FAILED(hr)) {
+ goto on_error;
+ }
+
+ hr = IFilterGraph_QueryInterface(graph->filter_graph, &IID_IMediaFilter,
+ (LPVOID *)&graph->media_filter);
+ if (FAILED(hr)) {
+ goto on_error;
+ }
+
+ if (dir == PJMEDIA_DIR_CAPTURE) {
+ hr = get_cap_device(df, id, &graph->source_filter);
+ if (FAILED(hr)) {
+ goto on_error;
+ }
+ } else {
+ graph->source_filter = SourceFilter_Create(&graph->csource_filter);
+ }
+
+ hr = IFilterGraph_AddFilter(graph->filter_graph, graph->source_filter,
+ L"capture");
+ if (FAILED(hr)) {
+ goto on_error;
+ }
+
+ if (dir == PJMEDIA_DIR_CAPTURE) {
+ graph->rend_filter = NullRenderer_Create(input_cb, strm);
+ } else {
+ hr = CoCreateInstance(&CLSID_VideoMixingRenderer, NULL,
+ CLSCTX_INPROC, &IID_IBaseFilter,
+ (LPVOID *)&graph->rend_filter);
+ if (FAILED (hr)) {
+ goto on_error;
+ }
+ }
+
+ IBaseFilter_EnumPins(graph->rend_filter, &pEnum);
+ if (SUCCEEDED(hr)) {
+ // Loop through all the pins
+ IPin *pPin = NULL;
+
+ while (IEnumPins_Next(pEnum, 1, &pPin, NULL) == S_OK) {
+ PIN_DIRECTION pindirtmp;
+
+ hr = IPin_QueryDirection(pPin, &pindirtmp);
+ if (hr == S_OK && pindirtmp == PINDIR_INPUT) {
+ sinkpin = pPin;
+ break;
+ }
+ IPin_Release(pPin);
+ }
+ IEnumPins_Release(pEnum);
+ }
+
+ vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE);
+
+ enum_dev_cap(graph->source_filter, dir,
+ get_dshow_format_info(strm->param.fmt.id)->dshow_format,
+ &mediatype, &srcpin, NULL);
+ graph->mediatype = mediatype;
+
+ if (srcpin && dir == PJMEDIA_DIR_RENDER) {
+ mediatype = graph->mediatype = &mtype;
+
+ memset (mediatype, 0, sizeof(AM_MEDIA_TYPE));
+ mediatype->majortype = MEDIATYPE_Video;
+ mediatype->subtype = *(get_dshow_format_info(strm->param.fmt.id)->
+ dshow_format);
+ mediatype->bFixedSizeSamples = TRUE;
+ mediatype->bTemporalCompression = FALSE;
+
+ vi = (VIDEOINFOHEADER *)
+ CoTaskMemAlloc(sizeof(VIDEOINFOHEADER));
+ memset (vi, 0, sizeof(VIDEOINFOHEADER));
+ mediatype->formattype = FORMAT_VideoInfo;
+ mediatype->cbFormat = sizeof(VIDEOINFOHEADER);
+ mediatype->pbFormat = (BYTE *)vi;
+
+ vi->rcSource.bottom = vfd->size.h;
+ vi->rcSource.right = vfd->size.w;
+ vi->rcTarget.bottom = vfd->size.h;
+ vi->rcTarget.right = vfd->size.w;
+
+ vi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ vi->bmiHeader.biPlanes = 1;
+ vi->bmiHeader.biBitCount = vfi->bpp;
+ vi->bmiHeader.biCompression = strm->param.fmt.id;
+ }
+
+ if (!srcpin || !sinkpin || !mediatype) {
+ hr = VFW_E_TYPE_NOT_ACCEPTED;
+ goto on_error;
+ }
+ video_info = (VIDEOINFOHEADER *) mediatype->pbFormat;
+ if (!use_def_size) {
+ video_info->bmiHeader.biWidth = vfd->size.w;
+ video_info->bmiHeader.biHeight = vfd->size.h;
+ }
+ if (video_info->AvgTimePerFrame == 0 ||
+ (!use_def_fps && vfd->fps.num != 0))
+ {
+ video_info->AvgTimePerFrame = (LONGLONG) (10000000 *
+ (double)vfd->fps.denum /
+ vfd->fps.num);
+ }
+ video_info->bmiHeader.biSizeImage = DIBSIZE(video_info->bmiHeader);
+ mediatype->lSampleSize = DIBSIZE(video_info->bmiHeader);
+ if (graph->csource_filter)
+ SourceFilter_SetMediaType(graph->csource_filter,
+ mediatype);
+
+ hr = IFilterGraph_AddFilter(graph->filter_graph,
+ (IBaseFilter *)graph->rend_filter,
+ L"renderer");
+ if (FAILED(hr))
+ goto on_error;
+
+ hr = IFilterGraph_ConnectDirect(graph->filter_graph, srcpin, sinkpin,
+ mediatype);
+ if (SUCCEEDED(hr)) {
+ if (use_def_size || use_def_fps) {
+ pjmedia_format_init_video(&strm->param.fmt, strm->param.fmt.id,
+ video_info->bmiHeader.biWidth,
+ video_info->bmiHeader.biHeight,
+ 10000000,
+ (unsigned)video_info->AvgTimePerFrame);
+ }
+
+ strm->frm_buf_size = 0;
+ if (dir == PJMEDIA_DIR_CAPTURE &&
+ video_info->bmiHeader.biCompression == BI_RGB &&
+ video_info->bmiHeader.biHeight > 0)
+ {
+ /* Allocate buffer to flip the captured image. */
+ strm->frm_buf_size = (video_info->bmiHeader.biBitCount >> 3) *
+ video_info->bmiHeader.biWidth;
+ strm->frm_buf = pj_pool_alloc(strm->pool, strm->frm_buf_size);
+ }
+ }
+
+on_error:
+ if (srcpin)
+ IPin_Release(srcpin);
+ if (sinkpin)
+ IPin_Release(sinkpin);
+ if (vi)
+ CoTaskMemFree(vi);
+ if (FAILED(hr)) {
+ char msg[80];
+ if (AMGetErrorText(hr, msg, sizeof(msg))) {
+ PJ_LOG(4,(THIS_FILE, "Error creating filter graph: %s (hr=0x%x)",
+ msg, hr));
+ }
+ return PJ_EUNKNOWN;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static void destroy_filter_graph(struct dshow_stream * stream)
+{
+ if (stream->dgraph.source_filter) {
+ IBaseFilter_Release(stream->dgraph.source_filter);
+ stream->dgraph.source_filter = NULL;
+ }
+ if (stream->dgraph.rend_filter) {
+ IBaseFilter_Release(stream->dgraph.rend_filter);
+ stream->dgraph.rend_filter = NULL;
+ }
+ if (stream->dgraph.media_filter) {
+ IMediaFilter_Release(stream->dgraph.media_filter);
+ stream->dgraph.media_filter = NULL;
+ }
+ if (stream->dgraph.filter_graph) {
+ IFilterGraph_Release(stream->dgraph.filter_graph);
+ stream->dgraph.filter_graph = NULL;
+ }
+}
+
+/* API: create stream */
+static pj_status_t dshow_factory_create_stream(
+ pjmedia_vid_dev_factory *f,
+ pjmedia_vid_dev_param *param,
+ const pjmedia_vid_dev_cb *cb,
+ void *user_data,
+ pjmedia_vid_dev_stream **p_vid_strm)
+{
+ struct dshow_factory *df = (struct dshow_factory*)f;
+ pj_pool_t *pool;
+ struct dshow_stream *strm;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_CAPTURE ||
+ param->dir == PJMEDIA_DIR_RENDER, PJ_EINVAL);
+
+ if (!get_dshow_format_info(param->fmt.id))
+ return PJMEDIA_EVID_BADFORMAT;
+
+ /* Create and Initialize stream descriptor */
+ pool = pj_pool_create(df->pf, "dshow-dev", 1000, 1000, NULL);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ strm = PJ_POOL_ZALLOC_T(pool, struct dshow_stream);
+ pj_memcpy(&strm->param, param, sizeof(*param));
+ strm->pool = pool;
+ pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
+ strm->user_data = user_data;
+
+ if (param->dir & PJMEDIA_DIR_CAPTURE) {
+ const pjmedia_video_format_detail *vfd;
+
+ /* Create capture stream here */
+ status = create_filter_graph(PJMEDIA_DIR_CAPTURE, param->cap_id,
+ PJ_FALSE, PJ_FALSE, df, strm,
+ &strm->dgraph);
+ if (status != PJ_SUCCESS) {
+ destroy_filter_graph(strm);
+ /* Try to use default fps */
+ PJ_LOG(4,(THIS_FILE, "Trying to open dshow dev with default fps"));
+ status = create_filter_graph(PJMEDIA_DIR_CAPTURE, param->cap_id,
+ PJ_FALSE, PJ_TRUE, df, strm,
+ &strm->dgraph);
+
+ if (status != PJ_SUCCESS) {
+ /* Still failed, now try to use default fps and size */
+ destroy_filter_graph(strm);
+ /* Try to use default fps */
+ PJ_LOG(4,(THIS_FILE, "Trying to open dshow dev with default "
+ "size & fps"));
+ status = create_filter_graph(PJMEDIA_DIR_CAPTURE,
+ param->cap_id,
+ PJ_TRUE, PJ_TRUE, df, strm,
+ &strm->dgraph);
+ }
+
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ pj_memcpy(param, &strm->param, sizeof(*param));
+ }
+
+ vfd = pjmedia_format_get_video_format_detail(&param->fmt, PJ_TRUE);
+ strm->cap_ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1);
+ } else if (param->dir & PJMEDIA_DIR_RENDER) {
+ /* Create render stream here */
+ status = create_filter_graph(PJMEDIA_DIR_RENDER, param->rend_id,
+ PJ_FALSE, PJ_FALSE, df, strm,
+ &strm->dgraph);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ /* Apply the remaining settings */
+ if (param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) {
+ dshow_stream_set_cap(&strm->base,
+ PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW,
+ &param->window);
+ }
+
+ /* Done */
+ strm->base.op = &stream_op;
+ *p_vid_strm = &strm->base;
+
+ return PJ_SUCCESS;
+
+on_error:
+ dshow_stream_destroy((pjmedia_vid_dev_stream *)strm);
+ return status;
+}
+
+/* API: Get stream info. */
+static pj_status_t dshow_stream_get_param(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_param *pi)
+{
+ struct dshow_stream *strm = (struct dshow_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+ if (dshow_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW,
+ &pi->window) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t dshow_stream_get_cap(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_cap cap,
+ void *pval)
+{
+ struct dshow_stream *strm = (struct dshow_stream*)s;
+
+ PJ_UNUSED_ARG(strm);
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW)
+ {
+ *(unsigned*)pval = 0;
+ return PJ_SUCCESS;
+ } else {
+ return PJMEDIA_EVID_INVCAP;
+ }
+}
+
+/* API: set capability */
+static pj_status_t dshow_stream_set_cap(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_cap cap,
+ const void *pval)
+{
+ struct dshow_stream *strm = (struct dshow_stream*)s;
+
+ PJ_UNUSED_ARG(strm);
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW)
+ {
+ // set renderer's window here
+ return PJ_SUCCESS;
+ }
+
+ return PJMEDIA_EVID_INVCAP;
+}
+
+/* API: Start stream. */
+static pj_status_t dshow_stream_start(pjmedia_vid_dev_stream *strm)
+{
+ struct dshow_stream *stream = (struct dshow_stream*)strm;
+ HRESULT hr;
+
+ stream->quit_flag = PJ_FALSE;
+ stream->cap_thread_exited = PJ_FALSE;
+ stream->rend_thread_exited = PJ_FALSE;
+
+ hr = IMediaFilter_Run(stream->dgraph.media_filter, 0);
+ if (FAILED(hr)) {
+ char msg[80];
+ if (AMGetErrorText(hr, msg, sizeof(msg))) {
+ PJ_LOG(4,(THIS_FILE, "Error starting media: %s", msg));
+ }
+ return PJ_EUNKNOWN;
+ }
+
+ PJ_LOG(4, (THIS_FILE, "Starting dshow video stream"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t dshow_stream_stop(pjmedia_vid_dev_stream *strm)
+{
+ struct dshow_stream *stream = (struct dshow_stream*)strm;
+ unsigned i;
+
+ stream->quit_flag = PJ_TRUE;
+ if (stream->cap_thread) {
+ for (i=0; !stream->cap_thread_exited && i<100; ++i)
+ pj_thread_sleep(10);
+ }
+ for (i=0; !stream->rend_thread_exited && i<100; ++i)
+ pj_thread_sleep(10);
+
+ IMediaFilter_Stop(stream->dgraph.media_filter);
+
+ PJ_LOG(4, (THIS_FILE, "Stopping dshow video stream"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: Destroy stream. */
+static pj_status_t dshow_stream_destroy(pjmedia_vid_dev_stream *strm)
+{
+ struct dshow_stream *stream = (struct dshow_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+ dshow_stream_stop(strm);
+ destroy_filter_graph(stream);
+
+ pj_pool_release(stream->pool);
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_VIDEO_DEV_HAS_DSHOW */
diff --git a/pjmedia/src/pjmedia-videodev/dshowclasses.cpp b/pjmedia/src/pjmedia-videodev/dshowclasses.cpp
new file mode 100644
index 0000000..5affe60
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/dshowclasses.cpp
@@ -0,0 +1,242 @@
+/* $Id: dshowclasses.cpp 4062 2012-04-19 06:36:57Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia-videodev/config.h>
+
+
+#if defined(PJMEDIA_VIDEO_DEV_HAS_DSHOW) && PJMEDIA_VIDEO_DEV_HAS_DSHOW != 0
+
+
+#include <assert.h>
+#include <streams.h>
+
+typedef void (*input_callback)(void *user_data, IMediaSample *pMediaSample);
+
+const GUID CLSID_NullRenderer = {0xF9168C5E, 0xCEB2, 0x4FAA, {0xB6, 0xBF,
+ 0x32, 0x9B, 0xF3, 0x9F, 0xA1, 0xE4}};
+
+const GUID CLSID_SourceFilter = {0xF9168C5E, 0xCEB2, 0x4FAA, {0xB6, 0xBF,
+ 0x32, 0x9B, 0xF3, 0x9F, 0xA1, 0xE5}};
+
+class NullRenderer: public CBaseRenderer
+{
+public:
+ NullRenderer(HRESULT *pHr);
+ virtual ~NullRenderer();
+
+ virtual HRESULT CheckMediaType(const CMediaType *pmt);
+ virtual HRESULT DoRenderSample(IMediaSample *pMediaSample);
+
+ input_callback input_cb;
+ void *user_data;
+};
+
+class OutputPin: public CBaseOutputPin
+{
+public:
+ OutputPin(CBaseFilter *pFilter, CCritSec *pLock, HRESULT *pHr);
+ ~OutputPin();
+
+ HRESULT Push(void *buf, long size);
+
+ virtual HRESULT CheckMediaType(const CMediaType *pmt);
+ virtual HRESULT DecideBufferSize(IMemAllocator *pAlloc,
+ ALLOCATOR_PROPERTIES *ppropInputRequest);
+
+ CMediaType mediaType;
+ long bufSize;
+};
+
+class SourceFilter: public CBaseFilter
+{
+public:
+ SourceFilter();
+ ~SourceFilter();
+
+ int GetPinCount();
+ CBasePin* GetPin(int n);
+
+protected:
+ CCritSec lock;
+ OutputPin* outPin;
+};
+
+OutputPin::OutputPin(CBaseFilter *pFilter, CCritSec *pLock, HRESULT *pHr):
+ CBaseOutputPin("OutputPin", pFilter, pLock, pHr, L"OutputPin")
+{
+}
+
+OutputPin::~OutputPin()
+{
+}
+
+HRESULT OutputPin::CheckMediaType(const CMediaType *pmt)
+{
+ return S_OK;
+}
+
+HRESULT OutputPin::DecideBufferSize(IMemAllocator *pAlloc,
+ ALLOCATOR_PROPERTIES *ppropInputRequest)
+{
+ ALLOCATOR_PROPERTIES properties;
+
+ ppropInputRequest->cbBuffer = bufSize;
+ ppropInputRequest->cBuffers = 1;
+
+ /* First set the buffer descriptions we're interested in */
+ pAlloc->SetProperties(ppropInputRequest, &properties);
+
+ return S_OK;
+}
+
+HRESULT OutputPin::Push(void *buf, long size)
+{
+ HRESULT hr;
+ IMediaSample *pSample;
+ VIDEOINFOHEADER *vi;
+ AM_MEDIA_TYPE *pmt;
+ BYTE *dst_buf;
+
+ /**
+ * Hold the critical section here as the pin might get disconnected
+ * during the Deliver() method call.
+ */
+ m_pLock->Lock();
+
+ hr = GetDeliveryBuffer(&pSample, NULL, NULL, 0);
+ if (FAILED(hr))
+ goto on_error;
+
+ pSample->GetMediaType(&pmt);
+ if (pmt) {
+ mediaType.Set(*pmt);
+ bufSize = pmt->lSampleSize;
+ }
+
+ pSample->GetPointer(&dst_buf);
+ vi = (VIDEOINFOHEADER *)mediaType.pbFormat;
+ if (vi->rcSource.right == vi->bmiHeader.biWidth) {
+ assert(pSample->GetSize() >= size);
+ memcpy(dst_buf, buf, size);
+ } else {
+ unsigned i, bpp;
+ unsigned dststride, srcstride;
+ BYTE *src_buf = (BYTE *)buf;
+
+ bpp = size / abs(vi->bmiHeader.biHeight) / vi->rcSource.right;
+ dststride = vi->bmiHeader.biWidth * bpp;
+ srcstride = vi->rcSource.right * bpp;
+ for (i = abs(vi->bmiHeader.biHeight); i > 0; i--) {
+ memcpy(dst_buf, src_buf, srcstride);
+ dst_buf += dststride;
+ src_buf += srcstride;
+ }
+ }
+ pSample->SetActualDataLength(size);
+
+ hr = Deliver(pSample);
+
+ pSample->Release();
+
+on_error:
+ m_pLock->Unlock();
+ return hr;
+}
+
+SourceFilter::SourceFilter(): CBaseFilter("SourceFilter", NULL, &lock,
+ CLSID_SourceFilter)
+{
+ HRESULT hr;
+ outPin = new OutputPin(this, &lock, &hr);
+}
+
+SourceFilter::~SourceFilter()
+{
+}
+
+int SourceFilter::GetPinCount()
+{
+ return 1;
+}
+
+CBasePin* SourceFilter::GetPin(int n)
+{
+ return outPin;
+}
+
+NullRenderer::NullRenderer(HRESULT *pHr): CBaseRenderer(CLSID_NullRenderer,
+ "NullRenderer",
+ NULL, pHr)
+{
+ input_cb = NULL;
+}
+
+NullRenderer::~NullRenderer()
+{
+}
+
+HRESULT NullRenderer::CheckMediaType(const CMediaType *pmt)
+{
+ return S_OK;
+}
+
+HRESULT NullRenderer::DoRenderSample(IMediaSample *pMediaSample)
+{
+ if (input_cb)
+ input_cb(user_data, pMediaSample);
+
+ return S_OK;
+}
+
+extern "C" IBaseFilter* NullRenderer_Create(input_callback input_cb,
+ void *user_data)
+{
+ HRESULT hr;
+ NullRenderer *renderer = new NullRenderer(&hr);
+ renderer->AddRef();
+ renderer->input_cb = input_cb;
+ renderer->user_data = user_data;
+
+ return (CBaseFilter *)renderer;
+}
+
+extern "C" IBaseFilter* SourceFilter_Create(SourceFilter **pSrc)
+{
+ SourceFilter *src = new SourceFilter();
+ src->AddRef();
+ *pSrc = src;
+
+ return (CBaseFilter *)src;
+}
+
+extern "C" HRESULT SourceFilter_Deliver(SourceFilter *src,
+ void *buf, long size)
+{
+ return ((OutputPin *)src->GetPin(0))->Push(buf, size);
+}
+
+extern "C" void SourceFilter_SetMediaType(SourceFilter *src,
+ AM_MEDIA_TYPE *pmt)
+{
+ ((OutputPin *)src->GetPin(0))->mediaType.Set(*pmt);
+ ((OutputPin *)src->GetPin(0))->bufSize = pmt->lSampleSize;
+}
+
+
+#endif /* PJMEDIA_VIDEO_DEV_HAS_DSHOW */
diff --git a/pjmedia/src/pjmedia-videodev/errno.c b/pjmedia/src/pjmedia-videodev/errno.c
new file mode 100644
index 0000000..ffcccf7
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/errno.c
@@ -0,0 +1,119 @@
+/* $Id: errno.c 3715 2011-08-19 09:35:25Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia-videodev/errno.h>
+#include <pj/string.h>
+#include <pj/unicode.h>
+
+/* PJMEDIA-videodev's own error codes/messages
+ * MUST KEEP THIS ARRAY SORTED!!
+ * Message must be limited to 64 chars!
+ */
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+static const struct
+{
+ int code;
+ const char *msg;
+} err_str[] =
+{
+ PJ_BUILD_ERR( PJMEDIA_EVID_ERR, "Unspecified video device error" ),
+ PJ_BUILD_ERR( PJMEDIA_EVID_SYSERR, "Unknown error from video driver" ),
+ PJ_BUILD_ERR( PJMEDIA_EVID_INIT, "video subsystem not initialized" ),
+ PJ_BUILD_ERR( PJMEDIA_EVID_INVDEV, "Invalid video device" ),
+ PJ_BUILD_ERR( PJMEDIA_EVID_NODEV, "Found no video devices" ),
+ PJ_BUILD_ERR( PJMEDIA_EVID_NODEFDEV, "Unable to find default video device" ),
+ PJ_BUILD_ERR( PJMEDIA_EVID_NOTREADY, "video device not ready" ),
+ PJ_BUILD_ERR( PJMEDIA_EVID_INVCAP, "Invalid or unsupported video capability" ),
+ PJ_BUILD_ERR( PJMEDIA_EVID_INVOP, "Invalid or unsupported video device operation" ),
+ PJ_BUILD_ERR( PJMEDIA_EVID_BADFORMAT, "Bad or invalid video device format" ),
+ PJ_BUILD_ERR( PJMEDIA_EVID_SAMPFORMAT, "Invalid video device sample format"),
+ PJ_BUILD_ERR( PJMEDIA_EVID_BADLATENCY, "Bad video latency setting")
+
+};
+
+#endif /* PJ_HAS_ERROR_STRING */
+
+
+
+/*
+ * pjmedia_videodev_strerror()
+ */
+PJ_DEF(pj_str_t) pjmedia_videodev_strerror(pj_status_t statcode,
+ char *buf, pj_size_t bufsize )
+{
+ pj_str_t errstr;
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+ /* videodev error */
+ if (statcode >= PJMEDIA_VIDEODEV_ERRNO_START &&
+ statcode < PJMEDIA_VIDEODEV_ERRNO_END)
+ {
+ /* Find the error in the table.
+ * Use binary search!
+ */
+ int first = 0;
+ int n = PJ_ARRAY_SIZE(err_str);
+
+ while (n > 0) {
+ int half = n/2;
+ int mid = first + half;
+
+ if (err_str[mid].code < statcode) {
+ first = mid+1;
+ n -= (half+1);
+ } else if (err_str[mid].code > statcode) {
+ n = half;
+ } else {
+ first = mid;
+ break;
+ }
+ }
+
+
+ if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) {
+ pj_str_t msg;
+
+ msg.ptr = (char*)err_str[first].msg;
+ msg.slen = pj_ansi_strlen(err_str[first].msg);
+
+ errstr.ptr = buf;
+ pj_strncpy_with_null(&errstr, &msg, bufsize);
+ return errstr;
+
+ }
+ }
+#endif /* PJ_HAS_ERROR_STRING */
+
+ /* Error not found. */
+ errstr.ptr = buf;
+ errstr.slen = pj_ansi_snprintf(buf, bufsize,
+ "Unknown pjmedia-videodev error %d",
+ statcode);
+
+ return errstr;
+}
+
+
+#endif /* PJMEDIA_HAS_VIDEO */
diff --git a/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c b/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c
new file mode 100644
index 0000000..fe8078b
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c
@@ -0,0 +1,516 @@
+/* $Id: ffmpeg_dev.c 3893 2011-12-01 10:49:07Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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
+ */
+
+/* Video device with ffmpeg backend, currently only capture devices are
+ * implemented.
+ *
+ * Issues:
+ * - no device enumeration (ffmpeg limitation), so this uses "host API" enum
+ * instead
+ * - need stricter filter on "host API" enum, currently audio capture devs are
+ * still listed.
+ * - no format enumeration, currently hardcoded to PJMEDIA_FORMAT_RGB24 only
+ * - tested on Vista only (vfw backend) with virtual cam
+ * - vfw backend produce bottom up pictures
+ * - using VS IDE, this cannot run under debugger!
+ */
+
+#include <pjmedia-videodev/videodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/unicode.h>
+
+
+#if defined(PJMEDIA_VIDEO_DEV_HAS_FFMPEG) && PJMEDIA_VIDEO_DEV_HAS_FFMPEG != 0
+
+
+#define THIS_FILE "ffmpeg.c"
+
+#include "../pjmedia/ffmpeg_util.h"
+#include <libavdevice/avdevice.h>
+#include <libavformat/avformat.h>
+
+#define MAX_DEV_CNT 8
+
+typedef struct ffmpeg_dev_info
+{
+ pjmedia_vid_dev_info base;
+ AVInputFormat *host_api;
+ const char *def_devname;
+} ffmpeg_dev_info;
+
+
+typedef struct ffmpeg_factory
+{
+ pjmedia_vid_dev_factory base;
+ pj_pool_factory *pf;
+ pj_pool_t *pool;
+ pj_pool_t *dev_pool;
+ unsigned dev_count;
+ ffmpeg_dev_info dev_info[MAX_DEV_CNT];
+} ffmpeg_factory;
+
+
+typedef struct ffmpeg_stream
+{
+ pjmedia_vid_dev_stream base;
+ ffmpeg_factory *factory;
+ pj_pool_t *pool;
+ pjmedia_vid_dev_param param;
+ AVFormatContext *ff_fmt_ctx;
+} ffmpeg_stream;
+
+
+/* Prototypes */
+static pj_status_t ffmpeg_factory_init(pjmedia_vid_dev_factory *f);
+static pj_status_t ffmpeg_factory_destroy(pjmedia_vid_dev_factory *f);
+static pj_status_t ffmpeg_factory_refresh(pjmedia_vid_dev_factory *f);
+static unsigned ffmpeg_factory_get_dev_count(pjmedia_vid_dev_factory *f);
+static pj_status_t ffmpeg_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_info *info);
+static pj_status_t ffmpeg_factory_default_param(pj_pool_t *pool,
+ pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_param *param);
+static pj_status_t ffmpeg_factory_create_stream(
+ pjmedia_vid_dev_factory *f,
+ pjmedia_vid_dev_param *param,
+ const pjmedia_vid_dev_cb *cb,
+ void *user_data,
+ pjmedia_vid_dev_stream **p_vid_strm);
+
+static pj_status_t ffmpeg_stream_get_param(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_param *param);
+static pj_status_t ffmpeg_stream_get_cap(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ void *value);
+static pj_status_t ffmpeg_stream_set_cap(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ const void *value);
+static pj_status_t ffmpeg_stream_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t ffmpeg_stream_get_frame(pjmedia_vid_dev_stream *s,
+ pjmedia_frame *frame);
+static pj_status_t ffmpeg_stream_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t ffmpeg_stream_destroy(pjmedia_vid_dev_stream *strm);
+
+/* Operations */
+static pjmedia_vid_dev_factory_op factory_op =
+{
+ &ffmpeg_factory_init,
+ &ffmpeg_factory_destroy,
+ &ffmpeg_factory_get_dev_count,
+ &ffmpeg_factory_get_dev_info,
+ &ffmpeg_factory_default_param,
+ &ffmpeg_factory_create_stream,
+ &ffmpeg_factory_refresh
+};
+
+static pjmedia_vid_dev_stream_op stream_op =
+{
+ &ffmpeg_stream_get_param,
+ &ffmpeg_stream_get_cap,
+ &ffmpeg_stream_set_cap,
+ &ffmpeg_stream_start,
+ &ffmpeg_stream_get_frame,
+ NULL,
+ &ffmpeg_stream_stop,
+ &ffmpeg_stream_destroy
+};
+
+
+static void print_ffmpeg_err(int err)
+{
+ char errbuf[512];
+ if (av_strerror(err, errbuf, sizeof(errbuf)) >= 0)
+ PJ_LOG(1, (THIS_FILE, "ffmpeg err %d: %s", err, errbuf));
+
+}
+
+static void print_ffmpeg_log(void* ptr, int level, const char* fmt, va_list vl)
+{
+ PJ_UNUSED_ARG(ptr);
+ PJ_UNUSED_ARG(level);
+ vfprintf(stdout, fmt, vl);
+}
+
+
+static pj_status_t ffmpeg_capture_open(AVFormatContext **ctx,
+ AVInputFormat *ifmt,
+ const char *dev_name,
+ const pjmedia_vid_dev_param *param)
+{
+ AVFormatParameters fp;
+ pjmedia_video_format_detail *vfd;
+ int err;
+
+ PJ_ASSERT_RETURN(ctx && ifmt && dev_name && param, PJ_EINVAL);
+ PJ_ASSERT_RETURN(param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO,
+ PJ_EINVAL);
+
+ vfd = pjmedia_format_get_video_format_detail(&param->fmt, PJ_TRUE);
+
+ /* Init ffmpeg format context */
+ *ctx = avformat_alloc_context();
+
+ /* Init ffmpeg format param */
+ pj_bzero(&fp, sizeof(fp));
+ fp.prealloced_context = 1;
+ fp.width = vfd->size.w;
+ fp.height = vfd->size.h;
+ fp.pix_fmt = PIX_FMT_BGR24;
+ fp.time_base.num = vfd->fps.denum;
+ fp.time_base.den = vfd->fps.num;
+
+ /* Open capture stream */
+ err = av_open_input_stream(ctx, NULL, dev_name, ifmt, &fp);
+ if (err < 0) {
+ *ctx = NULL; /* ffmpeg freed its states on failure, do we must too */
+ print_ffmpeg_err(err);
+ return PJ_EUNKNOWN;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static void ffmpeg_capture_close(AVFormatContext *ctx)
+{
+ if (ctx)
+ av_close_input_stream(ctx);
+}
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init ffmpeg_ video driver.
+ */
+pjmedia_vid_dev_factory* pjmedia_ffmpeg_factory(pj_pool_factory *pf)
+{
+ ffmpeg_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "ffmpeg_cap_dev", 1000, 1000, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, ffmpeg_factory);
+
+ f->pool = pool;
+ f->pf = pf;
+ f->base.op = &factory_op;
+
+ avdevice_register_all();
+
+ return &f->base;
+}
+
+
+/* API: init factory */
+static pj_status_t ffmpeg_factory_init(pjmedia_vid_dev_factory *f)
+{
+ return ffmpeg_factory_refresh(f);
+}
+
+/* API: destroy factory */
+static pj_status_t ffmpeg_factory_destroy(pjmedia_vid_dev_factory *f)
+{
+ ffmpeg_factory *ff = (ffmpeg_factory*)f;
+ pj_pool_t *pool = ff->pool;
+
+ ff->dev_count = 0;
+ ff->pool = NULL;
+ if (ff->dev_pool)
+ pj_pool_release(ff->dev_pool);
+ if (pool)
+ pj_pool_release(pool);
+
+ return PJ_SUCCESS;
+}
+
+/* API: refresh the list of devices */
+static pj_status_t ffmpeg_factory_refresh(pjmedia_vid_dev_factory *f)
+{
+ ffmpeg_factory *ff = (ffmpeg_factory*)f;
+ AVInputFormat *p;
+ ffmpeg_dev_info *info;
+
+ av_log_set_callback(&print_ffmpeg_log);
+ av_log_set_level(AV_LOG_DEBUG);
+
+ if (ff->dev_pool) {
+ pj_pool_release(ff->dev_pool);
+ ff->dev_pool = NULL;
+ }
+
+ /* TODO: this should enumerate devices, now it enumerates host APIs */
+ ff->dev_count = 0;
+ ff->dev_pool = pj_pool_create(ff->pf, "ffmpeg_cap_dev", 500, 500, NULL);
+
+ p = av_iformat_next(NULL);
+ while (p) {
+ if (p->flags & AVFMT_NOFILE) {
+ unsigned i;
+
+ info = &ff->dev_info[ff->dev_count++];
+ pj_bzero(info, sizeof(*info));
+ pj_ansi_strncpy(info->base.name, "default",
+ sizeof(info->base.name));
+ pj_ansi_snprintf(info->base.driver, sizeof(info->base.driver),
+ "%s (ffmpeg)", p->name);
+ info->base.dir = PJMEDIA_DIR_CAPTURE;
+ info->base.has_callback = PJ_FALSE;
+
+ info->host_api = p;
+
+#if defined(PJ_WIN32) && PJ_WIN32!=0
+ info->def_devname = "0";
+#elif defined(PJ_LINUX) && PJ_LINUX!=0
+ info->def_devname = "/dev/video0";
+#endif
+
+ /* Set supported formats, currently hardcoded to RGB24 only */
+ info->base.caps = PJMEDIA_VID_DEV_CAP_FORMAT;
+ info->base.fmt_cnt = 1;
+ for (i = 0; i < info->base.fmt_cnt; ++i) {
+ pjmedia_format *fmt = &info->base.fmt[i];
+
+ fmt->id = PJMEDIA_FORMAT_RGB24;
+ fmt->type = PJMEDIA_TYPE_VIDEO;
+ fmt->detail_type = PJMEDIA_FORMAT_DETAIL_NONE;
+ }
+ }
+ p = av_iformat_next(p);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned ffmpeg_factory_get_dev_count(pjmedia_vid_dev_factory *f)
+{
+ ffmpeg_factory *ff = (ffmpeg_factory*)f;
+ return ff->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t ffmpeg_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_info *info)
+{
+ ffmpeg_factory *ff = (ffmpeg_factory*)f;
+
+ PJ_ASSERT_RETURN(index < ff->dev_count, PJMEDIA_EVID_INVDEV);
+
+ pj_memcpy(info, &ff->dev_info[index].base, sizeof(*info));
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t ffmpeg_factory_default_param(pj_pool_t *pool,
+ pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_param *param)
+{
+ ffmpeg_factory *ff = (ffmpeg_factory*)f;
+ ffmpeg_dev_info *info;
+
+ PJ_ASSERT_RETURN(index < ff->dev_count, PJMEDIA_EVID_INVDEV);
+
+ PJ_UNUSED_ARG(pool);
+
+ info = &ff->dev_info[index];
+
+ pj_bzero(param, sizeof(*param));
+ param->dir = PJMEDIA_DIR_CAPTURE;
+ param->cap_id = index;
+ param->rend_id = PJMEDIA_VID_INVALID_DEV;
+ param->clock_rate = 0;
+
+ /* Set the device capabilities here */
+ param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
+ param->clock_rate = 90000;
+ pjmedia_format_init_video(&param->fmt, 0, 320, 240, 25, 1);
+ param->fmt.id = info->base.fmt[0].id;
+
+ return PJ_SUCCESS;
+}
+
+
+
+/* API: create stream */
+static pj_status_t ffmpeg_factory_create_stream(
+ pjmedia_vid_dev_factory *f,
+ pjmedia_vid_dev_param *param,
+ const pjmedia_vid_dev_cb *cb,
+ void *user_data,
+ pjmedia_vid_dev_stream **p_vid_strm)
+{
+ ffmpeg_factory *ff = (ffmpeg_factory*)f;
+ pj_pool_t *pool;
+ ffmpeg_stream *strm;
+
+ PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL);
+ PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_CAPTURE, PJ_EINVAL);
+ PJ_ASSERT_RETURN((unsigned)param->cap_id < ff->dev_count, PJ_EINVAL);
+ PJ_ASSERT_RETURN(param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO,
+ PJ_EINVAL);
+
+ PJ_UNUSED_ARG(cb);
+ PJ_UNUSED_ARG(user_data);
+
+ /* Create and Initialize stream descriptor */
+ pool = pj_pool_create(ff->pf, "ffmpeg-dev", 1000, 1000, NULL);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ strm = PJ_POOL_ZALLOC_T(pool, struct ffmpeg_stream);
+ strm->factory = (ffmpeg_factory*)f;
+ strm->pool = pool;
+ pj_memcpy(&strm->param, param, sizeof(*param));
+
+ /* Done */
+ strm->base.op = &stream_op;
+ *p_vid_strm = &strm->base;
+
+ return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t ffmpeg_stream_get_param(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_param *pi)
+{
+ ffmpeg_stream *strm = (ffmpeg_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t ffmpeg_stream_get_cap(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_cap cap,
+ void *pval)
+{
+ ffmpeg_stream *strm = (ffmpeg_stream*)s;
+
+ PJ_UNUSED_ARG(strm);
+ PJ_UNUSED_ARG(cap);
+ PJ_UNUSED_ARG(pval);
+
+ return PJMEDIA_EVID_INVCAP;
+}
+
+/* API: set capability */
+static pj_status_t ffmpeg_stream_set_cap(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_cap cap,
+ const void *pval)
+{
+ ffmpeg_stream *strm = (ffmpeg_stream*)s;
+
+ PJ_UNUSED_ARG(strm);
+ PJ_UNUSED_ARG(cap);
+ PJ_UNUSED_ARG(pval);
+
+ return PJMEDIA_EVID_INVCAP;
+}
+
+
+/* API: Start stream. */
+static pj_status_t ffmpeg_stream_start(pjmedia_vid_dev_stream *s)
+{
+ ffmpeg_stream *strm = (ffmpeg_stream*)s;
+ ffmpeg_dev_info *info;
+ pj_status_t status;
+
+ info = &strm->factory->dev_info[strm->param.cap_id];
+
+ PJ_LOG(4, (THIS_FILE, "Starting ffmpeg capture stream"));
+
+ status = ffmpeg_capture_open(&strm->ff_fmt_ctx, info->host_api,
+ info->def_devname, &strm->param);
+ if (status != PJ_SUCCESS) {
+ /* must set ffmpeg states to NULL on any failure */
+ strm->ff_fmt_ctx = NULL;
+ }
+
+ return status;
+}
+
+
+/* API: Get frame from stream */
+static pj_status_t ffmpeg_stream_get_frame(pjmedia_vid_dev_stream *s,
+ pjmedia_frame *frame)
+{
+ ffmpeg_stream *strm = (ffmpeg_stream*)s;
+ AVPacket p;
+ int err;
+
+ err = av_read_frame(strm->ff_fmt_ctx, &p);
+ if (err < 0) {
+ print_ffmpeg_err(err);
+ return PJ_EUNKNOWN;
+ }
+
+ pj_bzero(frame, sizeof(*frame));
+ frame->type = PJMEDIA_FRAME_TYPE_VIDEO;
+ frame->buf = p.data;
+ frame->size = p.size;
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Stop stream. */
+static pj_status_t ffmpeg_stream_stop(pjmedia_vid_dev_stream *s)
+{
+ ffmpeg_stream *strm = (ffmpeg_stream*)s;
+
+ PJ_LOG(4, (THIS_FILE, "Stopping ffmpeg capture stream"));
+
+ ffmpeg_capture_close(strm->ff_fmt_ctx);
+ strm->ff_fmt_ctx = NULL;
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t ffmpeg_stream_destroy(pjmedia_vid_dev_stream *s)
+{
+ ffmpeg_stream *strm = (ffmpeg_stream*)s;
+
+ PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL);
+
+ ffmpeg_stream_stop(s);
+
+ pj_pool_release(strm->pool);
+
+ return PJ_SUCCESS;
+}
+
+#ifdef _MSC_VER
+# pragma comment( lib, "avdevice.lib")
+# pragma comment( lib, "avformat.lib")
+# pragma comment( lib, "avutil.lib")
+#endif
+
+
+#endif /* PJMEDIA_VIDEO_DEV_HAS_FFMPEG */
diff --git a/pjmedia/src/pjmedia-videodev/ios_dev.m b/pjmedia/src/pjmedia-videodev/ios_dev.m
new file mode 100644
index 0000000..c842af9
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/ios_dev.m
@@ -0,0 +1,703 @@
+/* $Id: ios_dev.m 3979 2012-03-20 08:55:33Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia-videodev/videodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+
+#if PJMEDIA_VIDEO_DEV_HAS_IOS
+#include "Availability.h"
+#ifdef __IPHONE_4_0
+
+#import <UIKit/UIKit.h>
+#import <AVFoundation/AVFoundation.h>
+
+#define THIS_FILE "ios_dev.c"
+#define DEFAULT_CLOCK_RATE 90000
+#define DEFAULT_WIDTH 480
+#define DEFAULT_HEIGHT 360
+#define DEFAULT_FPS 15
+
+typedef struct ios_fmt_info
+{
+ pjmedia_format_id pjmedia_format;
+ UInt32 ios_format;
+} ios_fmt_info;
+
+static ios_fmt_info ios_fmts[] =
+{
+ {PJMEDIA_FORMAT_BGRA, kCVPixelFormatType_32BGRA} ,
+};
+
+/* qt device info */
+struct ios_dev_info
+{
+ pjmedia_vid_dev_info info;
+};
+
+/* qt factory */
+struct ios_factory
+{
+ pjmedia_vid_dev_factory base;
+ pj_pool_t *pool;
+ pj_pool_factory *pf;
+
+ unsigned dev_count;
+ struct ios_dev_info *dev_info;
+};
+
+@interface VOutDelegate: NSObject
+ <AVCaptureVideoDataOutputSampleBufferDelegate>
+{
+@public
+ struct ios_stream *stream;
+}
+@end
+
+/* Video stream. */
+struct ios_stream
+{
+ pjmedia_vid_dev_stream base; /**< Base stream */
+ pjmedia_vid_dev_param param; /**< Settings */
+ pj_pool_t *pool; /**< Memory pool */
+
+ pjmedia_vid_dev_cb vid_cb; /**< Stream callback */
+ void *user_data; /**< Application data */
+
+ pjmedia_rect_size size;
+ pj_uint8_t bpp;
+ unsigned bytes_per_row;
+ unsigned frame_size;
+
+ AVCaptureSession *cap_session;
+ AVCaptureDeviceInput *dev_input;
+ AVCaptureVideoDataOutput *video_output;
+ VOutDelegate *vout_delegate;
+
+ UIImageView *imgView;
+ void *buf;
+ dispatch_queue_t render_queue;
+
+ pj_timestamp frame_ts;
+ unsigned ts_inc;
+};
+
+
+/* Prototypes */
+static pj_status_t ios_factory_init(pjmedia_vid_dev_factory *f);
+static pj_status_t ios_factory_destroy(pjmedia_vid_dev_factory *f);
+static pj_status_t ios_factory_refresh(pjmedia_vid_dev_factory *f);
+static unsigned ios_factory_get_dev_count(pjmedia_vid_dev_factory *f);
+static pj_status_t ios_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_info *info);
+static pj_status_t ios_factory_default_param(pj_pool_t *pool,
+ pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_param *param);
+static pj_status_t ios_factory_create_stream(
+ pjmedia_vid_dev_factory *f,
+ pjmedia_vid_dev_param *param,
+ const pjmedia_vid_dev_cb *cb,
+ void *user_data,
+ pjmedia_vid_dev_stream **p_vid_strm);
+
+static pj_status_t ios_stream_get_param(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_param *param);
+static pj_status_t ios_stream_get_cap(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ void *value);
+static pj_status_t ios_stream_set_cap(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ const void *value);
+static pj_status_t ios_stream_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t ios_stream_put_frame(pjmedia_vid_dev_stream *strm,
+ const pjmedia_frame *frame);
+static pj_status_t ios_stream_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t ios_stream_destroy(pjmedia_vid_dev_stream *strm);
+
+/* Operations */
+static pjmedia_vid_dev_factory_op factory_op =
+{
+ &ios_factory_init,
+ &ios_factory_destroy,
+ &ios_factory_get_dev_count,
+ &ios_factory_get_dev_info,
+ &ios_factory_default_param,
+ &ios_factory_create_stream,
+ &ios_factory_refresh
+};
+
+static pjmedia_vid_dev_stream_op stream_op =
+{
+ &ios_stream_get_param,
+ &ios_stream_get_cap,
+ &ios_stream_set_cap,
+ &ios_stream_start,
+ NULL,
+ &ios_stream_put_frame,
+ &ios_stream_stop,
+ &ios_stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init ios_ video driver.
+ */
+pjmedia_vid_dev_factory* pjmedia_ios_factory(pj_pool_factory *pf)
+{
+ struct ios_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "ios video", 512, 512, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct ios_factory);
+ f->pf = pf;
+ f->pool = pool;
+ f->base.op = &factory_op;
+
+ return &f->base;
+}
+
+
+/* API: init factory */
+static pj_status_t ios_factory_init(pjmedia_vid_dev_factory *f)
+{
+ struct ios_factory *qf = (struct ios_factory*)f;
+ struct ios_dev_info *qdi;
+ unsigned i, l;
+
+ /* Initialize input and output devices here */
+ qf->dev_info = (struct ios_dev_info*)
+ pj_pool_calloc(qf->pool, 2,
+ sizeof(struct ios_dev_info));
+
+ qf->dev_count = 0;
+ qdi = &qf->dev_info[qf->dev_count++];
+ pj_bzero(qdi, sizeof(*qdi));
+ strcpy(qdi->info.name, "iOS UIView");
+ strcpy(qdi->info.driver, "iOS");
+ qdi->info.dir = PJMEDIA_DIR_RENDER;
+ qdi->info.has_callback = PJ_FALSE;
+ qdi->info.caps = PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
+
+ if (NSClassFromString(@"AVCaptureSession")) {
+ qdi = &qf->dev_info[qf->dev_count++];
+ pj_bzero(qdi, sizeof(*qdi));
+ strcpy(qdi->info.name, "iOS AVCapture");
+ strcpy(qdi->info.driver, "iOS");
+ qdi->info.dir = PJMEDIA_DIR_CAPTURE;
+ qdi->info.has_callback = PJ_TRUE;
+ }
+
+ for (i = 0; i < qf->dev_count; i++) {
+ qdi = &qf->dev_info[i];
+ qdi->info.fmt_cnt = PJ_ARRAY_SIZE(ios_fmts);
+ qdi->info.caps |= PJMEDIA_VID_DEV_CAP_FORMAT;
+
+ for (l = 0; l < PJ_ARRAY_SIZE(ios_fmts); l++) {
+ pjmedia_format *fmt = &qdi->info.fmt[l];
+ pjmedia_format_init_video(fmt,
+ ios_fmts[l].pjmedia_format,
+ DEFAULT_WIDTH,
+ DEFAULT_HEIGHT,
+ DEFAULT_FPS, 1);
+ }
+ }
+
+ PJ_LOG(4, (THIS_FILE, "iOS video initialized with %d devices",
+ qf->dev_count));
+
+ return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t ios_factory_destroy(pjmedia_vid_dev_factory *f)
+{
+ struct ios_factory *qf = (struct ios_factory*)f;
+ pj_pool_t *pool = qf->pool;
+
+ qf->pool = NULL;
+ pj_pool_release(pool);
+
+ return PJ_SUCCESS;
+}
+
+/* API: refresh the list of devices */
+static pj_status_t ios_factory_refresh(pjmedia_vid_dev_factory *f)
+{
+ PJ_UNUSED_ARG(f);
+ return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned ios_factory_get_dev_count(pjmedia_vid_dev_factory *f)
+{
+ struct ios_factory *qf = (struct ios_factory*)f;
+ return qf->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t ios_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_info *info)
+{
+ struct ios_factory *qf = (struct ios_factory*)f;
+
+ PJ_ASSERT_RETURN(index < qf->dev_count, PJMEDIA_EVID_INVDEV);
+
+ pj_memcpy(info, &qf->dev_info[index].info, sizeof(*info));
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t ios_factory_default_param(pj_pool_t *pool,
+ pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_param *param)
+{
+ struct ios_factory *qf = (struct ios_factory*)f;
+ struct ios_dev_info *di = &qf->dev_info[index];
+
+ PJ_ASSERT_RETURN(index < qf->dev_count, PJMEDIA_EVID_INVDEV);
+
+ PJ_UNUSED_ARG(pool);
+
+ pj_bzero(param, sizeof(*param));
+ if (di->info.dir & PJMEDIA_DIR_CAPTURE) {
+ param->dir = PJMEDIA_DIR_CAPTURE;
+ param->cap_id = index;
+ param->rend_id = PJMEDIA_VID_INVALID_DEV;
+ } else if (di->info.dir & PJMEDIA_DIR_RENDER) {
+ param->dir = PJMEDIA_DIR_RENDER;
+ param->rend_id = index;
+ param->cap_id = PJMEDIA_VID_INVALID_DEV;
+ } else {
+ return PJMEDIA_EVID_INVDEV;
+ }
+
+ param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
+ param->clock_rate = DEFAULT_CLOCK_RATE;
+ pj_memcpy(&param->fmt, &di->info.fmt[0], sizeof(param->fmt));
+
+ return PJ_SUCCESS;
+}
+
+@implementation VOutDelegate
+- (void)update_image
+{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ /* Create a device-dependent RGB color space */
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+
+ /* Create a bitmap graphics context with the sample buffer data */
+ CGContextRef context =
+ CGBitmapContextCreate(stream->buf, stream->size.w, stream->size.h, 8,
+ stream->bytes_per_row, colorSpace,
+ kCGBitmapByteOrder32Little |
+ kCGImageAlphaPremultipliedFirst);
+
+ /**
+ * Create a Quartz image from the pixel data in the bitmap graphics
+ * context
+ */
+ CGImageRef quartzImage = CGBitmapContextCreateImage(context);
+
+ /* Free up the context and color space */
+ CGContextRelease(context);
+ CGColorSpaceRelease(colorSpace);
+
+ /* Create an image object from the Quartz image */
+ UIImage *image = [UIImage imageWithCGImage:quartzImage scale:1.0
+ orientation:UIImageOrientationRight];
+
+ /* Release the Quartz image */
+ CGImageRelease(quartzImage);
+
+ dispatch_async(dispatch_get_main_queue(),
+ ^{[stream->imgView setImage:image];});
+ /*
+ [stream->imgView performSelectorOnMainThread:@selector(setImage:)
+ withObject:image waitUntilDone:NO];
+ */
+
+ [pool release];
+}
+
+- (void)captureOutput:(AVCaptureOutput *)captureOutput
+ didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
+ fromConnection:(AVCaptureConnection *)connection
+{
+ pjmedia_frame frame;
+ CVImageBufferRef imageBuffer;
+
+ if (!sampleBuffer)
+ return;
+
+ /* Get a CMSampleBuffer's Core Video image buffer for the media data */
+ imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
+
+ /* Lock the base address of the pixel buffer */
+ CVPixelBufferLockBaseAddress(imageBuffer, 0);
+
+ frame.type = PJMEDIA_FRAME_TYPE_VIDEO;
+ frame.buf = CVPixelBufferGetBaseAddress(imageBuffer);
+ frame.size = stream->frame_size;
+ frame.bit_info = 0;
+ frame.timestamp.u64 = stream->frame_ts.u64;
+
+ if (stream->vid_cb.capture_cb)
+ (*stream->vid_cb.capture_cb)(&stream->base, stream->user_data, &frame);
+
+ stream->frame_ts.u64 += stream->ts_inc;
+
+ /* Unlock the pixel buffer */
+ CVPixelBufferUnlockBaseAddress(imageBuffer,0);
+}
+@end
+
+static ios_fmt_info* get_ios_format_info(pjmedia_format_id id)
+{
+ unsigned i;
+
+ for (i = 0; i < PJ_ARRAY_SIZE(ios_fmts); i++) {
+ if (ios_fmts[i].pjmedia_format == id)
+ return &ios_fmts[i];
+ }
+
+ return NULL;
+}
+
+/* API: create stream */
+static pj_status_t ios_factory_create_stream(
+ pjmedia_vid_dev_factory *f,
+ pjmedia_vid_dev_param *param,
+ const pjmedia_vid_dev_cb *cb,
+ void *user_data,
+ pjmedia_vid_dev_stream **p_vid_strm)
+{
+ struct ios_factory *qf = (struct ios_factory*)f;
+ pj_pool_t *pool;
+ struct ios_stream *strm;
+ const pjmedia_video_format_detail *vfd;
+ const pjmedia_video_format_info *vfi;
+ pj_status_t status = PJ_SUCCESS;
+ ios_fmt_info *ifi = get_ios_format_info(param->fmt.id);
+ NSError *error;
+
+ PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL);
+ PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO &&
+ param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO &&
+ (param->dir == PJMEDIA_DIR_CAPTURE ||
+ param->dir == PJMEDIA_DIR_RENDER),
+ PJ_EINVAL);
+
+ if (!(ifi = get_ios_format_info(param->fmt.id)))
+ return PJMEDIA_EVID_BADFORMAT;
+
+ vfi = pjmedia_get_video_format_info(NULL, param->fmt.id);
+ if (!vfi)
+ return PJMEDIA_EVID_BADFORMAT;
+
+ /* Create and Initialize stream descriptor */
+ pool = pj_pool_create(qf->pf, "ios-dev", 4000, 4000, NULL);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ strm = PJ_POOL_ZALLOC_T(pool, struct ios_stream);
+ pj_memcpy(&strm->param, param, sizeof(*param));
+ strm->pool = pool;
+ pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
+ strm->user_data = user_data;
+
+ vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE);
+ pj_memcpy(&strm->size, &vfd->size, sizeof(vfd->size));
+ strm->bpp = vfi->bpp;
+ strm->bytes_per_row = strm->size.w * strm->bpp / 8;
+ strm->frame_size = strm->bytes_per_row * strm->size.h;
+ strm->ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1);
+
+ if (param->dir & PJMEDIA_DIR_CAPTURE) {
+ /* Create capture stream here */
+ strm->cap_session = [[AVCaptureSession alloc] init];
+ if (!strm->cap_session) {
+ status = PJ_ENOMEM;
+ goto on_error;
+ }
+ strm->cap_session.sessionPreset = AVCaptureSessionPresetMedium;
+
+ /* Open video device */
+ AVCaptureDevice *videoDevice =
+ [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
+ if (!videoDevice) {
+ status = PJMEDIA_EVID_SYSERR;
+ goto on_error;
+ }
+
+ /* Add the video device to the session as a device input */
+ strm->dev_input = [AVCaptureDeviceInput
+ deviceInputWithDevice:videoDevice
+ error: &error];
+ if (!strm->dev_input) {
+ status = PJMEDIA_EVID_SYSERR;
+ goto on_error;
+ }
+ [strm->cap_session addInput:strm->dev_input];
+
+ strm->video_output = [[[AVCaptureVideoDataOutput alloc] init]
+ autorelease];
+ if (!strm->video_output) {
+ status = PJMEDIA_EVID_SYSERR;
+ goto on_error;
+ }
+ [strm->cap_session addOutput:strm->video_output];
+
+ /* Configure the video output */
+ strm->vout_delegate = [VOutDelegate alloc];
+ strm->vout_delegate->stream = strm;
+ dispatch_queue_t queue = dispatch_queue_create("myQueue", NULL);
+ [strm->video_output setSampleBufferDelegate:strm->vout_delegate
+ queue:queue];
+ dispatch_release(queue);
+
+ strm->video_output.videoSettings =
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithInt:ifi->ios_format],
+ kCVPixelBufferPixelFormatTypeKey,
+ [NSNumber numberWithInt: vfd->size.w],
+ kCVPixelBufferWidthKey,
+ [NSNumber numberWithInt: vfd->size.h],
+ kCVPixelBufferHeightKey, nil];
+ strm->video_output.minFrameDuration = CMTimeMake(vfd->fps.denum,
+ vfd->fps.num);
+ } else if (param->dir & PJMEDIA_DIR_RENDER) {
+ /* Create renderer stream here */
+ /* Get the main window */
+ UIWindow *window = [[UIApplication sharedApplication] keyWindow];
+
+ if (param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW &&
+ param->window.info.ios.window)
+ window = (UIWindow *)param->window.info.ios.window;
+
+ pj_assert(window);
+ strm->imgView = [[UIImageView alloc] initWithFrame:[window bounds]];
+ if (!strm->imgView) {
+ status = PJ_ENOMEM;
+ goto on_error;
+ }
+ [window addSubview:strm->imgView];
+
+ if (!strm->vout_delegate) {
+ strm->vout_delegate = [VOutDelegate alloc];
+ strm->vout_delegate->stream = strm;
+ }
+
+ strm->render_queue = dispatch_queue_create("com.pjsip.render_queue",
+ NULL);
+ if (!strm->render_queue)
+ goto on_error;
+
+ strm->buf = pj_pool_alloc(pool, strm->frame_size);
+ }
+
+ /* Apply the remaining settings */
+ /*
+ if (param->flags & PJMEDIA_VID_DEV_CAP_INPUT_SCALE) {
+ ios_stream_set_cap(&strm->base,
+ PJMEDIA_VID_DEV_CAP_INPUT_SCALE,
+ &param->fmt);
+ }
+ */
+ /* Done */
+ strm->base.op = &stream_op;
+ *p_vid_strm = &strm->base;
+
+ return PJ_SUCCESS;
+
+on_error:
+ ios_stream_destroy((pjmedia_vid_dev_stream *)strm);
+
+ return status;
+}
+
+/* API: Get stream info. */
+static pj_status_t ios_stream_get_param(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_param *pi)
+{
+ struct ios_stream *strm = (struct ios_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+/* if (ios_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_INPUT_SCALE,
+ &pi->fmt.info_size) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_VID_DEV_CAP_INPUT_SCALE;
+ }
+*/
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t ios_stream_get_cap(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_cap cap,
+ void *pval)
+{
+ struct ios_stream *strm = (struct ios_stream*)s;
+
+ PJ_UNUSED_ARG(strm);
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE)
+ {
+ return PJMEDIA_EVID_INVCAP;
+// return PJ_SUCCESS;
+ } else {
+ return PJMEDIA_EVID_INVCAP;
+ }
+}
+
+/* API: set capability */
+static pj_status_t ios_stream_set_cap(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_cap cap,
+ const void *pval)
+{
+ struct ios_stream *strm = (struct ios_stream*)s;
+
+ PJ_UNUSED_ARG(strm);
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE)
+ {
+ return PJ_SUCCESS;
+ }
+
+ return PJMEDIA_EVID_INVCAP;
+}
+
+/* API: Start stream. */
+static pj_status_t ios_stream_start(pjmedia_vid_dev_stream *strm)
+{
+ struct ios_stream *stream = (struct ios_stream*)strm;
+
+ PJ_UNUSED_ARG(stream);
+
+ PJ_LOG(4, (THIS_FILE, "Starting ios video stream"));
+
+ if (stream->cap_session) {
+ [stream->cap_session startRunning];
+
+ if (![stream->cap_session isRunning])
+ return PJ_EUNKNOWN;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Put frame from stream */
+static pj_status_t ios_stream_put_frame(pjmedia_vid_dev_stream *strm,
+ const pjmedia_frame *frame)
+{
+ struct ios_stream *stream = (struct ios_stream*)strm;
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ pj_assert(stream->frame_size >= frame->size);
+ pj_memcpy(stream->buf, frame->buf, frame->size);
+ /* Perform video display in a background thread */
+/*
+ [stream->vout_delegate update_image];
+ [NSThread detachNewThreadSelector:@selector(update_image)
+ toTarget:stream->vout_delegate withObject:nil];
+*/
+ dispatch_async(stream->render_queue,
+ ^{[stream->vout_delegate update_image];});
+
+ [pool release];
+
+ return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t ios_stream_stop(pjmedia_vid_dev_stream *strm)
+{
+ struct ios_stream *stream = (struct ios_stream*)strm;
+
+ PJ_UNUSED_ARG(stream);
+
+ PJ_LOG(4, (THIS_FILE, "Stopping ios video stream"));
+
+ if (stream->cap_session && [stream->cap_session isRunning])
+ [stream->cap_session stopRunning];
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t ios_stream_destroy(pjmedia_vid_dev_stream *strm)
+{
+ struct ios_stream *stream = (struct ios_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+ ios_stream_stop(strm);
+
+ if (stream->imgView) {
+ [stream->imgView removeFromSuperview];
+ [stream->imgView release];
+ stream->imgView = NULL;
+ }
+
+ if (stream->cap_session) {
+ [stream->cap_session release];
+ stream->cap_session = NULL;
+ }
+/* if (stream->dev_input) {
+ [stream->dev_input release];
+ stream->dev_input = NULL;
+ }
+*/
+ if (stream->vout_delegate) {
+ [stream->vout_delegate release];
+ stream->vout_delegate = NULL;
+ }
+/* if (stream->video_output) {
+ [stream->video_output release];
+ stream->video_output = NULL;
+ }
+*/
+ if (stream->render_queue) {
+ dispatch_release(stream->render_queue);
+ stream->render_queue = NULL;
+ }
+
+ pj_pool_release(stream->pool);
+
+ return PJ_SUCCESS;
+}
+
+#endif
+#endif /* PJMEDIA_VIDEO_DEV_HAS_IOS */
diff --git a/pjmedia/src/pjmedia-videodev/qt_dev.m b/pjmedia/src/pjmedia-videodev/qt_dev.m
new file mode 100644
index 0000000..10dfe2e
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/qt_dev.m
@@ -0,0 +1,697 @@
+/* $Id: qt_dev.m 3979 2012-03-20 08:55:33Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia-videodev/videodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+
+#if PJMEDIA_VIDEO_DEV_HAS_QT
+
+#include <Foundation/NSAutoreleasePool.h>
+#include <QTKit/QTKit.h>
+
+#define THIS_FILE "qt_dev.c"
+#define DEFAULT_CLOCK_RATE 90000
+#define DEFAULT_WIDTH 640
+#define DEFAULT_HEIGHT 480
+#define DEFAULT_FPS 15
+
+#define kCVPixelFormatType_422YpCbCr8_yuvs 'yuvs'
+
+typedef struct qt_fmt_info
+{
+ pjmedia_format_id pjmedia_format;
+ unsigned qt_format;
+} qt_fmt_info;
+
+static qt_fmt_info qt_fmts[] =
+{
+ {PJMEDIA_FORMAT_YUY2, kCVPixelFormatType_422YpCbCr8_yuvs},
+ {PJMEDIA_FORMAT_UYVY, kCVPixelFormatType_422YpCbCr8},
+};
+
+/* qt device info */
+struct qt_dev_info
+{
+ pjmedia_vid_dev_info info;
+ char dev_id[192];
+};
+
+/* qt factory */
+struct qt_factory
+{
+ pjmedia_vid_dev_factory base;
+ pj_pool_t *pool;
+ pj_pool_t *dev_pool;
+ pj_pool_factory *pf;
+
+ unsigned dev_count;
+ struct qt_dev_info *dev_info;
+};
+
+struct qt_stream;
+typedef void (*func_ptr)(struct qt_stream *strm);
+
+@interface QTDelegate: NSObject
+{
+@public
+ struct qt_stream *strm;
+ func_ptr func;
+}
+
+- (void)run_func;
+@end
+
+/* Video stream. */
+struct qt_stream
+{
+ pjmedia_vid_dev_stream base; /**< Base stream */
+ pjmedia_vid_dev_param param; /**< Settings */
+ pj_pool_t *pool; /**< Memory pool. */
+
+ pj_timestamp cap_frame_ts; /**< Captured frame tstamp */
+ unsigned cap_ts_inc; /**< Increment */
+
+ pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */
+ void *user_data; /**< Application data. */
+
+ pj_bool_t cap_thread_exited;
+ pj_bool_t cap_thread_initialized;
+ pj_thread_desc cap_thread_desc;
+ pj_thread_t *cap_thread;
+
+ struct qt_factory *qf;
+ pj_status_t status;
+ pj_bool_t is_running;
+ pj_bool_t cap_exited;
+
+ QTCaptureSession *cap_session;
+ QTCaptureDeviceInput *dev_input;
+ QTCaptureDecompressedVideoOutput *video_output;
+ QTDelegate *qt_delegate;
+};
+
+
+/* Prototypes */
+static pj_status_t qt_factory_init(pjmedia_vid_dev_factory *f);
+static pj_status_t qt_factory_destroy(pjmedia_vid_dev_factory *f);
+static pj_status_t qt_factory_refresh(pjmedia_vid_dev_factory *f);
+static unsigned qt_factory_get_dev_count(pjmedia_vid_dev_factory *f);
+static pj_status_t qt_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_info *info);
+static pj_status_t qt_factory_default_param(pj_pool_t *pool,
+ pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_param *param);
+static pj_status_t qt_factory_create_stream(
+ pjmedia_vid_dev_factory *f,
+ pjmedia_vid_dev_param *param,
+ const pjmedia_vid_dev_cb *cb,
+ void *user_data,
+ pjmedia_vid_dev_stream **p_vid_strm);
+
+static pj_status_t qt_stream_get_param(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_param *param);
+static pj_status_t qt_stream_get_cap(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ void *value);
+static pj_status_t qt_stream_set_cap(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ const void *value);
+static pj_status_t qt_stream_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t qt_stream_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t qt_stream_destroy(pjmedia_vid_dev_stream *strm);
+
+/* Operations */
+static pjmedia_vid_dev_factory_op factory_op =
+{
+ &qt_factory_init,
+ &qt_factory_destroy,
+ &qt_factory_get_dev_count,
+ &qt_factory_get_dev_info,
+ &qt_factory_default_param,
+ &qt_factory_create_stream,
+ &qt_factory_refresh
+};
+
+static pjmedia_vid_dev_stream_op stream_op =
+{
+ &qt_stream_get_param,
+ &qt_stream_get_cap,
+ &qt_stream_set_cap,
+ &qt_stream_start,
+ NULL,
+ NULL,
+ &qt_stream_stop,
+ &qt_stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init qt_ video driver.
+ */
+pjmedia_vid_dev_factory* pjmedia_qt_factory(pj_pool_factory *pf)
+{
+ struct qt_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "qt video", 4000, 4000, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct qt_factory);
+ f->pf = pf;
+ f->pool = pool;
+ f->base.op = &factory_op;
+
+ return &f->base;
+}
+
+
+/* API: init factory */
+static pj_status_t qt_factory_init(pjmedia_vid_dev_factory *f)
+{
+ return qt_factory_refresh(f);
+}
+
+/* API: destroy factory */
+static pj_status_t qt_factory_destroy(pjmedia_vid_dev_factory *f)
+{
+ struct qt_factory *qf = (struct qt_factory*)f;
+ pj_pool_t *pool = qf->pool;
+
+ if (qf->dev_pool)
+ pj_pool_release(qf->dev_pool);
+ qf->pool = NULL;
+ if (pool)
+ pj_pool_release(pool);
+
+ return PJ_SUCCESS;
+}
+
+/* API: refresh the list of devices */
+static pj_status_t qt_factory_refresh(pjmedia_vid_dev_factory *f)
+{
+ struct qt_factory *qf = (struct qt_factory*)f;
+ struct qt_dev_info *qdi;
+ unsigned i, dev_count = 0;
+ NSAutoreleasePool *apool = [[NSAutoreleasePool alloc]init];
+ NSArray *dev_array;
+
+ if (qf->dev_pool) {
+ pj_pool_release(qf->dev_pool);
+ qf->dev_pool = NULL;
+ }
+
+ dev_array = [QTCaptureDevice inputDevices];
+ for (i = 0; i < [dev_array count]; i++) {
+ QTCaptureDevice *dev = [dev_array objectAtIndex:i];
+ if ([dev hasMediaType:QTMediaTypeVideo] ||
+ [dev hasMediaType:QTMediaTypeMuxed])
+ {
+ dev_count++;
+ }
+ }
+
+ /* Initialize input and output devices here */
+ qf->dev_count = 0;
+ qf->dev_pool = pj_pool_create(qf->pf, "qt video", 500, 500, NULL);
+
+ qf->dev_info = (struct qt_dev_info*)
+ pj_pool_calloc(qf->dev_pool, dev_count,
+ sizeof(struct qt_dev_info));
+ for (i = 0; i < [dev_array count]; i++) {
+ QTCaptureDevice *dev = [dev_array objectAtIndex:i];
+ if ([dev hasMediaType:QTMediaTypeVideo] ||
+ [dev hasMediaType:QTMediaTypeMuxed])
+ {
+ unsigned k;
+
+ qdi = &qf->dev_info[qf->dev_count++];
+ pj_bzero(qdi, sizeof(*qdi));
+ [[dev localizedDisplayName] getCString:qdi->info.name
+ maxLength:sizeof(qdi->info.name)
+ encoding:
+ [NSString defaultCStringEncoding]];
+ [[dev uniqueID] getCString:qdi->dev_id
+ maxLength:sizeof(qdi->dev_id)
+ encoding:[NSString defaultCStringEncoding]];
+ strcpy(qdi->info.driver, "QT");
+ qdi->info.dir = PJMEDIA_DIR_CAPTURE;
+ qdi->info.has_callback = PJ_TRUE;
+
+ qdi->info.fmt_cnt = 0;
+ qdi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT;
+ for (k = 0; k < [[dev formatDescriptions] count]; k++) {
+ unsigned l;
+ QTFormatDescription *desc = [[dev formatDescriptions]
+ objectAtIndex:k];
+ for (l = 0; l < PJ_ARRAY_SIZE(qt_fmts); l++) {
+ if ([desc formatType] == qt_fmts[l].qt_format) {
+ pjmedia_format *fmt =
+ &qdi->info.fmt[qdi->info.fmt_cnt++];
+ pjmedia_format_init_video(fmt,
+ qt_fmts[l].pjmedia_format,
+ DEFAULT_WIDTH,
+ DEFAULT_HEIGHT,
+ DEFAULT_FPS, 1);
+ break;
+ }
+ }
+ }
+
+ PJ_LOG(4, (THIS_FILE, " dev_id %d: %s", i, qdi->info.name));
+ }
+ }
+
+ [apool release];
+
+ PJ_LOG(4, (THIS_FILE, "qt video has %d devices",
+ qf->dev_count));
+
+ return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned qt_factory_get_dev_count(pjmedia_vid_dev_factory *f)
+{
+ struct qt_factory *qf = (struct qt_factory*)f;
+ return qf->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t qt_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_info *info)
+{
+ struct qt_factory *qf = (struct qt_factory*)f;
+
+ PJ_ASSERT_RETURN(index < qf->dev_count, PJMEDIA_EVID_INVDEV);
+
+ pj_memcpy(info, &qf->dev_info[index].info, sizeof(*info));
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t qt_factory_default_param(pj_pool_t *pool,
+ pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_param *param)
+{
+ struct qt_factory *qf = (struct qt_factory*)f;
+ struct qt_dev_info *di = &qf->dev_info[index];
+
+ PJ_ASSERT_RETURN(index < qf->dev_count, PJMEDIA_EVID_INVDEV);
+
+ PJ_UNUSED_ARG(pool);
+
+ pj_bzero(param, sizeof(*param));
+ param->dir = PJMEDIA_DIR_CAPTURE;
+ param->cap_id = index;
+ param->rend_id = PJMEDIA_VID_INVALID_DEV;
+ param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
+ param->clock_rate = DEFAULT_CLOCK_RATE;
+ pj_memcpy(&param->fmt, &di->info.fmt[0], sizeof(param->fmt));
+
+ return PJ_SUCCESS;
+}
+
+static qt_fmt_info* get_qt_format_info(pjmedia_format_id id)
+{
+ unsigned i;
+
+ for (i = 0; i < PJ_ARRAY_SIZE(qt_fmts); i++) {
+ if (qt_fmts[i].pjmedia_format == id)
+ return &qt_fmts[i];
+ }
+
+ return NULL;
+}
+
+@implementation QTDelegate
+- (void)captureOutput:(QTCaptureOutput *)captureOutput
+ didOutputVideoFrame:(CVImageBufferRef)videoFrame
+ withSampleBuffer:(QTSampleBuffer *)sampleBuffer
+ fromConnection:(QTCaptureConnection *)connection
+{
+ unsigned size = [sampleBuffer lengthForAllSamples];
+ pjmedia_frame frame;
+
+ if (!strm->is_running) {
+ strm->cap_exited = PJ_TRUE;
+ return;
+ }
+
+ if (strm->cap_thread_initialized == 0 || !pj_thread_is_registered())
+ {
+ pj_thread_register("qt_cap", strm->cap_thread_desc,
+ &strm->cap_thread);
+ strm->cap_thread_initialized = 1;
+ PJ_LOG(5,(THIS_FILE, "Capture thread started"));
+ }
+
+ if (!videoFrame)
+ return;
+
+ frame.type = PJMEDIA_FRAME_TYPE_VIDEO;
+ frame.buf = [sampleBuffer bytesForAllSamples];
+ frame.size = size;
+ frame.bit_info = 0;
+ frame.timestamp.u64 = strm->cap_frame_ts.u64;
+
+ if (strm->vid_cb.capture_cb)
+ (*strm->vid_cb.capture_cb)(&strm->base, strm->user_data, &frame);
+
+ strm->cap_frame_ts.u64 += strm->cap_ts_inc;
+}
+
+- (void)run_func
+{
+ (*func)(strm);
+}
+
+@end
+
+static void init_qt(struct qt_stream *strm)
+{
+ const pjmedia_video_format_detail *vfd;
+ qt_fmt_info *qfi = get_qt_format_info(strm->param.fmt.id);
+ BOOL success = NO;
+ NSError *error;
+
+ if (!qfi) {
+ strm->status = PJMEDIA_EVID_BADFORMAT;
+ return;
+ }
+
+ strm->cap_session = [[QTCaptureSession alloc] init];
+ if (!strm->cap_session) {
+ strm->status = PJ_ENOMEM;
+ return;
+ }
+
+ /* Open video device */
+ QTCaptureDevice *videoDevice =
+ [QTCaptureDevice deviceWithUniqueID:
+ [NSString stringWithCString:
+ strm->qf->dev_info[strm->param.cap_id].dev_id
+ encoding:
+ [NSString defaultCStringEncoding]]];
+ if (!videoDevice || ![videoDevice open:&error]) {
+ strm->status = PJMEDIA_EVID_SYSERR;
+ return;
+ }
+
+ /* Add the video device to the session as a device input */
+ strm->dev_input = [[QTCaptureDeviceInput alloc]
+ initWithDevice:videoDevice];
+ success = [strm->cap_session addInput:strm->dev_input error:&error];
+ if (!success) {
+ strm->status = PJMEDIA_EVID_SYSERR;
+ return;
+ }
+
+ strm->video_output = [[QTCaptureDecompressedVideoOutput alloc] init];
+ success = [strm->cap_session addOutput:strm->video_output
+ error:&error];
+ if (!success) {
+ strm->status = PJMEDIA_EVID_SYSERR;
+ return;
+ }
+
+ vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt,
+ PJ_TRUE);
+ [strm->video_output setPixelBufferAttributes:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithInt:qfi->qt_format],
+ kCVPixelBufferPixelFormatTypeKey,
+ [NSNumber numberWithInt:vfd->size.w],
+ kCVPixelBufferWidthKey,
+ [NSNumber numberWithInt:vfd->size.h],
+ kCVPixelBufferHeightKey, nil]];
+
+ pj_assert(vfd->fps.num);
+ strm->cap_ts_inc = PJMEDIA_SPF2(strm->param.clock_rate, &vfd->fps, 1);
+
+ if ([strm->video_output
+ respondsToSelector:@selector(setMinimumVideoFrameInterval)])
+ {
+ [strm->video_output setMinimumVideoFrameInterval:
+ (1.0f * vfd->fps.denum / (double)vfd->fps.num)];
+ }
+
+ strm->qt_delegate = [[QTDelegate alloc]init];
+ strm->qt_delegate->strm = strm;
+ [strm->video_output setDelegate:strm->qt_delegate];
+}
+
+static void run_func_on_main_thread(struct qt_stream *strm, func_ptr func)
+{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ QTDelegate *delg = [[QTDelegate alloc] init];
+
+ delg->strm = strm;
+ delg->func = func;
+ [delg performSelectorOnMainThread:@selector(run_func)
+ withObject:nil waitUntilDone:YES];
+
+ CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
+
+ [delg release];
+ [pool release];
+}
+
+/* API: create stream */
+static pj_status_t qt_factory_create_stream(
+ pjmedia_vid_dev_factory *f,
+ pjmedia_vid_dev_param *param,
+ const pjmedia_vid_dev_cb *cb,
+ void *user_data,
+ pjmedia_vid_dev_stream **p_vid_strm)
+{
+ struct qt_factory *qf = (struct qt_factory*)f;
+ pj_pool_t *pool;
+ struct qt_stream *strm;
+ const pjmedia_video_format_info *vfi;
+ pj_status_t status = PJ_SUCCESS;
+
+ PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL);
+ PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO &&
+ param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO &&
+ param->dir == PJMEDIA_DIR_CAPTURE,
+ PJ_EINVAL);
+
+ vfi = pjmedia_get_video_format_info(NULL, param->fmt.id);
+ if (!vfi)
+ return PJMEDIA_EVID_BADFORMAT;
+
+ /* Create and Initialize stream descriptor */
+ pool = pj_pool_create(qf->pf, "qt-dev", 4000, 4000, NULL);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ strm = PJ_POOL_ZALLOC_T(pool, struct qt_stream);
+ pj_memcpy(&strm->param, param, sizeof(*param));
+ strm->pool = pool;
+ pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
+ strm->user_data = user_data;
+ strm->qf = qf;
+
+ /* Create capture stream here */
+ if (param->dir & PJMEDIA_DIR_CAPTURE) {
+ strm->status = PJ_SUCCESS;
+ run_func_on_main_thread(strm, init_qt);
+ if ((status = strm->status) != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ /* Apply the remaining settings */
+ /*
+ if (param->flags & PJMEDIA_VID_DEV_CAP_INPUT_SCALE) {
+ qt_stream_set_cap(&strm->base,
+ PJMEDIA_VID_DEV_CAP_INPUT_SCALE,
+ &param->fmt);
+ }
+ */
+ /* Done */
+ strm->base.op = &stream_op;
+ *p_vid_strm = &strm->base;
+
+ return PJ_SUCCESS;
+
+on_error:
+ qt_stream_destroy((pjmedia_vid_dev_stream *)strm);
+
+ return status;
+}
+
+/* API: Get stream info. */
+static pj_status_t qt_stream_get_param(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_param *pi)
+{
+ struct qt_stream *strm = (struct qt_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+/* if (qt_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_INPUT_SCALE,
+ &pi->fmt.info_size) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_VID_DEV_CAP_INPUT_SCALE;
+ }
+*/
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t qt_stream_get_cap(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_cap cap,
+ void *pval)
+{
+ struct qt_stream *strm = (struct qt_stream*)s;
+
+ PJ_UNUSED_ARG(strm);
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE)
+ {
+ return PJMEDIA_EVID_INVCAP;
+// return PJ_SUCCESS;
+ } else {
+ return PJMEDIA_EVID_INVCAP;
+ }
+}
+
+/* API: set capability */
+static pj_status_t qt_stream_set_cap(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_cap cap,
+ const void *pval)
+{
+ struct qt_stream *strm = (struct qt_stream*)s;
+
+ PJ_UNUSED_ARG(strm);
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE)
+ {
+ return PJ_SUCCESS;
+ }
+
+ return PJMEDIA_EVID_INVCAP;
+}
+
+static void start_qt(struct qt_stream *strm)
+{
+ [strm->cap_session startRunning];
+}
+
+static void stop_qt(struct qt_stream *strm)
+{
+ [strm->cap_session stopRunning];
+}
+
+/* API: Start stream. */
+static pj_status_t qt_stream_start(pjmedia_vid_dev_stream *strm)
+{
+ struct qt_stream *stream = (struct qt_stream*)strm;
+
+ PJ_UNUSED_ARG(stream);
+
+ PJ_LOG(4, (THIS_FILE, "Starting qt video stream"));
+
+ if (stream->cap_session) {
+ run_func_on_main_thread(stream, start_qt);
+
+ if (![stream->cap_session isRunning])
+ return PJMEDIA_EVID_NOTREADY;
+
+ stream->is_running = PJ_TRUE;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t qt_stream_stop(pjmedia_vid_dev_stream *strm)
+{
+ struct qt_stream *stream = (struct qt_stream*)strm;
+
+ PJ_UNUSED_ARG(stream);
+
+ PJ_LOG(4, (THIS_FILE, "Stopping qt video stream"));
+
+ if (stream->cap_session && [stream->cap_session isRunning]) {
+ int i;
+
+ stream->cap_exited = PJ_FALSE;
+ run_func_on_main_thread(stream, stop_qt);
+
+ stream->is_running = PJ_FALSE;
+ for (i = 50; i >= 0 && !stream->cap_exited; i--) {
+ pj_thread_sleep(10);
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+static void destroy_qt(struct qt_stream *strm)
+{
+ if (strm->dev_input && [[strm->dev_input device] isOpen])
+ [[strm->dev_input device] close];
+
+ if (strm->cap_session) {
+ [strm->cap_session release];
+ strm->cap_session = NULL;
+ }
+ if (strm->dev_input) {
+ [strm->dev_input release];
+ strm->dev_input = NULL;
+ }
+ if (strm->qt_delegate) {
+ [strm->qt_delegate release];
+ strm->qt_delegate = NULL;
+ }
+ if (strm->video_output) {
+ [strm->video_output release];
+ strm->video_output = NULL;
+ }
+}
+
+/* API: Destroy stream. */
+static pj_status_t qt_stream_destroy(pjmedia_vid_dev_stream *strm)
+{
+ struct qt_stream *stream = (struct qt_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+ qt_stream_stop(strm);
+
+ run_func_on_main_thread(stream, destroy_qt);
+
+ pj_pool_release(stream->pool);
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_VIDEO_DEV_HAS_QT */
diff --git a/pjmedia/src/pjmedia-videodev/sdl_dev.c b/pjmedia/src/pjmedia-videodev/sdl_dev.c
new file mode 100644
index 0000000..7e40da1
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/sdl_dev.c
@@ -0,0 +1,1432 @@
+/* $Id: sdl_dev.c 4157 2012-06-06 09:37:25Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia-videodev/videodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+
+#if defined(PJMEDIA_VIDEO_DEV_HAS_SDL) && PJMEDIA_VIDEO_DEV_HAS_SDL != 0
+
+#include <SDL.h>
+#include <SDL_syswm.h>
+#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+# include "SDL_opengl.h"
+# define OPENGL_DEV_IDX 1
+#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+
+#if !(SDL_VERSION_ATLEAST(1,3,0))
+# error "SDL 1.3 or later is required"
+#endif
+
+#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
+# include "TargetConditionals.h"
+# include <Foundation/Foundation.h>
+#endif
+
+#define THIS_FILE "sdl_dev.c"
+#define DEFAULT_CLOCK_RATE 90000
+#define DEFAULT_WIDTH 640
+#define DEFAULT_HEIGHT 480
+#define DEFAULT_FPS 25
+
+typedef struct sdl_fmt_info
+{
+ pjmedia_format_id fmt_id;
+ Uint32 sdl_format;
+ Uint32 Rmask;
+ Uint32 Gmask;
+ Uint32 Bmask;
+ Uint32 Amask;
+} sdl_fmt_info;
+
+static sdl_fmt_info sdl_fmts[] =
+{
+#if PJ_IS_BIG_ENDIAN
+ {PJMEDIA_FORMAT_RGBA, (Uint32)SDL_PIXELFORMAT_RGBA8888,
+ 0xFF000000, 0xFF0000, 0xFF00, 0xFF} ,
+ {PJMEDIA_FORMAT_RGB24, (Uint32)SDL_PIXELFORMAT_RGB24,
+ 0xFF0000, 0xFF00, 0xFF, 0} ,
+ {PJMEDIA_FORMAT_BGRA, (Uint32)SDL_PIXELFORMAT_BGRA8888,
+ 0xFF00, 0xFF0000, 0xFF000000, 0xFF} ,
+#else /* PJ_IS_BIG_ENDIAN */
+ {PJMEDIA_FORMAT_RGBA, (Uint32)SDL_PIXELFORMAT_ABGR8888,
+ 0xFF, 0xFF00, 0xFF0000, 0xFF000000} ,
+ {PJMEDIA_FORMAT_RGB24, (Uint32)SDL_PIXELFORMAT_BGR24,
+ 0xFF, 0xFF00, 0xFF0000, 0} ,
+ {PJMEDIA_FORMAT_BGRA, (Uint32)SDL_PIXELFORMAT_ARGB8888,
+ 0xFF0000, 0xFF00, 0xFF, 0xFF000000} ,
+#endif /* PJ_IS_BIG_ENDIAN */
+
+ {PJMEDIA_FORMAT_DIB , (Uint32)SDL_PIXELFORMAT_RGB24,
+ 0xFF0000, 0xFF00, 0xFF, 0} ,
+
+ {PJMEDIA_FORMAT_YUY2, SDL_PIXELFORMAT_YUY2, 0, 0, 0, 0} ,
+ {PJMEDIA_FORMAT_UYVY, SDL_PIXELFORMAT_UYVY, 0, 0, 0, 0} ,
+ {PJMEDIA_FORMAT_YVYU, SDL_PIXELFORMAT_YVYU, 0, 0, 0, 0} ,
+ {PJMEDIA_FORMAT_I420, SDL_PIXELFORMAT_IYUV, 0, 0, 0, 0} ,
+ {PJMEDIA_FORMAT_YV12, SDL_PIXELFORMAT_YV12, 0, 0, 0, 0} ,
+ {PJMEDIA_FORMAT_I420JPEG, SDL_PIXELFORMAT_IYUV, 0, 0, 0, 0} ,
+ {PJMEDIA_FORMAT_I422JPEG, SDL_PIXELFORMAT_YV12, 0, 0, 0, 0}
+};
+
+/* sdl_ device info */
+struct sdl_dev_info
+{
+ pjmedia_vid_dev_info info;
+};
+
+/* Linked list of streams */
+struct stream_list
+{
+ PJ_DECL_LIST_MEMBER(struct stream_list);
+ struct sdl_stream *stream;
+};
+
+#define INITIAL_MAX_JOBS 64
+#define JOB_QUEUE_INC_FACTOR 2
+
+typedef pj_status_t (*job_func_ptr)(void *data);
+
+typedef struct job {
+ job_func_ptr func;
+ void *data;
+ unsigned flags;
+ pj_status_t retval;
+} job;
+
+#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
+@interface JQDelegate: NSObject
+{
+ @public
+ job *pjob;
+}
+
+- (void)run_job;
+@end
+
+@implementation JQDelegate
+- (void)run_job
+{
+ pjob->retval = (*pjob->func)(pjob->data);
+}
+@end
+#endif /* PJ_DARWINOS */
+
+typedef struct job_queue {
+ pj_pool_t *pool;
+ job **jobs;
+ pj_sem_t **job_sem;
+ pj_sem_t **old_sem;
+ pj_mutex_t *mutex;
+ pj_thread_t *thread;
+ pj_sem_t *sem;
+
+ unsigned size;
+ unsigned head, tail;
+ pj_bool_t is_full;
+ pj_bool_t is_quitting;
+} job_queue;
+
+/* sdl_ factory */
+struct sdl_factory
+{
+ pjmedia_vid_dev_factory base;
+ pj_pool_t *pool;
+ pj_pool_factory *pf;
+
+ unsigned dev_count;
+ struct sdl_dev_info *dev_info;
+ job_queue *jq;
+
+ pj_thread_t *sdl_thread; /**< SDL thread. */
+ pj_sem_t *sem;
+ pj_mutex_t *mutex;
+ struct stream_list streams;
+ pj_bool_t is_quitting;
+ pj_thread_desc thread_desc;
+ pj_thread_t *ev_thread;
+};
+
+/* Video stream. */
+struct sdl_stream
+{
+ pjmedia_vid_dev_stream base; /**< Base stream */
+ pjmedia_vid_dev_param param; /**< Settings */
+ pj_pool_t *pool; /**< Memory pool. */
+
+ pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */
+ void *user_data; /**< Application data. */
+
+ struct sdl_factory *sf;
+ const pjmedia_frame *frame;
+ pj_bool_t is_running;
+ pj_timestamp last_ts;
+ struct stream_list list_entry;
+
+ SDL_Window *window; /**< Display window. */
+ SDL_Renderer *renderer; /**< Display renderer. */
+ SDL_Texture *scr_tex; /**< Screen texture. */
+ int pitch; /**< Pitch value. */
+ SDL_Rect rect; /**< Frame rectangle. */
+ SDL_Rect dstrect; /**< Display rectangle. */
+#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+ SDL_GLContext *gl_context;
+ GLuint texture;
+#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+
+ pjmedia_video_apply_fmt_param vafp;
+};
+
+/* Prototypes */
+static pj_status_t sdl_factory_init(pjmedia_vid_dev_factory *f);
+static pj_status_t sdl_factory_destroy(pjmedia_vid_dev_factory *f);
+static pj_status_t sdl_factory_refresh(pjmedia_vid_dev_factory *f);
+static unsigned sdl_factory_get_dev_count(pjmedia_vid_dev_factory *f);
+static pj_status_t sdl_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_info *info);
+static pj_status_t sdl_factory_default_param(pj_pool_t *pool,
+ pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_param *param);
+static pj_status_t sdl_factory_create_stream(
+ pjmedia_vid_dev_factory *f,
+ pjmedia_vid_dev_param *param,
+ const pjmedia_vid_dev_cb *cb,
+ void *user_data,
+ pjmedia_vid_dev_stream **p_vid_strm);
+
+static pj_status_t sdl_stream_get_param(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_param *param);
+static pj_status_t sdl_stream_get_cap(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ void *value);
+static pj_status_t sdl_stream_set_cap(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ const void *value);
+static pj_status_t sdl_stream_put_frame(pjmedia_vid_dev_stream *strm,
+ const pjmedia_frame *frame);
+static pj_status_t sdl_stream_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t sdl_stream_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t sdl_stream_destroy(pjmedia_vid_dev_stream *strm);
+
+static pj_status_t resize_disp(struct sdl_stream *strm,
+ pjmedia_rect_size *new_disp_size);
+static pj_status_t sdl_destroy_all(void *data);
+
+/* Job queue prototypes */
+static pj_status_t job_queue_create(pj_pool_t *pool, job_queue **pjq);
+static pj_status_t job_queue_post_job(job_queue *jq, job_func_ptr func,
+ void *data, unsigned flags,
+ pj_status_t *retval);
+static pj_status_t job_queue_destroy(job_queue *jq);
+
+/* Operations */
+static pjmedia_vid_dev_factory_op factory_op =
+{
+ &sdl_factory_init,
+ &sdl_factory_destroy,
+ &sdl_factory_get_dev_count,
+ &sdl_factory_get_dev_info,
+ &sdl_factory_default_param,
+ &sdl_factory_create_stream,
+ &sdl_factory_refresh
+};
+
+static pjmedia_vid_dev_stream_op stream_op =
+{
+ &sdl_stream_get_param,
+ &sdl_stream_get_cap,
+ &sdl_stream_set_cap,
+ &sdl_stream_start,
+ NULL,
+ &sdl_stream_put_frame,
+ &sdl_stream_stop,
+ &sdl_stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init sdl_ video driver.
+ */
+pjmedia_vid_dev_factory* pjmedia_sdl_factory(pj_pool_factory *pf)
+{
+ struct sdl_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "sdl video", 1000, 1000, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct sdl_factory);
+ f->pf = pf;
+ f->pool = pool;
+ f->base.op = &factory_op;
+
+ return &f->base;
+}
+
+static pj_status_t sdl_init(void * data)
+{
+ PJ_UNUSED_ARG(data);
+
+ if (SDL_Init(SDL_INIT_VIDEO)) {
+ PJ_LOG(3, (THIS_FILE, "Failed initializing SDL"));
+ return PJMEDIA_EVID_INIT;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static struct sdl_stream* find_stream(struct sdl_factory *sf,
+ Uint32 windowID,
+ pjmedia_event *pevent)
+{
+ struct stream_list *it, *itBegin;
+ struct sdl_stream *strm = NULL;
+
+ itBegin = &sf->streams;
+ for (it = itBegin->next; it != itBegin; it = it->next) {
+ if (SDL_GetWindowID(it->stream->window) == windowID)
+ {
+ strm = it->stream;
+ break;
+ }
+ }
+
+ if (strm)
+ pjmedia_event_init(pevent, PJMEDIA_EVENT_NONE, &strm->last_ts,
+ strm);
+
+ return strm;
+}
+
+static pj_status_t handle_event(void *data)
+{
+ struct sdl_factory *sf = (struct sdl_factory*)data;
+ SDL_Event sevent;
+
+ if (!pj_thread_is_registered())
+ pj_thread_register("sdl_ev", sf->thread_desc, &sf->ev_thread);
+
+ while (SDL_PollEvent(&sevent)) {
+ struct sdl_stream *strm = NULL;
+ pjmedia_event pevent;
+
+ pj_mutex_lock(sf->mutex);
+ pevent.type = PJMEDIA_EVENT_NONE;
+ switch(sevent.type) {
+ case SDL_MOUSEBUTTONDOWN:
+ strm = find_stream(sf, sevent.button.windowID, &pevent);
+ pevent.type = PJMEDIA_EVENT_MOUSE_BTN_DOWN;
+ break;
+ case SDL_WINDOWEVENT:
+ strm = find_stream(sf, sevent.window.windowID, &pevent);
+ switch (sevent.window.event) {
+ case SDL_WINDOWEVENT_RESIZED:
+ pevent.type = PJMEDIA_EVENT_WND_RESIZED;
+ pevent.data.wnd_resized.new_size.w =
+ sevent.window.data1;
+ pevent.data.wnd_resized.new_size.h =
+ sevent.window.data2;
+ break;
+ case SDL_WINDOWEVENT_CLOSE:
+ pevent.type = PJMEDIA_EVENT_WND_CLOSING;
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (strm && pevent.type != PJMEDIA_EVENT_NONE) {
+ pj_status_t status;
+
+ pjmedia_event_publish(NULL, strm, &pevent, 0);
+
+ switch (pevent.type) {
+ case PJMEDIA_EVENT_WND_RESIZED:
+ status = resize_disp(strm, &pevent.data.wnd_resized.new_size);
+ if (status != PJ_SUCCESS)
+ PJ_LOG(3, (THIS_FILE, "Failed resizing the display."));
+ break;
+ case PJMEDIA_EVENT_WND_CLOSING:
+ if (pevent.data.wnd_closing.cancel) {
+ /* Cancel the closing operation */
+ break;
+ }
+
+ /* Proceed to cleanup SDL. App must still call
+ * pjmedia_dev_stream_destroy() when getting WND_CLOSED
+ * event
+ */
+ sdl_stream_stop(&strm->base);
+ sdl_destroy_all(strm);
+ pjmedia_event_init(&pevent, PJMEDIA_EVENT_WND_CLOSED,
+ &strm->last_ts, strm);
+ pjmedia_event_publish(NULL, strm, &pevent, 0);
+
+ /*
+ * Note: don't access the stream after this point, it
+ * might have been destroyed
+ */
+ break;
+ default:
+ /* Just to prevent gcc warning about unused enums */
+ break;
+ }
+ }
+
+ pj_mutex_unlock(sf->mutex);
+ }
+
+ return PJ_SUCCESS;
+}
+
+static int sdl_ev_thread(void *data)
+{
+ struct sdl_factory *sf = (struct sdl_factory*)data;
+
+ while(1) {
+ pj_status_t status;
+
+ pj_mutex_lock(sf->mutex);
+ if (pj_list_empty(&sf->streams)) {
+ pj_mutex_unlock(sf->mutex);
+ /* Wait until there is any stream. */
+ pj_sem_wait(sf->sem);
+ } else
+ pj_mutex_unlock(sf->mutex);
+
+ if (sf->is_quitting)
+ break;
+
+ job_queue_post_job(sf->jq, handle_event, sf, 0, &status);
+
+ pj_thread_sleep(50);
+ }
+
+ return 0;
+}
+
+static pj_status_t sdl_quit(void *data)
+{
+ PJ_UNUSED_ARG(data);
+ SDL_Quit();
+ return PJ_SUCCESS;
+}
+
+/* API: init factory */
+static pj_status_t sdl_factory_init(pjmedia_vid_dev_factory *f)
+{
+ struct sdl_factory *sf = (struct sdl_factory*)f;
+ struct sdl_dev_info *ddi;
+ unsigned i, j;
+ pj_status_t status;
+ SDL_version version;
+
+ status = job_queue_create(sf->pool, &sf->jq);
+ if (status != PJ_SUCCESS)
+ return PJMEDIA_EVID_INIT;
+
+ job_queue_post_job(sf->jq, sdl_init, NULL, 0, &status);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_list_init(&sf->streams);
+ status = pj_mutex_create_recursive(sf->pool, "sdl_factory",
+ &sf->mutex);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = pj_sem_create(sf->pool, NULL, 0, 1, &sf->sem);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Create event handler thread. */
+ status = pj_thread_create(sf->pool, "sdl_thread", sdl_ev_thread,
+ sf, 0, 0, &sf->sdl_thread);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ sf->dev_count = 1;
+#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+ sf->dev_count++;
+#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+ sf->dev_info = (struct sdl_dev_info*)
+ pj_pool_calloc(sf->pool, sf->dev_count,
+ sizeof(struct sdl_dev_info));
+
+ ddi = &sf->dev_info[0];
+ pj_bzero(ddi, sizeof(*ddi));
+ strncpy(ddi->info.name, "SDL renderer", sizeof(ddi->info.name));
+ ddi->info.name[sizeof(ddi->info.name)-1] = '\0';
+ ddi->info.fmt_cnt = PJ_ARRAY_SIZE(sdl_fmts);
+
+#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+ ddi = &sf->dev_info[OPENGL_DEV_IDX];
+ pj_bzero(ddi, sizeof(*ddi));
+ strncpy(ddi->info.name, "SDL openGL renderer", sizeof(ddi->info.name));
+ ddi->info.name[sizeof(ddi->info.name)-1] = '\0';
+ ddi->info.fmt_cnt = 1;
+#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+
+ for (i = 0; i < sf->dev_count; i++) {
+ ddi = &sf->dev_info[i];
+ strncpy(ddi->info.driver, "SDL", sizeof(ddi->info.driver));
+ ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0';
+ ddi->info.dir = PJMEDIA_DIR_RENDER;
+ ddi->info.has_callback = PJ_FALSE;
+ ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT |
+ PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE;
+ ddi->info.caps |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
+ ddi->info.caps |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
+
+ for (j = 0; j < ddi->info.fmt_cnt; j++) {
+ pjmedia_format *fmt = &ddi->info.fmt[j];
+ pjmedia_format_init_video(fmt, sdl_fmts[j].fmt_id,
+ DEFAULT_WIDTH, DEFAULT_HEIGHT,
+ DEFAULT_FPS, 1);
+ }
+ }
+
+ SDL_VERSION(&version);
+ PJ_LOG(4, (THIS_FILE, "SDL %d.%d initialized",
+ version.major, version.minor));
+
+ return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t sdl_factory_destroy(pjmedia_vid_dev_factory *f)
+{
+ struct sdl_factory *sf = (struct sdl_factory*)f;
+ pj_pool_t *pool = sf->pool;
+ pj_status_t status;
+
+ pj_assert(pj_list_empty(&sf->streams));
+
+ sf->is_quitting = PJ_TRUE;
+ if (sf->sdl_thread) {
+ pj_sem_post(sf->sem);
+#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
+ /* To prevent pj_thread_join() of getting stuck if we are in
+ * the main thread and we haven't finished processing the job
+ * posted by sdl_thread.
+ */
+ CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
+#endif
+ pj_thread_join(sf->sdl_thread);
+ pj_thread_destroy(sf->sdl_thread);
+ }
+
+ if (sf->mutex) {
+ pj_mutex_destroy(sf->mutex);
+ sf->mutex = NULL;
+ }
+
+ if (sf->sem) {
+ pj_sem_destroy(sf->sem);
+ sf->sem = NULL;
+ }
+
+ job_queue_post_job(sf->jq, sdl_quit, NULL, 0, &status);
+ job_queue_destroy(sf->jq);
+
+ sf->pool = NULL;
+ pj_pool_release(pool);
+
+ return PJ_SUCCESS;
+}
+
+/* API: refresh the list of devices */
+static pj_status_t sdl_factory_refresh(pjmedia_vid_dev_factory *f)
+{
+ PJ_UNUSED_ARG(f);
+ return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned sdl_factory_get_dev_count(pjmedia_vid_dev_factory *f)
+{
+ struct sdl_factory *sf = (struct sdl_factory*)f;
+ return sf->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t sdl_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_info *info)
+{
+ struct sdl_factory *sf = (struct sdl_factory*)f;
+
+ PJ_ASSERT_RETURN(index < sf->dev_count, PJMEDIA_EVID_INVDEV);
+
+ pj_memcpy(info, &sf->dev_info[index].info, sizeof(*info));
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t sdl_factory_default_param(pj_pool_t *pool,
+ pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_param *param)
+{
+ struct sdl_factory *sf = (struct sdl_factory*)f;
+ struct sdl_dev_info *di = &sf->dev_info[index];
+
+ PJ_ASSERT_RETURN(index < sf->dev_count, PJMEDIA_EVID_INVDEV);
+
+ PJ_UNUSED_ARG(pool);
+
+ pj_bzero(param, sizeof(*param));
+ param->dir = PJMEDIA_DIR_RENDER;
+ param->rend_id = index;
+ param->cap_id = PJMEDIA_VID_INVALID_DEV;
+
+ /* Set the device capabilities here */
+ param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
+ param->fmt.type = PJMEDIA_TYPE_VIDEO;
+ param->clock_rate = DEFAULT_CLOCK_RATE;
+ pj_memcpy(&param->fmt, &di->info.fmt[0], sizeof(param->fmt));
+
+ return PJ_SUCCESS;
+}
+
+static sdl_fmt_info* get_sdl_format_info(pjmedia_format_id id)
+{
+ unsigned i;
+
+ for (i = 0; i < sizeof(sdl_fmts)/sizeof(sdl_fmts[0]); i++) {
+ if (sdl_fmts[i].fmt_id == id)
+ return &sdl_fmts[i];
+ }
+
+ return NULL;
+}
+
+static pj_status_t sdl_destroy(void *data)
+{
+ struct sdl_stream *strm = (struct sdl_stream *)data;
+
+#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+ if (strm->texture) {
+ glDeleteTextures(1, &strm->texture);
+ strm->texture = 0;
+ }
+ if (strm->gl_context) {
+ SDL_GL_DeleteContext(strm->gl_context);
+ strm->gl_context = NULL;
+ }
+#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+ if (strm->scr_tex) {
+ SDL_DestroyTexture(strm->scr_tex);
+ strm->scr_tex = NULL;
+ }
+ if (strm->renderer) {
+ SDL_DestroyRenderer(strm->renderer);
+ strm->renderer = NULL;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t sdl_destroy_all(void *data)
+{
+ struct sdl_stream *strm = (struct sdl_stream *)data;
+
+ sdl_destroy(data);
+#if !defined(TARGET_OS_IPHONE) || TARGET_OS_IPHONE == 0
+ if (strm->window &&
+ !(strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW))
+ {
+ SDL_DestroyWindow(strm->window);
+ }
+ strm->window = NULL;
+#endif /* TARGET_OS_IPHONE */
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t sdl_create_rend(struct sdl_stream * strm,
+ pjmedia_format *fmt)
+{
+ sdl_fmt_info *sdl_info;
+ const pjmedia_video_format_info *vfi;
+ pjmedia_video_format_detail *vfd;
+
+ sdl_info = get_sdl_format_info(fmt->id);
+ vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(),
+ fmt->id);
+ if (!vfi || !sdl_info)
+ return PJMEDIA_EVID_BADFORMAT;
+
+ strm->vafp.size = fmt->det.vid.size;
+ strm->vafp.buffer = NULL;
+ if (vfi->apply_fmt(vfi, &strm->vafp) != PJ_SUCCESS)
+ return PJMEDIA_EVID_BADFORMAT;
+
+ vfd = pjmedia_format_get_video_format_detail(fmt, PJ_TRUE);
+ strm->rect.x = strm->rect.y = 0;
+ strm->rect.w = (Uint16)vfd->size.w;
+ strm->rect.h = (Uint16)vfd->size.h;
+ if (strm->param.disp_size.w == 0)
+ strm->param.disp_size.w = strm->rect.w;
+ if (strm->param.disp_size.h == 0)
+ strm->param.disp_size.h = strm->rect.h;
+ strm->dstrect.x = strm->dstrect.y = 0;
+ strm->dstrect.w = (Uint16)strm->param.disp_size.w;
+ strm->dstrect.h = (Uint16)strm->param.disp_size.h;
+
+ sdl_destroy(strm);
+
+#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+ if (strm->param.rend_id == OPENGL_DEV_IDX) {
+ SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);
+ }
+#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+
+ if (!strm->window) {
+ Uint32 flags = 0;
+
+ if (strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS) {
+ if (!(strm->param.window_flags & PJMEDIA_VID_DEV_WND_BORDER))
+ flags |= SDL_WINDOW_BORDERLESS;
+ if (strm->param.window_flags & PJMEDIA_VID_DEV_WND_RESIZABLE)
+ flags |= SDL_WINDOW_RESIZABLE;
+ } else {
+ flags |= SDL_WINDOW_BORDERLESS;
+ }
+
+ if (!((strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE) &&
+ strm->param.window_hide))
+ {
+ flags |= SDL_WINDOW_SHOWN;
+ } else {
+ flags &= ~SDL_WINDOW_SHOWN;
+ flags |= SDL_WINDOW_HIDDEN;
+ }
+
+#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+ if (strm->param.rend_id == OPENGL_DEV_IDX)
+ flags |= SDL_WINDOW_OPENGL;
+#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+
+ if (strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) {
+ /* Use the window supplied by the application. */
+ strm->window = SDL_CreateWindowFrom(
+ strm->param.window.info.window);
+ } else {
+ int x, y;
+
+ x = y = SDL_WINDOWPOS_CENTERED;
+ if (strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION) {
+ x = strm->param.window_pos.x;
+ y = strm->param.window_pos.y;
+ }
+
+ /* Create the window where we will draw. */
+ strm->window = SDL_CreateWindow("pjmedia-SDL video",
+ x, y,
+ strm->param.disp_size.w,
+ strm->param.disp_size.h,
+ flags);
+ }
+ if (!strm->window)
+ return PJMEDIA_EVID_SYSERR;
+ }
+
+ /**
+ * We must call SDL_CreateRenderer in order for draw calls to
+ * affect this window.
+ */
+ strm->renderer = SDL_CreateRenderer(strm->window, -1, 0);
+ if (!strm->renderer)
+ return PJMEDIA_EVID_SYSERR;
+
+#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+ if (strm->param.rend_id == OPENGL_DEV_IDX) {
+ strm->gl_context = SDL_GL_CreateContext(strm->window);
+ if (!strm->gl_context)
+ return PJMEDIA_EVID_SYSERR;
+ SDL_GL_MakeCurrent(strm->window, strm->gl_context);
+
+ /* Init some OpenGL settings */
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_CULL_FACE);
+ glEnable(GL_TEXTURE_2D);
+
+ /* Init the viewport */
+ glViewport(0, 0, strm->param.disp_size.w, strm->param.disp_size.h);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+
+ glOrtho(0.0, (GLdouble)strm->param.disp_size.w,
+ (GLdouble)strm->param.disp_size.h, 0.0, 0.0, 1.0);
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ /* Create a texture */
+ glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
+ glGenTextures(1, &strm->texture);
+
+ if (!strm->texture)
+ return PJMEDIA_EVID_SYSERR;
+ } else
+#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+ {
+ strm->scr_tex = SDL_CreateTexture(strm->renderer, sdl_info->sdl_format,
+ SDL_TEXTUREACCESS_STREAMING,
+ strm->rect.w, strm->rect.h);
+ if (strm->scr_tex == NULL)
+ return PJMEDIA_EVID_SYSERR;
+
+ strm->pitch = strm->rect.w * SDL_BYTESPERPIXEL(sdl_info->sdl_format);
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t sdl_create(void *data)
+{
+ struct sdl_stream *strm = (struct sdl_stream *)data;
+ return sdl_create_rend(strm, &strm->param.fmt);
+}
+
+static pj_status_t resize_disp(struct sdl_stream *strm,
+ pjmedia_rect_size *new_disp_size)
+{
+ pj_memcpy(&strm->param.disp_size, new_disp_size,
+ sizeof(strm->param.disp_size));
+
+ if (strm->scr_tex) {
+ strm->dstrect.x = strm->dstrect.y = 0;
+ strm->dstrect.w = (Uint16)strm->param.disp_size.w;
+ strm->dstrect.h = (Uint16)strm->param.disp_size.h;
+ SDL_RenderSetViewport(strm->renderer, &strm->dstrect);
+ }
+#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+ else if (strm->param.rend_id == OPENGL_DEV_IDX) {
+ sdl_create_rend(strm, &strm->param.fmt);
+ }
+#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t change_format(struct sdl_stream *strm,
+ pjmedia_format *new_fmt)
+{
+ pj_status_t status;
+
+ /* Recreate SDL renderer */
+ status = sdl_create_rend(strm, (new_fmt? new_fmt :
+ &strm->param.fmt));
+ if (status == PJ_SUCCESS && new_fmt)
+ pjmedia_format_copy(&strm->param.fmt, new_fmt);
+
+ return status;
+}
+
+static pj_status_t put_frame(void *data)
+{
+ struct sdl_stream *stream = (struct sdl_stream *)data;
+ const pjmedia_frame *frame = stream->frame;
+
+ if (stream->scr_tex) {
+ SDL_UpdateTexture(stream->scr_tex, NULL, frame->buf, stream->pitch);
+ SDL_RenderClear(stream->renderer);
+ SDL_RenderCopy(stream->renderer, stream->scr_tex,
+ &stream->rect, &stream->dstrect);
+ SDL_RenderPresent(stream->renderer);
+ }
+#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+ else if (stream->param.rend_id == OPENGL_DEV_IDX && stream->texture) {
+ glBindTexture(GL_TEXTURE_2D, stream->texture);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
+ stream->rect.w, stream->rect.h, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, frame->buf);
+ glBegin(GL_TRIANGLE_STRIP);
+ glTexCoord2f(0, 0); glVertex2i(0, 0);
+ glTexCoord2f(1, 0); glVertex2i(stream->param.disp_size.w, 0);
+ glTexCoord2f(0, 1); glVertex2i(0, stream->param.disp_size.h);
+ glTexCoord2f(1, 1);
+ glVertex2i(stream->param.disp_size.w, stream->param.disp_size.h);
+ glEnd();
+ SDL_GL_SwapWindow(stream->window);
+ }
+#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+
+ return PJ_SUCCESS;
+}
+
+/* API: Put frame from stream */
+static pj_status_t sdl_stream_put_frame(pjmedia_vid_dev_stream *strm,
+ const pjmedia_frame *frame)
+{
+ struct sdl_stream *stream = (struct sdl_stream*)strm;
+ pj_status_t status;
+
+ stream->last_ts.u64 = frame->timestamp.u64;
+
+ if (!stream->is_running)
+ return PJ_EINVALIDOP;
+
+ if (frame->size==0 || frame->buf==NULL ||
+ frame->size < stream->vafp.framebytes)
+ return PJ_SUCCESS;
+
+ stream->frame = frame;
+ job_queue_post_job(stream->sf->jq, put_frame, strm, 0, &status);
+
+ return status;
+}
+
+/* API: create stream */
+static pj_status_t sdl_factory_create_stream(
+ pjmedia_vid_dev_factory *f,
+ pjmedia_vid_dev_param *param,
+ const pjmedia_vid_dev_cb *cb,
+ void *user_data,
+ pjmedia_vid_dev_stream **p_vid_strm)
+{
+ struct sdl_factory *sf = (struct sdl_factory*)f;
+ pj_pool_t *pool;
+ struct sdl_stream *strm;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_RENDER, PJ_EINVAL);
+
+ /* Create and Initialize stream descriptor */
+ pool = pj_pool_create(sf->pf, "sdl-dev", 1000, 1000, NULL);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ strm = PJ_POOL_ZALLOC_T(pool, struct sdl_stream);
+ pj_memcpy(&strm->param, param, sizeof(*param));
+ strm->pool = pool;
+ strm->sf = sf;
+ pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
+ pj_list_init(&strm->list_entry);
+ strm->list_entry.stream = strm;
+ strm->user_data = user_data;
+
+ /* Create render stream here */
+ job_queue_post_job(sf->jq, sdl_create, strm, 0, &status);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+ pj_mutex_lock(strm->sf->mutex);
+ if (pj_list_empty(&strm->sf->streams))
+ pj_sem_post(strm->sf->sem);
+ pj_list_insert_after(&strm->sf->streams, &strm->list_entry);
+ pj_mutex_unlock(strm->sf->mutex);
+
+ /* Done */
+ strm->base.op = &stream_op;
+ *p_vid_strm = &strm->base;
+
+ return PJ_SUCCESS;
+
+on_error:
+ sdl_stream_destroy(&strm->base);
+ return status;
+}
+
+/* API: Get stream info. */
+static pj_status_t sdl_stream_get_param(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_param *pi)
+{
+ struct sdl_stream *strm = (struct sdl_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+ if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW,
+ &pi->window) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
+ }
+ if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION,
+ &pi->window_pos) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION;
+ }
+ if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE,
+ &pi->disp_size) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE;
+ }
+ if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE,
+ &pi->window_hide) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE;
+ }
+ if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS,
+ &pi->window_flags) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
+ }
+
+ return PJ_SUCCESS;
+}
+
+struct strm_cap {
+ struct sdl_stream *strm;
+ pjmedia_vid_dev_cap cap;
+ union {
+ void *pval;
+ const void *cpval;
+ } pval;
+};
+
+static pj_status_t get_cap(void *data)
+{
+ struct strm_cap *scap = (struct strm_cap *)data;
+ struct sdl_stream *strm = scap->strm;
+ pjmedia_vid_dev_cap cap = scap->cap;
+ void *pval = scap->pval.pval;
+
+ if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW)
+ {
+ SDL_SysWMinfo info;
+ SDL_VERSION(&info.version);
+
+ if (SDL_GetWindowWMInfo(strm->window, &info)) {
+ pjmedia_vid_dev_hwnd *wnd = (pjmedia_vid_dev_hwnd *)pval;
+ if (0) { }
+#if defined(SDL_VIDEO_DRIVER_WINDOWS)
+ else if (info.subsystem == SDL_SYSWM_WINDOWS) {
+ wnd->type = PJMEDIA_VID_DEV_HWND_TYPE_WINDOWS;
+ wnd->info.win.hwnd = (void *)info.info.win.window;
+ }
+#endif
+#if defined(SDL_VIDEO_DRIVER_X11)
+ else if (info.subsystem == SDL_SYSWM_X11) {
+ wnd->info.x11.window = (void *)info.info.x11.window;
+ wnd->info.x11.display = (void *)info.info.x11.display;
+ }
+#endif
+#if defined(SDL_VIDEO_DRIVER_COCOA)
+ else if (info.subsystem == SDL_SYSWM_COCOA) {
+ wnd->info.cocoa.window = (void *)info.info.cocoa.window;
+ }
+#endif
+#if defined(SDL_VIDEO_DRIVER_UIKIT)
+ else if (info.subsystem == SDL_SYSWM_UIKIT) {
+ wnd->info.ios.window = (void *)info.info.uikit.window;
+ }
+#endif
+ else {
+ return PJMEDIA_EVID_INVCAP;
+ }
+ return PJ_SUCCESS;
+ } else
+ return PJMEDIA_EVID_INVCAP;
+ } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION) {
+ SDL_GetWindowPosition(strm->window, &((pjmedia_coord *)pval)->x,
+ &((pjmedia_coord *)pval)->y);
+ return PJ_SUCCESS;
+ } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE) {
+ SDL_GetWindowSize(strm->window, (int *)&((pjmedia_rect_size *)pval)->w,
+ (int *)&((pjmedia_rect_size *)pval)->h);
+ return PJ_SUCCESS;
+ } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE) {
+ Uint32 flag = SDL_GetWindowFlags(strm->window);
+ *((pj_bool_t *)pval) = (flag & SDL_WINDOW_HIDDEN)? PJ_TRUE: PJ_FALSE;
+ return PJ_SUCCESS;
+ } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS) {
+ Uint32 flag = SDL_GetWindowFlags(strm->window);
+ unsigned *wnd_flags = (unsigned *)pval;
+ if (!(flag & SDL_WINDOW_BORDERLESS))
+ *wnd_flags |= PJMEDIA_VID_DEV_WND_BORDER;
+ if (flag & SDL_WINDOW_RESIZABLE)
+ *wnd_flags |= PJMEDIA_VID_DEV_WND_RESIZABLE;
+ return PJ_SUCCESS;
+ }
+
+ return PJMEDIA_EVID_INVCAP;
+}
+
+/* API: get capability */
+static pj_status_t sdl_stream_get_cap(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_cap cap,
+ void *pval)
+{
+ struct sdl_stream *strm = (struct sdl_stream*)s;
+ struct strm_cap scap;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ scap.strm = strm;
+ scap.cap = cap;
+ scap.pval.pval = pval;
+
+ job_queue_post_job(strm->sf->jq, get_cap, &scap, 0, &status);
+
+ return status;
+}
+
+static pj_status_t set_cap(void *data)
+{
+ struct strm_cap *scap = (struct strm_cap *)data;
+ struct sdl_stream *strm = scap->strm;
+ pjmedia_vid_dev_cap cap = scap->cap;
+ const void *pval = scap->pval.cpval;
+
+ if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION) {
+ /**
+ * Setting window's position when the window is hidden also sets
+ * the window's flag to shown (while the window is, actually,
+ * still hidden). This causes problems later when setting/querying
+ * the window's visibility.
+ * See ticket #1429 (http://trac.pjsip.org/repos/ticket/1429)
+ */
+ Uint32 flag = SDL_GetWindowFlags(strm->window);
+ if (flag & SDL_WINDOW_HIDDEN)
+ SDL_ShowWindow(strm->window);
+ SDL_SetWindowPosition(strm->window, ((pjmedia_coord *)pval)->x,
+ ((pjmedia_coord *)pval)->y);
+ if (flag & SDL_WINDOW_HIDDEN)
+ SDL_HideWindow(strm->window);
+ return PJ_SUCCESS;
+ } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE) {
+ if (*(pj_bool_t *)pval)
+ SDL_HideWindow(strm->window);
+ else
+ SDL_ShowWindow(strm->window);
+ return PJ_SUCCESS;
+ } else if (cap == PJMEDIA_VID_DEV_CAP_FORMAT) {
+ pj_status_t status;
+
+ status = change_format(strm, (pjmedia_format *)pval);
+ if (status != PJ_SUCCESS) {
+ pj_status_t status_;
+
+ /**
+ * Failed to change the output format. Try to revert
+ * to its original format.
+ */
+ status_ = change_format(strm, &strm->param.fmt);
+ if (status_ != PJ_SUCCESS) {
+ /**
+ * This means that we failed to revert to our
+ * original state!
+ */
+ status = PJMEDIA_EVID_ERR;
+ }
+ }
+
+ return status;
+ } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE) {
+ pjmedia_rect_size *new_size = (pjmedia_rect_size *)pval;
+
+ SDL_SetWindowSize(strm->window, new_size->w, new_size->h);
+ return resize_disp(strm, new_size);
+ }
+
+ return PJMEDIA_EVID_INVCAP;
+}
+
+/* API: set capability */
+static pj_status_t sdl_stream_set_cap(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_cap cap,
+ const void *pval)
+{
+ struct sdl_stream *strm = (struct sdl_stream*)s;
+ struct strm_cap scap;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ scap.strm = strm;
+ scap.cap = cap;
+ scap.pval.cpval = pval;
+
+ job_queue_post_job(strm->sf->jq, set_cap, &scap, 0, &status);
+
+ return status;
+}
+
+/* API: Start stream. */
+static pj_status_t sdl_stream_start(pjmedia_vid_dev_stream *strm)
+{
+ struct sdl_stream *stream = (struct sdl_stream*)strm;
+
+ PJ_LOG(4, (THIS_FILE, "Starting sdl video stream"));
+
+ stream->is_running = PJ_TRUE;
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Stop stream. */
+static pj_status_t sdl_stream_stop(pjmedia_vid_dev_stream *strm)
+{
+ struct sdl_stream *stream = (struct sdl_stream*)strm;
+
+ PJ_LOG(4, (THIS_FILE, "Stopping sdl video stream"));
+
+ stream->is_running = PJ_FALSE;
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t sdl_stream_destroy(pjmedia_vid_dev_stream *strm)
+{
+ struct sdl_stream *stream = (struct sdl_stream*)strm;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+ sdl_stream_stop(strm);
+
+ job_queue_post_job(stream->sf->jq, sdl_destroy_all, strm, 0, &status);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_mutex_lock(stream->sf->mutex);
+ if (!pj_list_empty(&stream->list_entry))
+ pj_list_erase(&stream->list_entry);
+ pj_mutex_unlock(stream->sf->mutex);
+
+ pj_pool_release(stream->pool);
+
+ return PJ_SUCCESS;
+}
+
+/****************************************************************************
+ * Job queue implementation
+ */
+#if PJ_DARWINOS==0
+static int job_thread(void * data)
+{
+ job_queue *jq = (job_queue *)data;
+
+ while (1) {
+ job *jb;
+
+ /* Wait until there is a job. */
+ pj_sem_wait(jq->sem);
+
+ /* Make sure there is no pending jobs before we quit. */
+ if (jq->is_quitting && jq->head == jq->tail && !jq->is_full)
+ break;
+
+ jb = jq->jobs[jq->head];
+ jb->retval = (*jb->func)(jb->data);
+ /* If job queue is full and we already finish all the pending
+ * jobs, increase the size.
+ */
+ if (jq->is_full && ((jq->head + 1) % jq->size == jq->tail)) {
+ unsigned i, head;
+ pj_status_t status;
+
+ if (jq->old_sem) {
+ for (i = 0; i < jq->size / JOB_QUEUE_INC_FACTOR; i++) {
+ pj_sem_destroy(jq->old_sem[i]);
+ }
+ }
+ jq->old_sem = jq->job_sem;
+
+ /* Double the job queue size. */
+ jq->size *= JOB_QUEUE_INC_FACTOR;
+ pj_sem_destroy(jq->sem);
+ status = pj_sem_create(jq->pool, "thread_sem", 0, jq->size + 1,
+ &jq->sem);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Failed growing SDL job queue size."));
+ return 0;
+ }
+ jq->jobs = (job **)pj_pool_calloc(jq->pool, jq->size,
+ sizeof(job *));
+ jq->job_sem = (pj_sem_t **) pj_pool_calloc(jq->pool, jq->size,
+ sizeof(pj_sem_t *));
+ for (i = 0; i < jq->size; i++) {
+ status = pj_sem_create(jq->pool, "job_sem", 0, 1,
+ &jq->job_sem[i]);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Failed growing SDL job "
+ "queue size."));
+ return 0;
+ }
+ }
+ jq->is_full = PJ_FALSE;
+ head = jq->head;
+ jq->head = jq->tail = 0;
+ pj_sem_post(jq->old_sem[head]);
+ } else {
+ pj_sem_post(jq->job_sem[jq->head]);
+ jq->head = (jq->head + 1) % jq->size;
+ }
+ }
+
+ return 0;
+}
+#endif
+
+static pj_status_t job_queue_create(pj_pool_t *pool, job_queue **pjq)
+{
+ unsigned i;
+ pj_status_t status;
+
+ job_queue *jq = PJ_POOL_ZALLOC_T(pool, job_queue);
+ jq->pool = pool;
+ jq->size = INITIAL_MAX_JOBS;
+ status = pj_sem_create(pool, "thread_sem", 0, jq->size + 1, &jq->sem);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ jq->jobs = (job **)pj_pool_calloc(pool, jq->size, sizeof(job *));
+ jq->job_sem = (pj_sem_t **) pj_pool_calloc(pool, jq->size,
+ sizeof(pj_sem_t *));
+ for (i = 0; i < jq->size; i++) {
+ status = pj_sem_create(pool, "job_sem", 0, 1, &jq->job_sem[i]);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ status = pj_mutex_create_recursive(pool, "job_mutex", &jq->mutex);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
+ PJ_UNUSED_ARG(status);
+#else
+ status = pj_thread_create(pool, "job_th", job_thread, jq, 0, 0,
+ &jq->thread);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+#endif /* PJ_DARWINOS */
+
+ *pjq = jq;
+ return PJ_SUCCESS;
+
+on_error:
+ job_queue_destroy(jq);
+ return status;
+}
+
+static pj_status_t job_queue_post_job(job_queue *jq, job_func_ptr func,
+ void *data, unsigned flags,
+ pj_status_t *retval)
+{
+ job jb;
+ int tail;
+
+ if (jq->is_quitting)
+ return PJ_EBUSY;
+
+ jb.func = func;
+ jb.data = data;
+ jb.flags = flags;
+
+#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
+ PJ_UNUSED_ARG(tail);
+ NSAutoreleasePool *apool = [[NSAutoreleasePool alloc]init];
+ JQDelegate *jqd = [[JQDelegate alloc]init];
+ jqd->pjob = &jb;
+ [jqd performSelectorOnMainThread:@selector(run_job)
+ withObject:nil waitUntilDone:YES];
+ [jqd release];
+ [apool release];
+#else /* PJ_DARWINOS */
+ pj_mutex_lock(jq->mutex);
+ jq->jobs[jq->tail] = &jb;
+ tail = jq->tail;
+ jq->tail = (jq->tail + 1) % jq->size;
+ if (jq->tail == jq->head) {
+ jq->is_full = PJ_TRUE;
+ PJ_LOG(4, (THIS_FILE, "SDL job queue is full, increasing "
+ "the queue size."));
+ pj_sem_post(jq->sem);
+ /* Wait until our posted job is completed. */
+ pj_sem_wait(jq->job_sem[tail]);
+ pj_mutex_unlock(jq->mutex);
+ } else {
+ pj_mutex_unlock(jq->mutex);
+ pj_sem_post(jq->sem);
+ /* Wait until our posted job is completed. */
+ pj_sem_wait(jq->job_sem[tail]);
+ }
+#endif /* PJ_DARWINOS */
+
+ *retval = jb.retval;
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t job_queue_destroy(job_queue *jq)
+{
+ unsigned i;
+
+ jq->is_quitting = PJ_TRUE;
+
+ if (jq->thread) {
+ pj_sem_post(jq->sem);
+ pj_thread_join(jq->thread);
+ pj_thread_destroy(jq->thread);
+ }
+
+ if (jq->sem) {
+ pj_sem_destroy(jq->sem);
+ jq->sem = NULL;
+ }
+ for (i = 0; i < jq->size; i++) {
+ if (jq->job_sem[i]) {
+ pj_sem_destroy(jq->job_sem[i]);
+ jq->job_sem[i] = NULL;
+ }
+ }
+ if (jq->old_sem) {
+ for (i = 0; i < jq->size / JOB_QUEUE_INC_FACTOR; i++) {
+ if (jq->old_sem[i]) {
+ pj_sem_destroy(jq->old_sem[i]);
+ jq->old_sem[i] = NULL;
+ }
+ }
+ }
+ if (jq->mutex) {
+ pj_mutex_destroy(jq->mutex);
+ jq->mutex = NULL;
+ }
+
+ return PJ_SUCCESS;
+}
+
+#ifdef _MSC_VER
+# if SDL_VERSION_ATLEAST(2,0,0)
+# pragma comment( lib, "sdl2.lib")
+# elif SDL_VERSION_ATLEAST(1,3,0)
+# pragma comment( lib, "sdl.lib")
+# endif
+# if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+# pragma comment(lib, "OpenGL32.lib")
+# endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+#endif /* _MSC_VER */
+
+
+#endif /* PJMEDIA_VIDEO_DEV_HAS_SDL */
diff --git a/pjmedia/src/pjmedia-videodev/sdl_dev_m.m b/pjmedia/src/pjmedia-videodev/sdl_dev_m.m
new file mode 100644
index 0000000..f6b5d0e
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/sdl_dev_m.m
@@ -0,0 +1,20 @@
+/* $Id: sdl_dev_m.m 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 "sdl_dev.c"
diff --git a/pjmedia/src/pjmedia-videodev/v4l2_dev.c b/pjmedia/src/pjmedia-videodev/v4l2_dev.c
new file mode 100644
index 0000000..880d47b
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/v4l2_dev.c
@@ -0,0 +1,819 @@
+/* $Id: v4l2_dev.c 3901 2011-12-07 10:43:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia-videodev/videodev_imp.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/file_access.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/rand.h>
+
+#if PJMEDIA_VIDEO_DEV_HAS_V4L2
+
+#include <linux/videodev2.h>
+#include <libv4l2.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/mman.h>
+
+#define THIS_FILE "v4l2_dev.c"
+#define DRIVER_NAME "v4l2"
+#define V4L2_MAX_DEVS 4
+#define DEFAULT_WIDTH 640
+#define DEFAULT_HEIGHT 480
+#define DEFAULT_FPS 25
+#define DEFAULT_CLOCK_RATE 90000
+#define INVALID_FD -1
+#define BUFFER_CNT 2
+#define MAX_IOCTL_RETRY 20
+
+
+/* mapping between pjmedia_fmt_id and v4l2 pixel format */
+typedef struct vid4lin_fmt_map
+{
+ pj_uint32_t pjmedia_fmt_id;
+ pj_uint32_t v4l2_fmt_id;
+} vid4lin_fmt_map;
+
+/* I/O type being used */
+enum vid4lin_io_type
+{
+ IO_TYPE_NONE,
+ IO_TYPE_READ,
+ IO_TYPE_MMAP,
+ IO_TYPE_MMAP_USER
+};
+
+/* descriptor for each mmap-ed buffer */
+typedef struct vid4lin_buffer
+{
+ void *start;
+ size_t length;
+} vid4lin_buffer;
+
+/* v4l2 device info */
+typedef struct vid4lin_dev_info
+{
+ pjmedia_vid_dev_info info;
+ char dev_name[32];
+ struct v4l2_capability v4l2_cap;
+} vid4lin_dev_info;
+
+/* v4l2 factory */
+typedef struct vid4lin_factory
+{
+ pjmedia_vid_dev_factory base;
+ pj_pool_t *pool;
+ pj_pool_t *dev_pool;
+ pj_pool_factory *pf;
+
+ unsigned dev_count;
+ vid4lin_dev_info *dev_info;
+} vid4lin_factory;
+
+/* Video stream. */
+typedef struct vid4lin_stream
+{
+ pjmedia_vid_dev_stream base; /**< Base stream */
+ pjmedia_vid_dev_param param; /**< Settings */
+ pj_pool_t *pool; /**< Memory pool. */
+
+ int fd; /**< Video fd. */
+ char name[64]; /**< Name for log */
+ enum vid4lin_io_type io_type; /**< I/O method. */
+ unsigned buf_cnt; /**< MMap buf cnt. */
+ vid4lin_buffer *buffers; /**< MMap buffers. */
+ pj_time_val start_time; /**< Time when started */
+
+ pjmedia_vid_dev_cb vid_cb; /**< Stream callback */
+ void *user_data; /**< Application data */
+} vid4lin_stream;
+
+/* Use this to convert between pjmedia_format_id and V4L2 fourcc */
+static vid4lin_fmt_map v4l2_fmt_maps[] =
+{
+ { PJMEDIA_FORMAT_RGB24, V4L2_PIX_FMT_BGR24 },
+ { PJMEDIA_FORMAT_RGBA, V4L2_PIX_FMT_BGR32 },
+ { PJMEDIA_FORMAT_RGB32, V4L2_PIX_FMT_BGR32 },
+ { PJMEDIA_FORMAT_AYUV, V4L2_PIX_FMT_YUV32 },
+ { PJMEDIA_FORMAT_YUY2, V4L2_PIX_FMT_YUYV },
+ { PJMEDIA_FORMAT_UYVY, V4L2_PIX_FMT_UYVY }
+};
+
+/* Prototypes */
+static pj_status_t vid4lin_factory_init(pjmedia_vid_dev_factory *f);
+static pj_status_t vid4lin_factory_destroy(pjmedia_vid_dev_factory *f);
+static pj_status_t vid4lin_factory_refresh(pjmedia_vid_dev_factory *f);
+static unsigned vid4lin_factory_get_dev_count(pjmedia_vid_dev_factory *f);
+static pj_status_t vid4lin_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_info *info);
+static pj_status_t vid4lin_factory_default_param(pj_pool_t *pool,
+ pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_param *param);
+static pj_status_t vid4lin_factory_create_stream(pjmedia_vid_dev_factory *f,
+ pjmedia_vid_dev_param *prm,
+ const pjmedia_vid_dev_cb *cb,
+ void *user_data,
+ pjmedia_vid_dev_stream **p);
+
+static pj_status_t vid4lin_stream_get_param(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_param *param);
+static pj_status_t vid4lin_stream_get_cap(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ void *value);
+static pj_status_t vid4lin_stream_set_cap(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ const void *value);
+static pj_status_t vid4lin_stream_get_frame(pjmedia_vid_dev_stream *strm,
+ pjmedia_frame *frame);
+static pj_status_t vid4lin_stream_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t vid4lin_stream_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t vid4lin_stream_destroy(pjmedia_vid_dev_stream *strm);
+
+/* Operations */
+static pjmedia_vid_dev_factory_op factory_op =
+{
+ &vid4lin_factory_init,
+ &vid4lin_factory_destroy,
+ &vid4lin_factory_get_dev_count,
+ &vid4lin_factory_get_dev_info,
+ &vid4lin_factory_default_param,
+ &vid4lin_factory_create_stream,
+ &vid4lin_factory_refresh
+};
+
+static pjmedia_vid_dev_stream_op stream_op =
+{
+ &vid4lin_stream_get_param,
+ &vid4lin_stream_get_cap,
+ &vid4lin_stream_set_cap,
+ &vid4lin_stream_start,
+ &vid4lin_stream_get_frame,
+ NULL,
+ &vid4lin_stream_stop,
+ &vid4lin_stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Factory creation function.
+ */
+pjmedia_vid_dev_factory* pjmedia_v4l2_factory(pj_pool_factory *pf)
+{
+ vid4lin_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, DRIVER_NAME, 512, 512, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, vid4lin_factory);
+ f->pf = pf;
+ f->pool = pool;
+ f->base.op = &factory_op;
+
+ return &f->base;
+}
+
+/* util: ioctl that tries harder. */
+static pj_status_t xioctl(int fh, int request, void *arg)
+{
+ enum { RETRY = MAX_IOCTL_RETRY };
+ int r, c=0;
+
+ do {
+ r = v4l2_ioctl(fh, request, arg);
+ } while (r==-1 && c++<RETRY && ((errno==EINTR) || (errno==EAGAIN)));
+
+ return (r == -1) ? pj_get_os_error() : PJ_SUCCESS;
+}
+
+/* Scan V4L2 devices */
+static pj_status_t v4l2_scan_devs(vid4lin_factory *f)
+{
+ vid4lin_dev_info vdi[V4L2_MAX_DEVS];
+ char dev_name[32];
+ unsigned i, old_count;
+ pj_status_t status;
+
+ if (f->dev_pool) {
+ pj_pool_release(f->dev_pool);
+ f->dev_pool = NULL;
+ }
+
+ pj_bzero(vdi, sizeof(vdi));
+ old_count = f->dev_count;
+ f->dev_count = 0;
+ f->dev_pool = pj_pool_create(f->pf, DRIVER_NAME, 500, 500, NULL);
+
+ for (i=0; i<V4L2_MAX_DEVS && f->dev_count < V4L2_MAX_DEVS; ++i) {
+ int fd;
+ vid4lin_dev_info *pdi;
+ pj_uint32_t fmt_cap[8];
+ int j, fmt_cnt=0;
+
+ pdi = &vdi[f->dev_count];
+
+ snprintf(dev_name, sizeof(dev_name), "/dev/video%d", i);
+ if (!pj_file_exists(dev_name))
+ continue;
+
+ fd = v4l2_open(dev_name, O_RDWR, 0);
+ if (fd == -1)
+ continue;
+
+ status = xioctl(fd, VIDIOC_QUERYCAP, &pdi->v4l2_cap);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4,(THIS_FILE, status, "Error querying %s", dev_name));
+ v4l2_close(fd);
+ continue;
+ }
+
+ if ((pdi->v4l2_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) {
+ v4l2_close(fd);
+ continue;
+ }
+
+ PJ_LOG(5,(THIS_FILE, "Found capture device %s", pdi->v4l2_cap.card));
+ PJ_LOG(5,(THIS_FILE, " Enumerating formats:"));
+ for (j=0; fmt_cnt<PJ_ARRAY_SIZE(fmt_cap); ++j) {
+ struct v4l2_fmtdesc fdesc;
+ unsigned k;
+
+ pj_bzero(&fdesc, sizeof(fdesc));
+ fdesc.index = j;
+ fdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ status = xioctl(fd, VIDIOC_ENUM_FMT, &fdesc);
+ if (status != PJ_SUCCESS)
+ break;
+
+ for (k=0; k<PJ_ARRAY_SIZE(v4l2_fmt_maps); ++k) {
+ if (v4l2_fmt_maps[k].v4l2_fmt_id == fdesc.pixelformat) {
+ fmt_cap[fmt_cnt++] = v4l2_fmt_maps[k].pjmedia_fmt_id;
+ PJ_LOG(5,(THIS_FILE, " Supported: %s",
+ fdesc.description));
+ break;
+ }
+ }
+ if (k==PJ_ARRAY_SIZE(v4l2_fmt_maps)) {
+ PJ_LOG(5,(THIS_FILE, " Unsupported: %s", fdesc.description));
+ }
+ }
+
+ v4l2_close(fd);
+
+ if (fmt_cnt==0) {
+ PJ_LOG(5,(THIS_FILE, " Found no common format"));
+ continue;
+ }
+
+ strncpy(pdi->dev_name, dev_name, sizeof(pdi->dev_name));
+ pdi->dev_name[sizeof(pdi->dev_name)-1] = '\0';
+ strncpy(pdi->info.name, (char*)pdi->v4l2_cap.card,
+ sizeof(pdi->info.name));
+ pdi->info.name[sizeof(pdi->info.name)-1] = '\0';
+ strncpy(pdi->info.driver, DRIVER_NAME, sizeof(pdi->info.driver));
+ pdi->info.driver[sizeof(pdi->info.driver)-1] = '\0';
+ pdi->info.dir = PJMEDIA_DIR_CAPTURE;
+ pdi->info.has_callback = PJ_FALSE;
+ pdi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT;
+
+ pdi->info.fmt_cnt = fmt_cnt;
+ for (j=0; j<fmt_cnt; ++j) {
+ pjmedia_format_init_video(&pdi->info.fmt[j],
+ fmt_cap[j],
+ DEFAULT_WIDTH,
+ DEFAULT_HEIGHT,
+ DEFAULT_FPS, 1);
+ }
+ if (j < fmt_cnt)
+ continue;
+
+ f->dev_count++;
+ }
+
+ if (f->dev_count == 0)
+ return PJ_SUCCESS;
+
+ if (f->dev_count > old_count || f->dev_info == NULL) {
+ f->dev_info = (vid4lin_dev_info*)
+ pj_pool_calloc(f->dev_pool,
+ f->dev_count,
+ sizeof(vid4lin_dev_info));
+ }
+ pj_memcpy(f->dev_info, vdi, f->dev_count * sizeof(vid4lin_dev_info));
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: init factory */
+static pj_status_t vid4lin_factory_init(pjmedia_vid_dev_factory *f)
+{
+ return vid4lin_factory_refresh(f);
+}
+
+/* API: destroy factory */
+static pj_status_t vid4lin_factory_destroy(pjmedia_vid_dev_factory *f)
+{
+ vid4lin_factory *cf = (vid4lin_factory*)f;
+ pj_pool_t *pool = cf->pool;
+
+ if (cf->dev_pool)
+ pj_pool_release(cf->dev_pool);
+ if (cf->pool) {
+ cf->pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: refresh the list of devices */
+static pj_status_t vid4lin_factory_refresh(pjmedia_vid_dev_factory *f)
+{
+ vid4lin_factory *cf = (vid4lin_factory*)f;
+ pj_status_t status;
+
+ status = v4l2_scan_devs(cf);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ PJ_LOG(4, (THIS_FILE, "Video4Linux2 has %d devices",
+ cf->dev_count));
+
+ return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned vid4lin_factory_get_dev_count(pjmedia_vid_dev_factory *f)
+{
+ vid4lin_factory *cf = (vid4lin_factory*)f;
+ return cf->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t vid4lin_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_info *info)
+{
+ vid4lin_factory *cf = (vid4lin_factory*)f;
+
+ PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV);
+
+ pj_memcpy(info, &cf->dev_info[index].info, sizeof(*info));
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t vid4lin_factory_default_param(pj_pool_t *pool,
+ pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_param *param)
+{
+ vid4lin_factory *cf = (vid4lin_factory*)f;
+
+ PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV);
+
+ pj_bzero(param, sizeof(*param));
+ param->dir = PJMEDIA_DIR_CAPTURE;
+ param->cap_id = index;
+ param->rend_id = PJMEDIA_VID_INVALID_DEV;
+ param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
+ param->clock_rate = DEFAULT_CLOCK_RATE;
+ pjmedia_format_copy(&param->fmt, &cf->dev_info[index].info.fmt[0]);
+
+ return PJ_SUCCESS;
+}
+
+static vid4lin_fmt_map* get_v4l2_format_info(pjmedia_format_id id)
+{
+ unsigned i;
+
+ for (i = 0; i < PJ_ARRAY_SIZE(v4l2_fmt_maps); i++) {
+ if (v4l2_fmt_maps[i].pjmedia_fmt_id == id)
+ return &v4l2_fmt_maps[i];
+ }
+
+ return NULL;
+}
+
+/* util: setup format */
+static pj_status_t vid4lin_stream_init_fmt(vid4lin_stream *stream,
+ const pjmedia_vid_dev_param *param,
+ pj_uint32_t pix_fmt)
+{
+ pjmedia_video_format_detail *vfd;
+ struct v4l2_format v4l2_fmt;
+ pj_status_t status;
+
+ vfd = pjmedia_format_get_video_format_detail(&param->fmt, PJ_TRUE);
+ if (vfd == NULL)
+ return PJMEDIA_EVID_BADFORMAT;
+
+ pj_bzero(&v4l2_fmt, sizeof(v4l2_fmt));
+ v4l2_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ v4l2_fmt.fmt.pix.width = vfd->size.w;
+ v4l2_fmt.fmt.pix.height = vfd->size.h;
+ v4l2_fmt.fmt.pix.pixelformat = pix_fmt;
+ v4l2_fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
+ status = xioctl(stream->fd, VIDIOC_S_FMT, &v4l2_fmt);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (v4l2_fmt.fmt.pix.pixelformat != pix_fmt) {
+ status = PJMEDIA_EVID_BADFORMAT;
+ return status;
+ }
+
+ if ((v4l2_fmt.fmt.pix.width != vfd->size.w) ||
+ (v4l2_fmt.fmt.pix.height != vfd->size.h))
+ {
+ /* Size has changed */
+ vfd->size.w = v4l2_fmt.fmt.pix.width;
+ vfd->size.h = v4l2_fmt.fmt.pix.height;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* Util: initiate v4l2 streaming via mmap */
+static pj_status_t vid4lin_stream_init_streaming(vid4lin_stream *stream)
+{
+ struct v4l2_requestbuffers req;
+ unsigned i;
+ pj_status_t status;
+
+ pj_bzero(&req, sizeof(req));
+ req.count = BUFFER_CNT;
+ req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ req.memory = V4L2_MEMORY_MMAP;
+ status = xioctl(stream->fd, VIDIOC_REQBUFS, &req);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ stream->buffers = pj_pool_calloc(stream->pool, req.count,
+ sizeof(*stream->buffers));
+ stream->buf_cnt = 0;
+
+ for (i = 0; i < req.count; ++i) {
+ struct v4l2_buffer buf;
+
+ pj_bzero(&buf, sizeof(buf));
+
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ buf.index = i;
+
+ status = xioctl(stream->fd, VIDIOC_QUERYBUF, &buf);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ stream->buffers[i].length = buf.length;
+ stream->buffers[i].start = v4l2_mmap(NULL, buf.length,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED, stream->fd,
+ buf.m.offset);
+
+ if (MAP_FAILED == stream->buffers[i].start) {
+ status = pj_get_os_error();
+ goto on_error;
+ }
+
+ stream->buf_cnt++;
+ }
+
+ PJ_LOG(5,(THIS_FILE, " mmap streaming initialized"));
+
+ stream->io_type = IO_TYPE_MMAP;
+ return PJ_SUCCESS;
+
+on_error:
+ return status;
+}
+
+/* init streaming with user pointer */
+static pj_status_t vid4lin_stream_init_streaming_user(vid4lin_stream *stream)
+{
+ return PJ_ENOTSUP;
+}
+
+/* init streaming with read() */
+static pj_status_t vid4lin_stream_init_read_write(vid4lin_stream *stream)
+{
+ return PJ_ENOTSUP;
+}
+
+/* API: create stream */
+static pj_status_t vid4lin_factory_create_stream(pjmedia_vid_dev_factory *f,
+ pjmedia_vid_dev_param *param,
+ const pjmedia_vid_dev_cb *cb,
+ void *user_data,
+ pjmedia_vid_dev_stream **p_vid_strm)
+{
+ vid4lin_factory *cf = (vid4lin_factory*)f;
+ pj_pool_t *pool;
+ vid4lin_stream *stream;
+ vid4lin_dev_info *vdi;
+ const vid4lin_fmt_map *fmt_map;
+ const pjmedia_video_format_info *fmt_info;
+ pjmedia_video_format_detail *vfd;
+ pj_status_t status = PJ_SUCCESS;
+
+
+ PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL);
+ PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO &&
+ param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO &&
+ param->dir == PJMEDIA_DIR_CAPTURE,
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(param->cap_id >= 0 && param->cap_id < cf->dev_count,
+ PJMEDIA_EVID_INVDEV);
+
+ fmt_info = pjmedia_get_video_format_info(NULL, param->fmt.id);
+ if (!fmt_info || (fmt_map=get_v4l2_format_info(param->fmt.id))==NULL)
+ return PJMEDIA_EVID_BADFORMAT;
+
+ vdi = &cf->dev_info[param->cap_id];
+ vfd = pjmedia_format_get_video_format_detail(&param->fmt, PJ_TRUE);
+
+ /* Create and Initialize stream descriptor */
+ pool = pj_pool_create(cf->pf, vdi->info.name, 512, 512, NULL);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ stream = PJ_POOL_ZALLOC_T(pool, vid4lin_stream);
+ pj_memcpy(&stream->param, param, sizeof(*param));
+ stream->pool = pool;
+ pj_memcpy(&stream->vid_cb, cb, sizeof(*cb));
+ strncpy(stream->name, vdi->info.name, sizeof(stream->name));
+ stream->name[sizeof(stream->name)-1] = '\0';
+ stream->user_data = user_data;
+ stream->fd = INVALID_FD;
+
+ stream->fd = v4l2_open(vdi->dev_name, O_RDWR, 0);
+ if (stream->fd < 0)
+ goto on_error;
+
+ status = vid4lin_stream_init_fmt(stream, param, fmt_map->v4l2_fmt_id);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ if (vdi->v4l2_cap.capabilities & V4L2_CAP_STREAMING)
+ status = vid4lin_stream_init_streaming(stream);
+
+ if (status!=PJ_SUCCESS && vdi->v4l2_cap.capabilities & V4L2_CAP_STREAMING)
+ status = vid4lin_stream_init_streaming_user(stream);
+
+ if (status!=PJ_SUCCESS && vdi->v4l2_cap.capabilities & V4L2_CAP_READWRITE)
+ status = vid4lin_stream_init_read_write(stream);
+
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(1,(THIS_FILE, "Error: unable to initiate I/O on %s",
+ stream->name));
+ goto on_error;
+ }
+
+ /* Done */
+ stream->base.op = &stream_op;
+ *p_vid_strm = &stream->base;
+
+ return PJ_SUCCESS;
+
+on_error:
+ if (status == PJ_SUCCESS)
+ status = PJ_RETURN_OS_ERROR(errno);
+
+ vid4lin_stream_destroy(&stream->base);
+ return status;
+}
+
+/* API: Get stream info. */
+static pj_status_t vid4lin_stream_get_param(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_param *pi)
+{
+ vid4lin_stream *strm = (vid4lin_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t vid4lin_stream_get_cap(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_cap cap,
+ void *pval)
+{
+ vid4lin_stream *strm = (vid4lin_stream*)s;
+
+ PJ_UNUSED_ARG(strm);
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE)
+ {
+ return PJMEDIA_EVID_INVCAP;
+// return PJ_SUCCESS;
+ } else {
+ return PJMEDIA_EVID_INVCAP;
+ }
+}
+
+/* API: set capability */
+static pj_status_t vid4lin_stream_set_cap(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_cap cap,
+ const void *pval)
+{
+ vid4lin_stream *strm = (vid4lin_stream*)s;
+
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ /*
+ if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE)
+ {
+ return PJ_SUCCESS;
+ }
+ */
+ PJ_UNUSED_ARG(strm);
+ PJ_UNUSED_ARG(cap);
+ PJ_UNUSED_ARG(pval);
+
+ return PJMEDIA_EVID_INVCAP;
+}
+
+/* get frame from mmap */
+static pj_status_t vid4lin_stream_get_frame_mmap(vid4lin_stream *stream,
+ pjmedia_frame *frame)
+{
+ struct v4l2_buffer buf;
+ pj_time_val time;
+ pj_status_t status = PJ_SUCCESS;
+
+ pj_bzero(&buf, sizeof(buf));
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ status = xioctl(stream->fd, VIDIOC_DQBUF, &buf);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (frame->size < buf.bytesused) {
+ /* supplied buffer is too small */
+ pj_assert(!"frame buffer is too small for v4l2");
+ status = PJ_ETOOSMALL;
+ goto on_return;
+ }
+
+ time.sec = buf.timestamp.tv_sec;
+ time.msec = buf.timestamp.tv_usec / 1000;
+ PJ_TIME_VAL_SUB(time, stream->start_time);
+
+ frame->type = PJMEDIA_FRAME_TYPE_VIDEO;
+ frame->bit_info = 0;
+ frame->size = buf.bytesused;
+ frame->timestamp.u64 = PJ_UINT64(1) * PJ_TIME_VAL_MSEC(time) *
+ stream->param.clock_rate / PJ_UINT64(1000);
+ pj_memcpy(frame->buf, stream->buffers[buf.index].start, buf.bytesused);
+
+on_return:
+ pj_bzero(&buf, sizeof(buf));
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ xioctl(stream->fd, VIDIOC_QBUF, &buf);
+
+ return status;
+}
+
+/* API: Get frame from stream */
+static pj_status_t vid4lin_stream_get_frame(pjmedia_vid_dev_stream *strm,
+ pjmedia_frame *frame)
+{
+ vid4lin_stream *stream = (vid4lin_stream*)strm;
+
+ if (stream->io_type == IO_TYPE_MMAP)
+ return vid4lin_stream_get_frame_mmap(stream, frame);
+ else {
+ pj_assert(!"Unsupported i/o type");
+ return PJ_EINVALIDOP;
+ }
+}
+
+/* API: Start stream. */
+static pj_status_t vid4lin_stream_start(pjmedia_vid_dev_stream *strm)
+{
+ vid4lin_stream *stream = (vid4lin_stream*)strm;
+ struct v4l2_buffer buf;
+ enum v4l2_buf_type type;
+ unsigned i;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(stream->fd != -1, PJ_EINVALIDOP);
+
+ PJ_LOG(4, (THIS_FILE, "Starting v4l2 video stream %s", stream->name));
+
+ pj_gettimeofday(&stream->start_time);
+
+ for (i = 0; i < stream->buf_cnt; ++i) {
+ pj_bzero(&buf, sizeof(buf));
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ buf.index = i;
+ status = xioctl(stream->fd, VIDIOC_QBUF, &buf);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+ type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ status = xioctl(stream->fd, VIDIOC_STREAMON, &type);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ return PJ_SUCCESS;
+
+on_error:
+ if (i > 0) {
+ /* Dequeue already enqueued buffers. Can we do this while streaming
+ * is not started?
+ */
+ unsigned n = i;
+ for (i=0; i<n; ++i) {
+ pj_bzero(&buf, sizeof(buf));
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ xioctl(stream->fd, VIDIOC_DQBUF, &buf);
+ }
+ }
+ return status;
+}
+
+/* API: Stop stream. */
+static pj_status_t vid4lin_stream_stop(pjmedia_vid_dev_stream *strm)
+{
+ vid4lin_stream *stream = (vid4lin_stream*)strm;
+ enum v4l2_buf_type type;
+ pj_status_t status;
+
+ if (stream->fd < 0)
+ return PJ_SUCCESS;
+
+ PJ_LOG(4, (THIS_FILE, "Stopping v4l2 video stream %s", stream->name));
+
+ type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ status = xioctl(stream->fd, VIDIOC_STREAMOFF, &type);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t vid4lin_stream_destroy(pjmedia_vid_dev_stream *strm)
+{
+ vid4lin_stream *stream = (vid4lin_stream*)strm;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+ vid4lin_stream_stop(strm);
+
+ PJ_LOG(4, (THIS_FILE, "Destroying v4l2 video stream %s", stream->name));
+
+ for (i=0; i<stream->buf_cnt; ++i) {
+ if (stream->buffers[i].start != MAP_FAILED) {
+ v4l2_munmap(stream->buffers[i].start, stream->buffers[i].length);
+ stream->buffers[i].start = MAP_FAILED;
+ }
+ }
+
+ if (stream->fd >= 0) {
+ v4l2_close(stream->fd);
+ stream->fd = -1;
+ }
+ pj_pool_release(stream->pool);
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_VIDEO_DEV_HAS_V4L2 */
diff --git a/pjmedia/src/pjmedia-videodev/videodev.c b/pjmedia/src/pjmedia-videodev/videodev.c
new file mode 100644
index 0000000..080b437
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/videodev.c
@@ -0,0 +1,877 @@
+/* $Id: videodev.c 4016 2012-04-04 05:05:50Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia-videodev/videodev_imp.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+
+#define THIS_FILE "videodev.c"
+
+#define DEFINE_CAP(name, info) {name, info}
+
+/* Capability names */
+static struct cap_info
+{
+ const char *name;
+ const char *info;
+} cap_infos[] =
+{
+ DEFINE_CAP("format", "Video format"),
+ DEFINE_CAP("scale", "Input dimension"),
+ DEFINE_CAP("window", "Window handle"),
+ DEFINE_CAP("resize", "Renderer resize"),
+ DEFINE_CAP("position", "Renderer position"),
+ DEFINE_CAP("hide", "Renderer hide"),
+ DEFINE_CAP("preview", "Input preview"),
+ DEFINE_CAP("orientation", "Video orientation"),
+ DEFINE_CAP("switch", "Switch device"),
+ DEFINE_CAP("wndflags", "Window flags")
+};
+
+
+/*
+ * The device index seen by application and driver is different.
+ *
+ * At application level, device index is index to global list of device.
+ * At driver level, device index is index to device list on that particular
+ * factory only.
+ */
+#define MAKE_DEV_ID(f_id, index) (((f_id & 0xFFFF) << 16) | (index & 0xFFFF))
+#define GET_INDEX(dev_id) ((dev_id) & 0xFFFF)
+#define GET_FID(dev_id) ((dev_id) >> 16)
+#define DEFAULT_DEV_ID 0
+
+
+/* extern functions to create factories */
+#if PJMEDIA_VIDEO_DEV_HAS_NULL_VIDEO
+pjmedia_vid_dev_factory* pjmedia_null_video_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_VIDEO_DEV_HAS_DSHOW
+pjmedia_vid_dev_factory* pjmedia_dshow_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC
+pjmedia_vid_dev_factory* pjmedia_cbar_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_VIDEO_DEV_HAS_SDL
+pjmedia_vid_dev_factory* pjmedia_sdl_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_VIDEO_DEV_HAS_FFMPEG
+pjmedia_vid_dev_factory* pjmedia_ffmpeg_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_VIDEO_DEV_HAS_V4L2
+pjmedia_vid_dev_factory* pjmedia_v4l2_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_VIDEO_DEV_HAS_QT
+pjmedia_vid_dev_factory* pjmedia_qt_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_VIDEO_DEV_HAS_IOS
+pjmedia_vid_dev_factory* pjmedia_ios_factory(pj_pool_factory *pf);
+#endif
+
+#define MAX_DRIVERS 16
+#define MAX_DEVS 64
+
+
+/* driver structure */
+struct driver
+{
+ /* Creation function */
+ pjmedia_vid_dev_factory_create_func_ptr create;
+ /* Factory instance */
+ pjmedia_vid_dev_factory *f;
+ char name[32]; /* Driver name */
+ unsigned dev_cnt; /* Number of devices */
+ unsigned start_idx; /* Start index in global list */
+ int cap_dev_idx; /* Default capture device. */
+ int rend_dev_idx; /* Default render device */
+};
+
+/* The video device subsystem */
+static struct vid_subsys
+{
+ unsigned init_count; /* How many times init() is called */
+ pj_pool_factory *pf; /* The pool factory. */
+
+ unsigned drv_cnt; /* Number of drivers. */
+ struct driver drv[MAX_DRIVERS]; /* Array of drivers. */
+
+ unsigned dev_cnt; /* Total number of devices. */
+ pj_uint32_t dev_list[MAX_DEVS];/* Array of device IDs. */
+
+} vid_subsys;
+
+/* API: get capability name/info */
+PJ_DEF(const char*) pjmedia_vid_dev_cap_name(pjmedia_vid_dev_cap cap,
+ const char **p_desc)
+{
+ const char *desc;
+ unsigned i;
+
+ if (p_desc==NULL) p_desc = &desc;
+
+ for (i=0; i<PJ_ARRAY_SIZE(cap_infos); ++i) {
+ if ((1 << i)==cap)
+ break;
+ }
+
+ if (i==PJ_ARRAY_SIZE(cap_infos)) {
+ *p_desc = "??";
+ return "??";
+ }
+
+ *p_desc = cap_infos[i].info;
+ return cap_infos[i].name;
+}
+
+static pj_status_t get_cap_pointer(const pjmedia_vid_dev_param *param,
+ pjmedia_vid_dev_cap cap,
+ void **ptr,
+ unsigned *size)
+{
+#define FIELD_INFO(name) *ptr = (void*)&param->name; \
+ *size = sizeof(param->name)
+
+ switch (cap) {
+ case PJMEDIA_VID_DEV_CAP_FORMAT:
+ FIELD_INFO(fmt);
+ break;
+ case PJMEDIA_VID_DEV_CAP_INPUT_SCALE:
+ FIELD_INFO(disp_size);
+ break;
+ case PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW:
+ FIELD_INFO(window);
+ break;
+ case PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE:
+ FIELD_INFO(disp_size);
+ break;
+ case PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION:
+ FIELD_INFO(window_pos);
+ break;
+ case PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE:
+ FIELD_INFO(window_hide);
+ break;
+ case PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW:
+ FIELD_INFO(native_preview);
+ break;
+ case PJMEDIA_VID_DEV_CAP_ORIENTATION:
+ FIELD_INFO(orient);
+ break;
+ /* The PJMEDIA_VID_DEV_CAP_SWITCH does not have an entry in the
+ * param (it doesn't make sense to open a stream and tell it
+ * to switch immediately).
+ */
+ case PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS:
+ FIELD_INFO(window_flags);
+ break;
+ default:
+ return PJMEDIA_EVID_INVCAP;
+ }
+
+#undef FIELD_INFO
+
+ return PJ_SUCCESS;
+}
+
+/* API: set cap value to param */
+PJ_DEF(pj_status_t)
+pjmedia_vid_dev_param_set_cap( pjmedia_vid_dev_param *param,
+ pjmedia_vid_dev_cap cap,
+ const void *pval)
+{
+ void *cap_ptr;
+ unsigned cap_size;
+ pj_status_t status;
+
+ status = get_cap_pointer(param, cap, &cap_ptr, &cap_size);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_memcpy(cap_ptr, pval, cap_size);
+ param->flags |= cap;
+
+ return PJ_SUCCESS;
+}
+
+/* API: get cap value from param */
+PJ_DEF(pj_status_t)
+pjmedia_vid_dev_param_get_cap( const pjmedia_vid_dev_param *param,
+ pjmedia_vid_dev_cap cap,
+ void *pval)
+{
+ void *cap_ptr;
+ unsigned cap_size;
+ pj_status_t status;
+
+ status = get_cap_pointer(param, cap, &cap_ptr, &cap_size);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if ((param->flags & cap) == 0) {
+ pj_bzero(cap_ptr, cap_size);
+ return PJMEDIA_EVID_INVCAP;
+ }
+
+ pj_memcpy(pval, cap_ptr, cap_size);
+ return PJ_SUCCESS;
+}
+
+/* Internal: init driver */
+static pj_status_t init_driver(unsigned drv_idx, pj_bool_t refresh)
+{
+ struct driver *drv = &vid_subsys.drv[drv_idx];
+ pjmedia_vid_dev_factory *f;
+ unsigned i, dev_cnt;
+ pj_status_t status;
+
+ if (!refresh) {
+ /* Create the factory */
+ f = (*drv->create)(vid_subsys.pf);
+ if (!f)
+ return PJ_EUNKNOWN;
+
+ /* Call factory->init() */
+ status = f->op->init(f);
+ if (status != PJ_SUCCESS) {
+ f->op->destroy(f);
+ return status;
+ }
+ } else {
+ f = drv->f;
+ }
+
+ /* Get number of devices */
+ dev_cnt = f->op->get_dev_count(f);
+ if (dev_cnt + vid_subsys.dev_cnt > MAX_DEVS) {
+ PJ_LOG(4,(THIS_FILE, "%d device(s) cannot be registered because"
+ " there are too many devices",
+ vid_subsys.dev_cnt + dev_cnt - MAX_DEVS));
+ dev_cnt = MAX_DEVS - vid_subsys.dev_cnt;
+ }
+
+ /* enabling this will cause pjsua-lib initialization to fail when there
+ * is no video device installed in the system, even when pjsua has been
+ * run with --null-video
+ *
+ if (dev_cnt == 0) {
+ f->op->destroy(f);
+ return PJMEDIA_EVID_NODEV;
+ }
+ */
+
+ /* Fill in default devices */
+ drv->rend_dev_idx = drv->cap_dev_idx = -1;
+ for (i=0; i<dev_cnt; ++i) {
+ pjmedia_vid_dev_info info;
+
+ status = f->op->get_dev_info(f, i, &info);
+ if (status != PJ_SUCCESS) {
+ f->op->destroy(f);
+ return status;
+ }
+
+ if (drv->name[0]=='\0') {
+ /* Set driver name */
+ pj_ansi_strncpy(drv->name, info.driver, sizeof(drv->name));
+ drv->name[sizeof(drv->name)-1] = '\0';
+ }
+
+ if (drv->rend_dev_idx < 0 && (info.dir & PJMEDIA_DIR_RENDER)) {
+ /* Set default render device */
+ drv->rend_dev_idx = i;
+ }
+ if (drv->cap_dev_idx < 0 && (info.dir & PJMEDIA_DIR_CAPTURE)) {
+ /* Set default capture device */
+ drv->cap_dev_idx = i;
+ }
+
+ if (drv->rend_dev_idx >= 0 && drv->cap_dev_idx >= 0) {
+ /* Done. */
+ break;
+ }
+ }
+
+ /* Register the factory */
+ drv->f = f;
+ drv->f->sys.drv_idx = drv_idx;
+ drv->start_idx = vid_subsys.dev_cnt;
+ drv->dev_cnt = dev_cnt;
+
+ /* Register devices to global list */
+ for (i=0; i<dev_cnt; ++i) {
+ vid_subsys.dev_list[vid_subsys.dev_cnt++] = MAKE_DEV_ID(drv_idx, i);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* Internal: deinit driver */
+static void deinit_driver(unsigned drv_idx)
+{
+ struct driver *drv = &vid_subsys.drv[drv_idx];
+
+ if (drv->f) {
+ drv->f->op->destroy(drv->f);
+ drv->f = NULL;
+ }
+
+ drv->dev_cnt = 0;
+ drv->rend_dev_idx = drv->cap_dev_idx = -1;
+}
+
+/* API: Initialize the video device subsystem. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_subsys_init(pj_pool_factory *pf)
+{
+ unsigned i;
+ pj_status_t status = PJ_SUCCESS;
+
+ /* Allow init() to be called multiple times as long as there is matching
+ * number of shutdown().
+ */
+ if (vid_subsys.init_count++ != 0) {
+ return PJ_SUCCESS;
+ }
+
+ /* Register error subsystem */
+ pj_register_strerror(PJMEDIA_VIDEODEV_ERRNO_START,
+ PJ_ERRNO_SPACE_SIZE,
+ &pjmedia_videodev_strerror);
+
+ /* Init */
+ vid_subsys.pf = pf;
+ vid_subsys.drv_cnt = 0;
+ vid_subsys.dev_cnt = 0;
+
+ /* Register creation functions */
+#if PJMEDIA_VIDEO_DEV_HAS_V4L2
+ vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_v4l2_factory;
+#endif
+#if PJMEDIA_VIDEO_DEV_HAS_QT
+ vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_qt_factory;
+#endif
+#if PJMEDIA_VIDEO_DEV_HAS_IOS
+ vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_ios_factory;
+#endif
+#if PJMEDIA_VIDEO_DEV_HAS_DSHOW
+ vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_dshow_factory;
+#endif
+#if PJMEDIA_VIDEO_DEV_HAS_FFMPEG
+ vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_ffmpeg_factory;
+#endif
+#if PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC
+ vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_cbar_factory;
+#endif
+#if PJMEDIA_VIDEO_DEV_HAS_SDL
+ vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_sdl_factory;
+#endif
+
+ /* Initialize each factory and build the device ID list */
+ for (i=0; i<vid_subsys.drv_cnt; ++i) {
+ status = init_driver(i, PJ_FALSE);
+ if (status != PJ_SUCCESS) {
+ deinit_driver(i);
+ continue;
+ }
+ }
+
+ return vid_subsys.dev_cnt ? PJ_SUCCESS : status;
+}
+
+/* API: register a video device factory to the video device subsystem. */
+PJ_DEF(pj_status_t)
+pjmedia_vid_register_factory(pjmedia_vid_dev_factory_create_func_ptr adf,
+ pjmedia_vid_dev_factory *factory)
+{
+ pj_bool_t refresh = PJ_FALSE;
+ pj_status_t status;
+
+ if (vid_subsys.init_count == 0)
+ return PJMEDIA_EVID_INIT;
+
+ vid_subsys.drv[vid_subsys.drv_cnt].create = adf;
+ vid_subsys.drv[vid_subsys.drv_cnt].f = factory;
+
+ if (factory) {
+ /* Call factory->init() */
+ status = factory->op->init(factory);
+ if (status != PJ_SUCCESS) {
+ factory->op->destroy(factory);
+ return status;
+ }
+ refresh = PJ_TRUE;
+ }
+
+ status = init_driver(vid_subsys.drv_cnt, refresh);
+ if (status == PJ_SUCCESS) {
+ vid_subsys.drv_cnt++;
+ } else {
+ deinit_driver(vid_subsys.drv_cnt);
+ }
+
+ return status;
+}
+
+/* API: unregister a video device factory from the video device subsystem. */
+PJ_DEF(pj_status_t)
+pjmedia_vid_unregister_factory(pjmedia_vid_dev_factory_create_func_ptr adf,
+ pjmedia_vid_dev_factory *factory)
+{
+ unsigned i, j;
+
+ if (vid_subsys.init_count == 0)
+ return PJMEDIA_EVID_INIT;
+
+ for (i=0; i<vid_subsys.drv_cnt; ++i) {
+ struct driver *drv = &vid_subsys.drv[i];
+
+ if ((factory && drv->f==factory) || (adf && drv->create == adf)) {
+ for (j = drv->start_idx; j < drv->start_idx + drv->dev_cnt; j++)
+ {
+ vid_subsys.dev_list[j] = (pj_uint32_t)PJMEDIA_VID_INVALID_DEV;
+ }
+
+ deinit_driver(i);
+ pj_bzero(drv, sizeof(*drv));
+ return PJ_SUCCESS;
+ }
+ }
+
+ return PJMEDIA_EVID_ERR;
+}
+
+/* API: get the pool factory registered to the video device subsystem. */
+PJ_DEF(pj_pool_factory*) pjmedia_vid_dev_subsys_get_pool_factory(void)
+{
+ return vid_subsys.pf;
+}
+
+/* API: Shutdown the video device subsystem. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_subsys_shutdown(void)
+{
+ unsigned i;
+
+ /* Allow shutdown() to be called multiple times as long as there is
+ * matching number of init().
+ */
+ if (vid_subsys.init_count == 0) {
+ return PJ_SUCCESS;
+ }
+ --vid_subsys.init_count;
+
+ if (vid_subsys.init_count == 0) {
+ for (i=0; i<vid_subsys.drv_cnt; ++i) {
+ deinit_driver(i);
+ }
+
+ vid_subsys.pf = NULL;
+ }
+ return PJ_SUCCESS;
+}
+
+/* API: Refresh the list of video devices installed in the system. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_refresh(void)
+{
+ unsigned i;
+
+ vid_subsys.dev_cnt = 0;
+ for (i=0; i<vid_subsys.drv_cnt; ++i) {
+ struct driver *drv = &vid_subsys.drv[i];
+
+ if (drv->f && drv->f->op->refresh) {
+ pj_status_t status = drv->f->op->refresh(drv->f);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4, (THIS_FILE, status, "Unable to refresh device "
+ "list for %s", drv->name));
+ }
+ }
+ init_driver(i, PJ_TRUE);
+ }
+ return PJ_SUCCESS;
+}
+
+/* API: Get the number of video devices installed in the system. */
+PJ_DEF(unsigned) pjmedia_vid_dev_count(void)
+{
+ return vid_subsys.dev_cnt;
+}
+
+/* Internal: convert local index to global device index */
+static pj_status_t make_global_index(unsigned drv_idx,
+ pjmedia_vid_dev_index *id)
+{
+ if (*id < 0) {
+ return PJ_SUCCESS;
+ }
+
+ /* Check that factory still exists */
+ PJ_ASSERT_RETURN(vid_subsys.drv[drv_idx].f, PJ_EBUG);
+
+ /* Check that device index is valid */
+ PJ_ASSERT_RETURN(*id>=0 && *id<(int)vid_subsys.drv[drv_idx].dev_cnt,
+ PJ_EBUG);
+
+ *id += vid_subsys.drv[drv_idx].start_idx;
+ return PJ_SUCCESS;
+}
+
+/* Internal: lookup device id */
+static pj_status_t lookup_dev(pjmedia_vid_dev_index id,
+ pjmedia_vid_dev_factory **p_f,
+ unsigned *p_local_index)
+{
+ int f_id, index;
+
+ if (id < 0) {
+ unsigned i;
+
+ if (id <= PJMEDIA_VID_INVALID_DEV)
+ return PJMEDIA_EVID_INVDEV;
+
+ for (i=0; i<vid_subsys.drv_cnt; ++i) {
+ struct driver *drv = &vid_subsys.drv[i];
+ if (id==PJMEDIA_VID_DEFAULT_CAPTURE_DEV &&
+ drv->cap_dev_idx >= 0)
+ {
+ id = drv->cap_dev_idx;
+ make_global_index(i, &id);
+ break;
+ } else if (id==PJMEDIA_VID_DEFAULT_RENDER_DEV &&
+ drv->rend_dev_idx >= 0)
+ {
+ id = drv->rend_dev_idx;
+ make_global_index(i, &id);
+ break;
+ }
+ }
+
+ if (id < 0) {
+ return PJMEDIA_EVID_NODEFDEV;
+ }
+ }
+
+ f_id = GET_FID(vid_subsys.dev_list[id]);
+ index = GET_INDEX(vid_subsys.dev_list[id]);
+
+ if (f_id < 0 || f_id >= (int)vid_subsys.drv_cnt)
+ return PJMEDIA_EVID_INVDEV;
+
+ if (index < 0 || index >= (int)vid_subsys.drv[f_id].dev_cnt)
+ return PJMEDIA_EVID_INVDEV;
+
+ *p_f = vid_subsys.drv[f_id].f;
+ *p_local_index = (unsigned)index;
+
+ return PJ_SUCCESS;
+
+}
+
+/* API: lookup device id */
+PJ_DEF(pj_status_t)
+pjmedia_vid_dev_get_local_index(pjmedia_vid_dev_index id,
+ pjmedia_vid_dev_factory **p_f,
+ unsigned *p_local_index)
+{
+ return lookup_dev(id, p_f, p_local_index);
+}
+
+/* API: from factory and local index, get global index */
+PJ_DEF(pj_status_t)
+pjmedia_vid_dev_get_global_index(const pjmedia_vid_dev_factory *f,
+ unsigned local_idx,
+ pjmedia_vid_dev_index *pid)
+{
+ PJ_ASSERT_RETURN(f->sys.drv_idx >= 0 && f->sys.drv_idx < MAX_DRIVERS,
+ PJ_EINVALIDOP);
+ *pid = local_idx;
+ return make_global_index(f->sys.drv_idx, pid);
+}
+
+/* API: Get device information. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_get_info(pjmedia_vid_dev_index id,
+ pjmedia_vid_dev_info *info)
+{
+ pjmedia_vid_dev_factory *f;
+ unsigned index;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(info, PJ_EINVAL);
+ PJ_ASSERT_RETURN(vid_subsys.pf, PJMEDIA_EVID_INIT);
+
+ if (id <= PJMEDIA_VID_INVALID_DEV)
+ return PJMEDIA_EVID_INVDEV;
+
+ status = lookup_dev(id, &f, &index);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = f->op->get_dev_info(f, index, info);
+
+ /* Make sure device ID is the real ID (not PJMEDIA_VID_DEFAULT_*_DEV) */
+ info->id = index;
+ make_global_index(f->sys.drv_idx, &info->id);
+
+ return status;
+}
+
+/* API: find device */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_lookup( const char *drv_name,
+ const char *dev_name,
+ pjmedia_vid_dev_index *id)
+{
+ pjmedia_vid_dev_factory *f = NULL;
+ unsigned drv_idx, dev_idx;
+
+ PJ_ASSERT_RETURN(drv_name && dev_name && id, PJ_EINVAL);
+ PJ_ASSERT_RETURN(vid_subsys.pf, PJMEDIA_EVID_INIT);
+
+ for (drv_idx=0; drv_idx<vid_subsys.drv_cnt; ++drv_idx) {
+ if (!pj_ansi_stricmp(drv_name, vid_subsys.drv[drv_idx].name))
+ {
+ f = vid_subsys.drv[drv_idx].f;
+ break;
+ }
+ }
+
+ if (!f)
+ return PJ_ENOTFOUND;
+
+ for (dev_idx=0; dev_idx<vid_subsys.drv[drv_idx].dev_cnt; ++dev_idx)
+ {
+ pjmedia_vid_dev_info info;
+ pj_status_t status;
+
+ status = f->op->get_dev_info(f, dev_idx, &info);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (!pj_ansi_stricmp(dev_name, info.name))
+ break;
+ }
+
+ if (dev_idx==vid_subsys.drv[drv_idx].dev_cnt)
+ return PJ_ENOTFOUND;
+
+ *id = dev_idx;
+ make_global_index(drv_idx, id);
+
+ return PJ_SUCCESS;
+}
+
+/* API: Initialize the video device parameters with default values for the
+ * specified device.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_default_param(pj_pool_t *pool,
+ pjmedia_vid_dev_index id,
+ pjmedia_vid_dev_param *param)
+{
+ pjmedia_vid_dev_factory *f;
+ unsigned index;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(param, PJ_EINVAL);
+ PJ_ASSERT_RETURN(vid_subsys.pf, PJMEDIA_EVID_INIT);
+
+ if (id <= PJMEDIA_VID_INVALID_DEV)
+ return PJMEDIA_EVID_INVDEV;
+
+ status = lookup_dev(id, &f, &index);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = f->op->default_param(pool, f, index, param);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Normalize device IDs */
+ make_global_index(f->sys.drv_idx, &param->cap_id);
+ make_global_index(f->sys.drv_idx, &param->rend_id);
+
+ return PJ_SUCCESS;
+}
+
+/* API: Open video stream object using the specified parameters. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_create(
+ pjmedia_vid_dev_param *prm,
+ const pjmedia_vid_dev_cb *cb,
+ void *user_data,
+ pjmedia_vid_dev_stream **p_vid_strm)
+{
+ pjmedia_vid_dev_factory *cap_f=NULL, *rend_f=NULL, *f=NULL;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(prm && prm->dir && p_vid_strm, PJ_EINVAL);
+ PJ_ASSERT_RETURN(vid_subsys.pf, PJMEDIA_EVID_INIT);
+ PJ_ASSERT_RETURN(prm->dir==PJMEDIA_DIR_CAPTURE ||
+ prm->dir==PJMEDIA_DIR_RENDER ||
+ prm->dir==PJMEDIA_DIR_CAPTURE_RENDER,
+ PJ_EINVAL);
+
+ /* Normalize cap_id */
+ if (prm->dir & PJMEDIA_DIR_CAPTURE) {
+ unsigned index;
+
+ if (prm->cap_id < 0)
+ prm->cap_id = PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
+
+ status = lookup_dev(prm->cap_id, &cap_f, &index);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ prm->cap_id = index;
+ f = cap_f;
+ }
+
+ /* Normalize rend_id */
+ if (prm->dir & PJMEDIA_DIR_RENDER) {
+ unsigned index;
+
+ if (prm->rend_id < 0)
+ prm->rend_id = PJMEDIA_VID_DEFAULT_RENDER_DEV;
+
+ status = lookup_dev(prm->rend_id, &rend_f, &index);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ prm->rend_id = index;
+ f = rend_f;
+ }
+
+ PJ_ASSERT_RETURN(f != NULL, PJ_EBUG);
+
+ /* For now, cap_id and rend_id must belong to the same factory */
+ PJ_ASSERT_RETURN((prm->dir != PJMEDIA_DIR_CAPTURE_RENDER) ||
+ (cap_f == rend_f),
+ PJMEDIA_EVID_INVDEV);
+
+ /* Create the stream */
+ status = f->op->create_stream(f, prm, cb,
+ user_data, p_vid_strm);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Assign factory id to the stream */
+ (*p_vid_strm)->sys.drv_idx = f->sys.drv_idx;
+ return PJ_SUCCESS;
+}
+
+/* API: Get the running parameters for the specified video stream. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_get_param(
+ pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_param *param)
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(strm && param, PJ_EINVAL);
+ PJ_ASSERT_RETURN(vid_subsys.pf, PJMEDIA_EVID_INIT);
+
+ status = strm->op->get_param(strm, param);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Normalize device id's */
+ make_global_index(strm->sys.drv_idx, &param->cap_id);
+ make_global_index(strm->sys.drv_idx, &param->rend_id);
+
+ return PJ_SUCCESS;
+}
+
+/* API: Get the value of a specific capability of the video stream. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_get_cap(
+ pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ void *value)
+{
+ return strm->op->get_cap(strm, cap, value);
+}
+
+/* API: Set the value of a specific capability of the video stream. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_set_cap(
+ pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ const void *value)
+{
+ return strm->op->set_cap(strm, cap, value);
+}
+
+/* API: Start the stream. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_start(pjmedia_vid_dev_stream *strm)
+{
+ pj_status_t status;
+
+ if (pjmedia_vid_dev_stream_is_running(strm))
+ return PJ_SUCCESS;
+
+ status = strm->op->start(strm);
+ if (status == PJ_SUCCESS)
+ strm->sys.is_running = PJ_TRUE;
+ return status;
+}
+
+/* API: has it been started? */
+PJ_DEF(pj_bool_t)
+pjmedia_vid_dev_stream_is_running(pjmedia_vid_dev_stream *strm)
+{
+ return strm->sys.is_running;
+}
+
+PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_get_frame(
+ pjmedia_vid_dev_stream *strm,
+ pjmedia_frame *frame)
+{
+ pj_assert(strm->op->get_frame);
+ return strm->op->get_frame(strm, frame);
+}
+
+PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_put_frame(
+ pjmedia_vid_dev_stream *strm,
+ const pjmedia_frame *frame)
+{
+ pj_assert(strm->op->put_frame);
+ return strm->op->put_frame(strm, frame);
+}
+
+/* API: Stop the stream. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_stop(pjmedia_vid_dev_stream *strm)
+{
+ strm->sys.is_running = PJ_FALSE;
+ return strm->op->stop(strm);
+}
+
+/* API: Destroy the stream. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_destroy(
+ pjmedia_vid_dev_stream *strm)
+{
+ strm->sys.is_running = PJ_FALSE;
+ return strm->op->destroy(strm);
+}
+
+
+#endif /* PJMEDIA_HAS_VIDEO */
diff --git a/pjmedia/src/pjmedia/alaw_ulaw.c b/pjmedia/src/pjmedia/alaw_ulaw.c
new file mode 100644
index 0000000..0a14de8
--- /dev/null
+++ b/pjmedia/src/pjmedia/alaw_ulaw.c
@@ -0,0 +1,300 @@
+/*
+ * This source code is a product of Sun Microsystems, Inc. and is provided
+ * for unrestricted use. Users may copy or modify this source code without
+ * charge.
+ *
+ * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING
+ * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
+ *
+ * Sun source code is provided with no support and without any obligation on
+ * the part of Sun Microsystems, Inc. to assist in its use, correction,
+ * modification or enhancement.
+ *
+ * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
+ * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE
+ * OR ANY PART THEREOF.
+ *
+ * In no event will Sun Microsystems, Inc. be liable for any lost revenue
+ * or profits or other special, indirect and consequential damages, even if
+ * Sun has been advised of the possibility of such damages.
+ *
+ * Sun Microsystems, Inc.
+ * 2550 Garcia Avenue
+ * Mountain View, California 94043
+ */
+#include <pjmedia/alaw_ulaw.h>
+
+#if !defined(PJMEDIA_HAS_ALAW_ULAW_TABLE) || PJMEDIA_HAS_ALAW_ULAW_TABLE==0
+
+#ifdef _MSC_VER
+# pragma warning ( disable: 4244 ) /* Conversion from int to char etc */
+#endif
+
+/*
+ * g711.c
+ *
+ * u-law, A-law and linear PCM conversions.
+ */
+#define SIGN_BIT (0x80) /* Sign bit for a A-law byte. */
+#define QUANT_MASK (0xf) /* Quantization field mask. */
+#define NSEGS (8) /* Number of A-law segments. */
+#define SEG_SHIFT (4) /* Left shift for segment number. */
+#define SEG_MASK (0x70) /* Segment field mask. */
+
+static short seg_end[8] = {0xFF, 0x1FF, 0x3FF, 0x7FF,
+ 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF};
+
+/* copy from CCITT G.711 specifications */
+static unsigned char _u2a[128] = { /* u- to A-law conversions */
+ 1, 1, 2, 2, 3, 3, 4, 4,
+ 5, 5, 6, 6, 7, 7, 8, 8,
+ 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 27, 29, 31, 33, 34, 35, 36,
+ 37, 38, 39, 40, 41, 42, 43, 44,
+ 46, 48, 49, 50, 51, 52, 53, 54,
+ 55, 56, 57, 58, 59, 60, 61, 62,
+ 64, 65, 66, 67, 68, 69, 70, 71,
+ 72, 73, 74, 75, 76, 77, 78, 79,
+ 81, 82, 83, 84, 85, 86, 87, 88,
+ 89, 90, 91, 92, 93, 94, 95, 96,
+ 97, 98, 99, 100, 101, 102, 103, 104,
+ 105, 106, 107, 108, 109, 110, 111, 112,
+ 113, 114, 115, 116, 117, 118, 119, 120,
+ 121, 122, 123, 124, 125, 126, 127, 128};
+
+static unsigned char _a2u[128] = { /* A- to u-law conversions */
+ 1, 3, 5, 7, 9, 11, 13, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 32, 33, 33, 34, 34, 35, 35,
+ 36, 37, 38, 39, 40, 41, 42, 43,
+ 44, 45, 46, 47, 48, 48, 49, 49,
+ 50, 51, 52, 53, 54, 55, 56, 57,
+ 58, 59, 60, 61, 62, 63, 64, 64,
+ 65, 66, 67, 68, 69, 70, 71, 72,
+ 73, 74, 75, 76, 77, 78, 79, 79,
+ 80, 81, 82, 83, 84, 85, 86, 87,
+ 88, 89, 90, 91, 92, 93, 94, 95,
+ 96, 97, 98, 99, 100, 101, 102, 103,
+ 104, 105, 106, 107, 108, 109, 110, 111,
+ 112, 113, 114, 115, 116, 117, 118, 119,
+ 120, 121, 122, 123, 124, 125, 126, 127};
+
+static int
+search(
+ int val,
+ short *table,
+ int size)
+{
+ int i;
+
+ for (i = 0; i < size; i++) {
+ if (val <= *table++)
+ return (i);
+ }
+ return (size);
+}
+
+/*
+ * linear2alaw() - Convert a 16-bit linear PCM value to 8-bit A-law
+ *
+ * linear2alaw() accepts an 16-bit integer and encodes it as A-law data.
+ *
+ * Linear Input Code Compressed Code
+ * ------------------------ ---------------
+ * 0000000wxyza 000wxyz
+ * 0000001wxyza 001wxyz
+ * 000001wxyzab 010wxyz
+ * 00001wxyzabc 011wxyz
+ * 0001wxyzabcd 100wxyz
+ * 001wxyzabcde 101wxyz
+ * 01wxyzabcdef 110wxyz
+ * 1wxyzabcdefg 111wxyz
+ *
+ * For further information see John C. Bellamy's Digital Telephony, 1982,
+ * John Wiley & Sons, pps 98-111 and 472-476.
+ */
+PJ_DEF(pj_uint8_t) pjmedia_linear2alaw(
+ int pcm_val) /* 2's complement (16-bit range) */
+{
+ int mask;
+ int seg;
+ unsigned char aval;
+
+ if (pcm_val >= 0) {
+ mask = 0xD5; /* sign (7th) bit = 1 */
+ } else {
+ mask = 0x55; /* sign bit = 0 */
+ pcm_val = -pcm_val - 8;
+
+ /* https://trac.pjsip.org/repos/ticket/1301
+ * Thank you K Johnson - Zetron - 27 May 2011
+ */
+ if (pcm_val < 0)
+ pcm_val = 0;
+ }
+
+ /* Convert the scaled magnitude to segment number. */
+ seg = search(pcm_val, seg_end, 8);
+
+ /* Combine the sign, segment, and quantization bits. */
+
+ if (seg >= 8) /* out of range, return maximum value. */
+ return (0x7F ^ mask);
+ else {
+ aval = seg << SEG_SHIFT;
+ if (seg < 2)
+ aval |= (pcm_val >> 4) & QUANT_MASK;
+ else
+ aval |= (pcm_val >> (seg + 3)) & QUANT_MASK;
+ return (aval ^ mask);
+ }
+}
+
+/*
+ * alaw2linear() - Convert an A-law value to 16-bit linear PCM
+ *
+ */
+PJ_DEF(int) pjmedia_alaw2linear(
+ unsigned a_val)
+{
+ int t;
+ int seg;
+
+ a_val ^= 0x55;
+
+ t = (a_val & QUANT_MASK) << 4;
+ seg = ((unsigned)a_val & SEG_MASK) >> SEG_SHIFT;
+ switch (seg) {
+ case 0:
+ t += 8;
+ break;
+ case 1:
+ t += 0x108;
+ break;
+ default:
+ t += 0x108;
+ t <<= seg - 1;
+ }
+ return ((a_val & SIGN_BIT) ? t : -t);
+}
+
+#define BIAS (0x84) /* Bias for linear code. */
+
+/*
+ * linear2ulaw() - Convert a linear PCM value to u-law
+ *
+ * In order to simplify the encoding process, the original linear magnitude
+ * is biased by adding 33 which shifts the encoding range from (0 - 8158) to
+ * (33 - 8191). The result can be seen in the following encoding table:
+ *
+ * Biased Linear Input Code Compressed Code
+ * ------------------------ ---------------
+ * 00000001wxyza 000wxyz
+ * 0000001wxyzab 001wxyz
+ * 000001wxyzabc 010wxyz
+ * 00001wxyzabcd 011wxyz
+ * 0001wxyzabcde 100wxyz
+ * 001wxyzabcdef 101wxyz
+ * 01wxyzabcdefg 110wxyz
+ * 1wxyzabcdefgh 111wxyz
+ *
+ * Each biased linear code has a leading 1 which identifies the segment
+ * number. The value of the segment number is equal to 7 minus the number
+ * of leading 0's. The quantization interval is directly available as the
+ * four bits wxyz. * The trailing bits (a - h) are ignored.
+ *
+ * Ordinarily the complement of the resulting code word is used for
+ * transmission, and so the code word is complemented before it is returned.
+ *
+ * For further information see John C. Bellamy's Digital Telephony, 1982,
+ * John Wiley & Sons, pps 98-111 and 472-476.
+ */
+PJ_DEF(unsigned char) pjmedia_linear2ulaw(
+ int pcm_val) /* 2's complement (16-bit range) */
+{
+ int mask;
+ int seg;
+ unsigned char uval;
+
+ /* Get the sign and the magnitude of the value. */
+ if (pcm_val < 0) {
+ pcm_val = BIAS - pcm_val;
+ mask = 0x7F;
+ } else {
+ pcm_val += BIAS;
+ mask = 0xFF;
+ }
+
+ /* Convert the scaled magnitude to segment number. */
+ seg = search(pcm_val, seg_end, 8);
+
+ /*
+ * Combine the sign, segment, quantization bits;
+ * and complement the code word.
+ */
+ if (seg >= 8) /* out of range, return maximum value. */
+ return (0x7F ^ mask);
+ else {
+ uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0xF);
+ return (uval ^ mask);
+ }
+
+}
+
+/*
+ * ulaw2linear() - Convert a u-law value to 16-bit linear PCM
+ *
+ * First, a biased linear code is derived from the code word. An unbiased
+ * output can then be obtained by subtracting 33 from the biased code.
+ *
+ * Note that this function expects to be passed the complement of the
+ * original code word. This is in keeping with ISDN conventions.
+ */
+PJ_DEF(int) pjmedia_ulaw2linear(
+ unsigned char u_val)
+{
+ int t;
+
+ /* Shortcut: when input is zero, output is zero
+ * This will also make the VAD works harder.
+ * -bennylp
+ */
+ if (u_val == 0) return 0;
+
+ /* Complement to obtain normal u-law value. */
+ u_val = ~u_val;
+
+ /*
+ * Extract and bias the quantization bits. Then
+ * shift up by the segment number and subtract out the bias.
+ */
+ t = ((u_val & QUANT_MASK) << 3) + BIAS;
+ t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT;
+
+ return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS));
+}
+
+/* A-law to u-law conversion */
+PJ_DEF(unsigned char) pjmedia_alaw2ulaw(
+ unsigned char aval)
+{
+ aval &= 0xff;
+ return ((aval & 0x80) ? (0xFF ^ _a2u[aval ^ 0xD5]) :
+ (0x7F ^ _a2u[aval ^ 0x55]));
+}
+
+/* u-law to A-law conversion */
+PJ_DEF(unsigned char) pjmedia_ulaw2alaw(
+ unsigned char uval)
+{
+ uval &= 0xff;
+ return ((uval & 0x80) ? (0xD5 ^ (_u2a[0xFF ^ uval] - 1)) :
+ (0x55 ^ (_u2a[0x7F ^ uval] - 1)));
+}
+
+
+#endif /* PJMEDIA_HAS_ALAW_ULAW_TABLE */
+
diff --git a/pjmedia/src/pjmedia/alaw_ulaw_table.c b/pjmedia/src/pjmedia/alaw_ulaw_table.c
new file mode 100644
index 0000000..e6eec2c
--- /dev/null
+++ b/pjmedia/src/pjmedia/alaw_ulaw_table.c
@@ -0,0 +1,4207 @@
+/* $Id: alaw_ulaw_table.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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
+ */
+
+/*
+ * The tables here and also the conversion ideas are contributed by
+ * Toni Rutar <toni at aufbix.org>. Many thanks!
+ */
+#include <pjmedia/alaw_ulaw.h>
+
+#if defined(PJMEDIA_HAS_ALAW_ULAW_TABLE) && PJMEDIA_HAS_ALAW_ULAW_TABLE!=0
+
+const pj_uint8_t pjmedia_linear2ulaw_tab[16384] =
+{
+ 0xff,0xfe,0xfe,0xfd,0xfd,0xfc,0xfc,0xfb,
+ 0xfb,0xfa,0xfa,0xf9,0xf9,0xf8,0xf8,0xf7,
+ 0xf7,0xf6,0xf6,0xf5,0xf5,0xf4,0xf4,0xf3,
+ 0xf3,0xf2,0xf2,0xf1,0xf1,0xf0,0xf0,0xef,
+ 0xef,0xef,0xef,0xee,0xee,0xee,0xee,0xed,
+ 0xed,0xed,0xed,0xec,0xec,0xec,0xec,0xeb,
+ 0xeb,0xeb,0xeb,0xea,0xea,0xea,0xea,0xe9,
+ 0xe9,0xe9,0xe9,0xe8,0xe8,0xe8,0xe8,0xe7,
+ 0xe7,0xe7,0xe7,0xe6,0xe6,0xe6,0xe6,0xe5,
+ 0xe5,0xe5,0xe5,0xe4,0xe4,0xe4,0xe4,0xe3,
+ 0xe3,0xe3,0xe3,0xe2,0xe2,0xe2,0xe2,0xe1,
+ 0xe1,0xe1,0xe1,0xe0,0xe0,0xe0,0xe0,0xdf,
+ 0xdf,0xdf,0xdf,0xdf,0xdf,0xdf,0xdf,0xde,
+ 0xde,0xde,0xde,0xde,0xde,0xde,0xde,0xdd,
+ 0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdc,
+ 0xdc,0xdc,0xdc,0xdc,0xdc,0xdc,0xdc,0xdb,
+ 0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xda,
+ 0xda,0xda,0xda,0xda,0xda,0xda,0xda,0xd9,
+ 0xd9,0xd9,0xd9,0xd9,0xd9,0xd9,0xd9,0xd8,
+ 0xd8,0xd8,0xd8,0xd8,0xd8,0xd8,0xd8,0xd7,
+ 0xd7,0xd7,0xd7,0xd7,0xd7,0xd7,0xd7,0xd6,
+ 0xd6,0xd6,0xd6,0xd6,0xd6,0xd6,0xd6,0xd5,
+ 0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xd4,
+ 0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd3,
+ 0xd3,0xd3,0xd3,0xd3,0xd3,0xd3,0xd3,0xd2,
+ 0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd1,
+ 0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd0,
+ 0xd0,0xd0,0xd0,0xd0,0xd0,0xd0,0xd0,0xcf,
+ 0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,
+ 0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xce,
+ 0xce,0xce,0xce,0xce,0xce,0xce,0xce,0xce,
+ 0xce,0xce,0xce,0xce,0xce,0xce,0xce,0xcd,
+ 0xcd,0xcd,0xcd,0xcd,0xcd,0xcd,0xcd,0xcd,
+ 0xcd,0xcd,0xcd,0xcd,0xcd,0xcd,0xcd,0xcc,
+ 0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,
+ 0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcb,
+ 0xcb,0xcb,0xcb,0xcb,0xcb,0xcb,0xcb,0xcb,
+ 0xcb,0xcb,0xcb,0xcb,0xcb,0xcb,0xcb,0xca,
+ 0xca,0xca,0xca,0xca,0xca,0xca,0xca,0xca,
+ 0xca,0xca,0xca,0xca,0xca,0xca,0xca,0xc9,
+ 0xc9,0xc9,0xc9,0xc9,0xc9,0xc9,0xc9,0xc9,
+ 0xc9,0xc9,0xc9,0xc9,0xc9,0xc9,0xc9,0xc8,
+ 0xc8,0xc8,0xc8,0xc8,0xc8,0xc8,0xc8,0xc8,
+ 0xc8,0xc8,0xc8,0xc8,0xc8,0xc8,0xc8,0xc7,
+ 0xc7,0xc7,0xc7,0xc7,0xc7,0xc7,0xc7,0xc7,
+ 0xc7,0xc7,0xc7,0xc7,0xc7,0xc7,0xc7,0xc6,
+ 0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,
+ 0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xc5,
+ 0xc5,0xc5,0xc5,0xc5,0xc5,0xc5,0xc5,0xc5,
+ 0xc5,0xc5,0xc5,0xc5,0xc5,0xc5,0xc5,0xc4,
+ 0xc4,0xc4,0xc4,0xc4,0xc4,0xc4,0xc4,0xc4,
+ 0xc4,0xc4,0xc4,0xc4,0xc4,0xc4,0xc4,0xc3,
+ 0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,
+ 0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc2,
+ 0xc2,0xc2,0xc2,0xc2,0xc2,0xc2,0xc2,0xc2,
+ 0xc2,0xc2,0xc2,0xc2,0xc2,0xc2,0xc2,0xc1,
+ 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,
+ 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc0,
+ 0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,
+ 0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xbf,
+ 0xbf,0xbf,0xbf,0xbf,0xbf,0xbf,0xbf,0xbf,
+ 0xbf,0xbf,0xbf,0xbf,0xbf,0xbf,0xbf,0xbf,
+ 0xbf,0xbf,0xbf,0xbf,0xbf,0xbf,0xbf,0xbf,
+ 0xbf,0xbf,0xbf,0xbf,0xbf,0xbf,0xbf,0xbe,
+ 0xbe,0xbe,0xbe,0xbe,0xbe,0xbe,0xbe,0xbe,
+ 0xbe,0xbe,0xbe,0xbe,0xbe,0xbe,0xbe,0xbe,
+ 0xbe,0xbe,0xbe,0xbe,0xbe,0xbe,0xbe,0xbe,
+ 0xbe,0xbe,0xbe,0xbe,0xbe,0xbe,0xbe,0xbd,
+ 0xbd,0xbd,0xbd,0xbd,0xbd,0xbd,0xbd,0xbd,
+ 0xbd,0xbd,0xbd,0xbd,0xbd,0xbd,0xbd,0xbd,
+ 0xbd,0xbd,0xbd,0xbd,0xbd,0xbd,0xbd,0xbd,
+ 0xbd,0xbd,0xbd,0xbd,0xbd,0xbd,0xbd,0xbc,
+ 0xbc,0xbc,0xbc,0xbc,0xbc,0xbc,0xbc,0xbc,
+ 0xbc,0xbc,0xbc,0xbc,0xbc,0xbc,0xbc,0xbc,
+ 0xbc,0xbc,0xbc,0xbc,0xbc,0xbc,0xbc,0xbc,
+ 0xbc,0xbc,0xbc,0xbc,0xbc,0xbc,0xbc,0xbb,
+ 0xbb,0xbb,0xbb,0xbb,0xbb,0xbb,0xbb,0xbb,
+ 0xbb,0xbb,0xbb,0xbb,0xbb,0xbb,0xbb,0xbb,
+ 0xbb,0xbb,0xbb,0xbb,0xbb,0xbb,0xbb,0xbb,
+ 0xbb,0xbb,0xbb,0xbb,0xbb,0xbb,0xbb,0xba,
+ 0xba,0xba,0xba,0xba,0xba,0xba,0xba,0xba,
+ 0xba,0xba,0xba,0xba,0xba,0xba,0xba,0xba,
+ 0xba,0xba,0xba,0xba,0xba,0xba,0xba,0xba,
+ 0xba,0xba,0xba,0xba,0xba,0xba,0xba,0xb9,
+ 0xb9,0xb9,0xb9,0xb9,0xb9,0xb9,0xb9,0xb9,
+ 0xb9,0xb9,0xb9,0xb9,0xb9,0xb9,0xb9,0xb9,
+ 0xb9,0xb9,0xb9,0xb9,0xb9,0xb9,0xb9,0xb9,
+ 0xb9,0xb9,0xb9,0xb9,0xb9,0xb9,0xb9,0xb8,
+ 0xb8,0xb8,0xb8,0xb8,0xb8,0xb8,0xb8,0xb8,
+ 0xb8,0xb8,0xb8,0xb8,0xb8,0xb8,0xb8,0xb8,
+ 0xb8,0xb8,0xb8,0xb8,0xb8,0xb8,0xb8,0xb8,
+ 0xb8,0xb8,0xb8,0xb8,0xb8,0xb8,0xb8,0xb7,
+ 0xb7,0xb7,0xb7,0xb7,0xb7,0xb7,0xb7,0xb7,
+ 0xb7,0xb7,0xb7,0xb7,0xb7,0xb7,0xb7,0xb7,
+ 0xb7,0xb7,0xb7,0xb7,0xb7,0xb7,0xb7,0xb7,
+ 0xb7,0xb7,0xb7,0xb7,0xb7,0xb7,0xb7,0xb6,
+ 0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,
+ 0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,
+ 0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,
+ 0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,0xb5,
+ 0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,
+ 0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,
+ 0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,
+ 0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb4,
+ 0xb4,0xb4,0xb4,0xb4,0xb4,0xb4,0xb4,0xb4,
+ 0xb4,0xb4,0xb4,0xb4,0xb4,0xb4,0xb4,0xb4,
+ 0xb4,0xb4,0xb4,0xb4,0xb4,0xb4,0xb4,0xb4,
+ 0xb4,0xb4,0xb4,0xb4,0xb4,0xb4,0xb4,0xb3,
+ 0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,
+ 0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,
+ 0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,
+ 0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,0xb2,
+ 0xb2,0xb2,0xb2,0xb2,0xb2,0xb2,0xb2,0xb2,
+ 0xb2,0xb2,0xb2,0xb2,0xb2,0xb2,0xb2,0xb2,
+ 0xb2,0xb2,0xb2,0xb2,0xb2,0xb2,0xb2,0xb2,
+ 0xb2,0xb2,0xb2,0xb2,0xb2,0xb2,0xb2,0xb1,
+ 0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,
+ 0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,
+ 0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,
+ 0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0xb0,
+ 0xb0,0xb0,0xb0,0xb0,0xb0,0xb0,0xb0,0xb0,
+ 0xb0,0xb0,0xb0,0xb0,0xb0,0xb0,0xb0,0xb0,
+ 0xb0,0xb0,0xb0,0xb0,0xb0,0xb0,0xb0,0xb0,
+ 0xb0,0xb0,0xb0,0xb0,0xb0,0xb0,0xb0,0xaf,
+ 0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,
+ 0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,
+ 0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,
+ 0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,
+ 0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,
+ 0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,
+ 0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,
+ 0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xae,
+ 0xae,0xae,0xae,0xae,0xae,0xae,0xae,0xae,
+ 0xae,0xae,0xae,0xae,0xae,0xae,0xae,0xae,
+ 0xae,0xae,0xae,0xae,0xae,0xae,0xae,0xae,
+ 0xae,0xae,0xae,0xae,0xae,0xae,0xae,0xae,
+ 0xae,0xae,0xae,0xae,0xae,0xae,0xae,0xae,
+ 0xae,0xae,0xae,0xae,0xae,0xae,0xae,0xae,
+ 0xae,0xae,0xae,0xae,0xae,0xae,0xae,0xae,
+ 0xae,0xae,0xae,0xae,0xae,0xae,0xae,0xad,
+ 0xad,0xad,0xad,0xad,0xad,0xad,0xad,0xad,
+ 0xad,0xad,0xad,0xad,0xad,0xad,0xad,0xad,
+ 0xad,0xad,0xad,0xad,0xad,0xad,0xad,0xad,
+ 0xad,0xad,0xad,0xad,0xad,0xad,0xad,0xad,
+ 0xad,0xad,0xad,0xad,0xad,0xad,0xad,0xad,
+ 0xad,0xad,0xad,0xad,0xad,0xad,0xad,0xad,
+ 0xad,0xad,0xad,0xad,0xad,0xad,0xad,0xad,
+ 0xad,0xad,0xad,0xad,0xad,0xad,0xad,0xac,
+ 0xac,0xac,0xac,0xac,0xac,0xac,0xac,0xac,
+ 0xac,0xac,0xac,0xac,0xac,0xac,0xac,0xac,
+ 0xac,0xac,0xac,0xac,0xac,0xac,0xac,0xac,
+ 0xac,0xac,0xac,0xac,0xac,0xac,0xac,0xac,
+ 0xac,0xac,0xac,0xac,0xac,0xac,0xac,0xac,
+ 0xac,0xac,0xac,0xac,0xac,0xac,0xac,0xac,
+ 0xac,0xac,0xac,0xac,0xac,0xac,0xac,0xac,
+ 0xac,0xac,0xac,0xac,0xac,0xac,0xac,0xab,
+ 0xab,0xab,0xab,0xab,0xab,0xab,0xab,0xab,
+ 0xab,0xab,0xab,0xab,0xab,0xab,0xab,0xab,
+ 0xab,0xab,0xab,0xab,0xab,0xab,0xab,0xab,
+ 0xab,0xab,0xab,0xab,0xab,0xab,0xab,0xab,
+ 0xab,0xab,0xab,0xab,0xab,0xab,0xab,0xab,
+ 0xab,0xab,0xab,0xab,0xab,0xab,0xab,0xab,
+ 0xab,0xab,0xab,0xab,0xab,0xab,0xab,0xab,
+ 0xab,0xab,0xab,0xab,0xab,0xab,0xab,0xaa,
+ 0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,
+ 0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,
+ 0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,
+ 0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,
+ 0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,
+ 0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,
+ 0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,
+ 0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xa9,
+ 0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,
+ 0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,
+ 0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,
+ 0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,
+ 0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,
+ 0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,
+ 0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,
+ 0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa8,
+ 0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,
+ 0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,
+ 0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,
+ 0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,
+ 0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,
+ 0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,
+ 0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,
+ 0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa7,
+ 0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,
+ 0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,
+ 0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,
+ 0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,
+ 0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,
+ 0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,
+ 0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,
+ 0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,0xa6,
+ 0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,
+ 0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,
+ 0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,
+ 0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,
+ 0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,
+ 0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,
+ 0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,
+ 0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa5,
+ 0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,
+ 0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,
+ 0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,
+ 0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,
+ 0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,
+ 0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,
+ 0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,
+ 0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa4,
+ 0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,
+ 0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,
+ 0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,
+ 0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,
+ 0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,
+ 0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,
+ 0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,
+ 0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa3,
+ 0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,
+ 0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,
+ 0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,
+ 0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,
+ 0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,
+ 0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,
+ 0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,
+ 0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,0xa2,
+ 0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,
+ 0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,
+ 0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,
+ 0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,
+ 0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,
+ 0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,
+ 0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,
+ 0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa1,
+ 0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,
+ 0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,
+ 0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,
+ 0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,
+ 0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,
+ 0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,
+ 0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,
+ 0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa0,
+ 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,
+ 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,
+ 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,
+ 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,
+ 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,
+ 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,
+ 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,
+ 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0x9f,
+ 0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,
+ 0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,
+ 0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,
+ 0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,
+ 0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,
+ 0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,
+ 0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,
+ 0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,
+ 0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,
+ 0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,
+ 0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,
+ 0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,
+ 0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,
+ 0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,
+ 0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,
+ 0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9e,
+ 0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,
+ 0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,
+ 0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,
+ 0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,
+ 0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,
+ 0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,
+ 0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,
+ 0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,
+ 0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,
+ 0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,
+ 0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,
+ 0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,
+ 0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,
+ 0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,
+ 0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,
+ 0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9d,
+ 0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,
+ 0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,
+ 0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,
+ 0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,
+ 0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,
+ 0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,
+ 0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,
+ 0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,
+ 0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,
+ 0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,
+ 0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,
+ 0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,
+ 0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,
+ 0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,
+ 0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,
+ 0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9c,
+ 0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,
+ 0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,
+ 0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,
+ 0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,
+ 0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,
+ 0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,
+ 0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,
+ 0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,
+ 0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,
+ 0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,
+ 0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,
+ 0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,
+ 0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,
+ 0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,
+ 0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,
+ 0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9b,
+ 0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,
+ 0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,
+ 0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,
+ 0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,
+ 0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,
+ 0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,
+ 0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,
+ 0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,
+ 0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,
+ 0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,
+ 0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,
+ 0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,
+ 0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,
+ 0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,
+ 0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,
+ 0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9a,
+ 0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,
+ 0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,
+ 0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,
+ 0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,
+ 0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,
+ 0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,
+ 0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,
+ 0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,
+ 0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,
+ 0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,
+ 0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,
+ 0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,
+ 0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,
+ 0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,
+ 0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,
+ 0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x99,
+ 0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,
+ 0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,
+ 0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,
+ 0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,
+ 0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,
+ 0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,
+ 0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,
+ 0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,
+ 0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,
+ 0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,
+ 0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,
+ 0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,
+ 0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,
+ 0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,
+ 0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,
+ 0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x98,
+ 0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x98,
+ 0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x98,
+ 0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x98,
+ 0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x98,
+ 0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x98,
+ 0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x98,
+ 0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x98,
+ 0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x98,
+ 0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x98,
+ 0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x98,
+ 0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x98,
+ 0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x98,
+ 0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x98,
+ 0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x98,
+ 0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x98,
+ 0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x97,
+ 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x97,
+ 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x97,
+ 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x97,
+ 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x97,
+ 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x97,
+ 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x97,
+ 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x97,
+ 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x97,
+ 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x97,
+ 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x97,
+ 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x97,
+ 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x97,
+ 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x97,
+ 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x97,
+ 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x97,
+ 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x96,
+ 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,
+ 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,
+ 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,
+ 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,
+ 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,
+ 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,
+ 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,
+ 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,
+ 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,
+ 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,
+ 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,
+ 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,
+ 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,
+ 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,
+ 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,
+ 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x95,
+ 0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,
+ 0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,
+ 0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,
+ 0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,
+ 0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,
+ 0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,
+ 0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,
+ 0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,
+ 0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,
+ 0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,
+ 0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,
+ 0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,
+ 0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,
+ 0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,
+ 0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,
+ 0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x94,
+ 0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,
+ 0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,
+ 0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,
+ 0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,
+ 0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,
+ 0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,
+ 0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,
+ 0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,
+ 0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,
+ 0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,
+ 0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,
+ 0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,
+ 0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,
+ 0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,
+ 0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,
+ 0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x93,
+ 0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+ 0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+ 0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+ 0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+ 0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+ 0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+ 0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+ 0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+ 0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+ 0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+ 0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+ 0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+ 0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+ 0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+ 0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+ 0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x92,
+ 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+ 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+ 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+ 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+ 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+ 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+ 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+ 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+ 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+ 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+ 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+ 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+ 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+ 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+ 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+ 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x91,
+ 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,
+ 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,
+ 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,
+ 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,
+ 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,
+ 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,
+ 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,
+ 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,
+ 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,
+ 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,
+ 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,
+ 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,
+ 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,
+ 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,
+ 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,
+ 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x90,
+ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
+ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
+ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
+ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
+ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
+ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
+ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
+ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
+ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
+ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
+ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
+ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
+ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
+ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
+ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
+ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,
+ 0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,
+ 0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,
+ 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,
+ 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,
+ 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,
+ 0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
+ 0x0a,0x0a,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+ 0x0b,0x0b,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
+ 0x0c,0x0c,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
+ 0x0d,0x0d,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
+ 0x0e,0x0e,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+ 0x0f,0x0f,0x10,0x10,0x10,0x10,0x10,0x10,
+ 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+ 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+ 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+ 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+ 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+ 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+ 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+ 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+ 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+ 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+ 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+ 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+ 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+ 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+ 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+ 0x10,0x10,0x11,0x11,0x11,0x11,0x11,0x11,
+ 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
+ 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
+ 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
+ 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
+ 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
+ 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
+ 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
+ 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
+ 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
+ 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
+ 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
+ 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
+ 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
+ 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
+ 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
+ 0x11,0x11,0x12,0x12,0x12,0x12,0x12,0x12,
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
+ 0x12,0x12,0x13,0x13,0x13,0x13,0x13,0x13,
+ 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+ 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+ 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+ 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+ 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+ 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+ 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+ 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+ 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+ 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+ 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+ 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+ 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+ 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+ 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+ 0x13,0x13,0x14,0x14,0x14,0x14,0x14,0x14,
+ 0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
+ 0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
+ 0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
+ 0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
+ 0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
+ 0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
+ 0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
+ 0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
+ 0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
+ 0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
+ 0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
+ 0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
+ 0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
+ 0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
+ 0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
+ 0x14,0x14,0x15,0x15,0x15,0x15,0x15,0x15,
+ 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,
+ 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,
+ 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,
+ 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,
+ 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,
+ 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,
+ 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,
+ 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,
+ 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,
+ 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,
+ 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,
+ 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,
+ 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,
+ 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,
+ 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,
+ 0x15,0x15,0x16,0x16,0x16,0x16,0x16,0x16,
+ 0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
+ 0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
+ 0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
+ 0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
+ 0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
+ 0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
+ 0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
+ 0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
+ 0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
+ 0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
+ 0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
+ 0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
+ 0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
+ 0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
+ 0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
+ 0x16,0x16,0x17,0x17,0x17,0x17,0x17,0x17,
+ 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,
+ 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,
+ 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,
+ 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,
+ 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,
+ 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,
+ 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,
+ 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,
+ 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,
+ 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,
+ 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,
+ 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,
+ 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,
+ 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,
+ 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,
+ 0x17,0x17,0x18,0x18,0x18,0x18,0x18,0x18,
+ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
+ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
+ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
+ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
+ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
+ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
+ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
+ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
+ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
+ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
+ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
+ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
+ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
+ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
+ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
+ 0x18,0x18,0x19,0x19,0x19,0x19,0x19,0x19,
+ 0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,
+ 0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,
+ 0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,
+ 0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,
+ 0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,
+ 0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,
+ 0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,
+ 0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,
+ 0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,
+ 0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,
+ 0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,
+ 0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,
+ 0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,
+ 0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,
+ 0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,
+ 0x19,0x19,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,
+ 0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,
+ 0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,
+ 0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,
+ 0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,
+ 0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,
+ 0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,
+ 0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,
+ 0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,
+ 0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,
+ 0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,
+ 0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,
+ 0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,
+ 0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,
+ 0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,
+ 0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,
+ 0x1a,0x1a,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,
+ 0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,
+ 0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,
+ 0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,
+ 0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,
+ 0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,
+ 0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,
+ 0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,
+ 0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,
+ 0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,
+ 0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,
+ 0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,
+ 0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,
+ 0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,
+ 0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,
+ 0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,
+ 0x1b,0x1b,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,
+ 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,
+ 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,
+ 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,
+ 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,
+ 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,
+ 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,
+ 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,
+ 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,
+ 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,
+ 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,
+ 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,
+ 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,
+ 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,
+ 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,
+ 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,
+ 0x1c,0x1c,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,
+ 0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,
+ 0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,
+ 0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,
+ 0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,
+ 0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,
+ 0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,
+ 0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,
+ 0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,
+ 0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,
+ 0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,
+ 0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,
+ 0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,
+ 0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,
+ 0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,
+ 0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,
+ 0x1d,0x1d,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+ 0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+ 0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+ 0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+ 0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+ 0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+ 0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+ 0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+ 0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+ 0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+ 0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+ 0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+ 0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+ 0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+ 0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+ 0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+ 0x1e,0x1e,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
+ 0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
+ 0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
+ 0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
+ 0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
+ 0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
+ 0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
+ 0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
+ 0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
+ 0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
+ 0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
+ 0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
+ 0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
+ 0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
+ 0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
+ 0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
+ 0x1f,0x1f,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,
+ 0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,
+ 0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,
+ 0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,
+ 0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,
+ 0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,
+ 0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,
+ 0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,
+ 0x2a,0x2a,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,
+ 0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,
+ 0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,
+ 0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,
+ 0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,
+ 0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,
+ 0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,
+ 0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,
+ 0x2b,0x2b,0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,
+ 0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,
+ 0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,
+ 0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,
+ 0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,
+ 0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,
+ 0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,
+ 0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,
+ 0x2c,0x2c,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,
+ 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,
+ 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,
+ 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,
+ 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,
+ 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,
+ 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,
+ 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,
+ 0x2d,0x2d,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,
+ 0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,
+ 0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,
+ 0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,
+ 0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,
+ 0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,
+ 0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,
+ 0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,
+ 0x2e,0x2e,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,
+ 0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,
+ 0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,
+ 0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,
+ 0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,
+ 0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,
+ 0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,
+ 0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,
+ 0x2f,0x2f,0x30,0x30,0x30,0x30,0x30,0x30,
+ 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+ 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+ 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+ 0x30,0x30,0x31,0x31,0x31,0x31,0x31,0x31,
+ 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
+ 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
+ 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
+ 0x31,0x31,0x32,0x32,0x32,0x32,0x32,0x32,
+ 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,
+ 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,
+ 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,
+ 0x32,0x32,0x33,0x33,0x33,0x33,0x33,0x33,
+ 0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
+ 0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
+ 0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
+ 0x33,0x33,0x34,0x34,0x34,0x34,0x34,0x34,
+ 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,
+ 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,
+ 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,
+ 0x34,0x34,0x35,0x35,0x35,0x35,0x35,0x35,
+ 0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,
+ 0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,
+ 0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,
+ 0x35,0x35,0x36,0x36,0x36,0x36,0x36,0x36,
+ 0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
+ 0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
+ 0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
+ 0x36,0x36,0x37,0x37,0x37,0x37,0x37,0x37,
+ 0x37,0x37,0x37,0x37,0x37,0x37,0x37,0x37,
+ 0x37,0x37,0x37,0x37,0x37,0x37,0x37,0x37,
+ 0x37,0x37,0x37,0x37,0x37,0x37,0x37,0x37,
+ 0x37,0x37,0x38,0x38,0x38,0x38,0x38,0x38,
+ 0x38,0x38,0x38,0x38,0x38,0x38,0x38,0x38,
+ 0x38,0x38,0x38,0x38,0x38,0x38,0x38,0x38,
+ 0x38,0x38,0x38,0x38,0x38,0x38,0x38,0x38,
+ 0x38,0x38,0x39,0x39,0x39,0x39,0x39,0x39,
+ 0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,
+ 0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,
+ 0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,
+ 0x39,0x39,0x3a,0x3a,0x3a,0x3a,0x3a,0x3a,
+ 0x3a,0x3a,0x3a,0x3a,0x3a,0x3a,0x3a,0x3a,
+ 0x3a,0x3a,0x3a,0x3a,0x3a,0x3a,0x3a,0x3a,
+ 0x3a,0x3a,0x3a,0x3a,0x3a,0x3a,0x3a,0x3a,
+ 0x3a,0x3a,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,
+ 0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,
+ 0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,
+ 0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,
+ 0x3b,0x3b,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,
+ 0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,
+ 0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,
+ 0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,
+ 0x3c,0x3c,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,
+ 0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,
+ 0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,
+ 0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,
+ 0x3d,0x3d,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,
+ 0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,
+ 0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,
+ 0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,
+ 0x3e,0x3e,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,
+ 0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,
+ 0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,
+ 0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,
+ 0x3f,0x3f,0x40,0x40,0x40,0x40,0x40,0x40,
+ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,
+ 0x40,0x40,0x41,0x41,0x41,0x41,0x41,0x41,
+ 0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,
+ 0x41,0x41,0x42,0x42,0x42,0x42,0x42,0x42,
+ 0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,
+ 0x42,0x42,0x43,0x43,0x43,0x43,0x43,0x43,
+ 0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x43,
+ 0x43,0x43,0x44,0x44,0x44,0x44,0x44,0x44,
+ 0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,
+ 0x44,0x44,0x45,0x45,0x45,0x45,0x45,0x45,
+ 0x45,0x45,0x45,0x45,0x45,0x45,0x45,0x45,
+ 0x45,0x45,0x46,0x46,0x46,0x46,0x46,0x46,
+ 0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,
+ 0x46,0x46,0x47,0x47,0x47,0x47,0x47,0x47,
+ 0x47,0x47,0x47,0x47,0x47,0x47,0x47,0x47,
+ 0x47,0x47,0x48,0x48,0x48,0x48,0x48,0x48,
+ 0x48,0x48,0x48,0x48,0x48,0x48,0x48,0x48,
+ 0x48,0x48,0x49,0x49,0x49,0x49,0x49,0x49,
+ 0x49,0x49,0x49,0x49,0x49,0x49,0x49,0x49,
+ 0x49,0x49,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,
+ 0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,
+ 0x4a,0x4a,0x4b,0x4b,0x4b,0x4b,0x4b,0x4b,
+ 0x4b,0x4b,0x4b,0x4b,0x4b,0x4b,0x4b,0x4b,
+ 0x4b,0x4b,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,
+ 0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,
+ 0x4c,0x4c,0x4d,0x4d,0x4d,0x4d,0x4d,0x4d,
+ 0x4d,0x4d,0x4d,0x4d,0x4d,0x4d,0x4d,0x4d,
+ 0x4d,0x4d,0x4e,0x4e,0x4e,0x4e,0x4e,0x4e,
+ 0x4e,0x4e,0x4e,0x4e,0x4e,0x4e,0x4e,0x4e,
+ 0x4e,0x4e,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,
+ 0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,
+ 0x4f,0x4f,0x50,0x50,0x50,0x50,0x50,0x50,
+ 0x50,0x50,0x51,0x51,0x51,0x51,0x51,0x51,
+ 0x51,0x51,0x52,0x52,0x52,0x52,0x52,0x52,
+ 0x52,0x52,0x53,0x53,0x53,0x53,0x53,0x53,
+ 0x53,0x53,0x54,0x54,0x54,0x54,0x54,0x54,
+ 0x54,0x54,0x55,0x55,0x55,0x55,0x55,0x55,
+ 0x55,0x55,0x56,0x56,0x56,0x56,0x56,0x56,
+ 0x56,0x56,0x57,0x57,0x57,0x57,0x57,0x57,
+ 0x57,0x57,0x58,0x58,0x58,0x58,0x58,0x58,
+ 0x58,0x58,0x59,0x59,0x59,0x59,0x59,0x59,
+ 0x59,0x59,0x5a,0x5a,0x5a,0x5a,0x5a,0x5a,
+ 0x5a,0x5a,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,
+ 0x5b,0x5b,0x5c,0x5c,0x5c,0x5c,0x5c,0x5c,
+ 0x5c,0x5c,0x5d,0x5d,0x5d,0x5d,0x5d,0x5d,
+ 0x5d,0x5d,0x5e,0x5e,0x5e,0x5e,0x5e,0x5e,
+ 0x5e,0x5e,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,
+ 0x5f,0x5f,0x60,0x60,0x60,0x60,0x61,0x61,
+ 0x61,0x61,0x62,0x62,0x62,0x62,0x63,0x63,
+ 0x63,0x63,0x64,0x64,0x64,0x64,0x65,0x65,
+ 0x65,0x65,0x66,0x66,0x66,0x66,0x67,0x67,
+ 0x67,0x67,0x68,0x68,0x68,0x68,0x69,0x69,
+ 0x69,0x69,0x6a,0x6a,0x6a,0x6a,0x6b,0x6b,
+ 0x6b,0x6b,0x6c,0x6c,0x6c,0x6c,0x6d,0x6d,
+ 0x6d,0x6d,0x6e,0x6e,0x6e,0x6e,0x6f,0x6f,
+ 0x6f,0x6f,0x70,0x70,0x71,0x71,0x72,0x72,
+ 0x73,0x73,0x74,0x74,0x75,0x75,0x76,0x76,
+ 0x77,0x77,0x78,0x78,0x79,0x79,0x7a,0x7a,
+ 0x7b,0x7b,0x7c,0x7c,0x7d,0x7d,0x7e,0x7e
+};
+
+
+const pj_uint8_t pjmedia_linear2alaw_tab[16384] =
+{
+ 0xD5,0xD5,0xD5,0xD5,0xD4,0xD4,0xD4,0xD4,
+ 0xD7,0xD7,0xD7,0xD7,0xD6,0xD6,0xD6,0xD6,
+ 0xD1,0xD1,0xD1,0xD1,0xD0,0xD0,0xD0,0xD0,
+ 0xD3,0xD3,0xD3,0xD3,0xD2,0xD2,0xD2,0xD2,
+ 0xDD,0xDD,0xDD,0xDD,0xDC,0xDC,0xDC,0xDC,
+ 0xDF,0xDF,0xDF,0xDF,0xDE,0xDE,0xDE,0xDE,
+ 0xD9,0xD9,0xD9,0xD9,0xD8,0xD8,0xD8,0xD8,
+ 0xDB,0xDB,0xDB,0xDB,0xDA,0xDA,0xDA,0xDA,
+ 0xC5,0xC5,0xC5,0xC5,0xC4,0xC4,0xC4,0xC4,
+ 0xC7,0xC7,0xC7,0xC7,0xC6,0xC6,0xC6,0xC6,
+ 0xC1,0xC1,0xC1,0xC1,0xC0,0xC0,0xC0,0xC0,
+ 0xC3,0xC3,0xC3,0xC3,0xC2,0xC2,0xC2,0xC2,
+ 0xCD,0xCD,0xCD,0xCD,0xCC,0xCC,0xCC,0xCC,
+ 0xCF,0xCF,0xCF,0xCF,0xCE,0xCE,0xCE,0xCE,
+ 0xC9,0xC9,0xC9,0xC9,0xC8,0xC8,0xC8,0xC8,
+ 0xCB,0xCB,0xCB,0xCB,0xCA,0xCA,0xCA,0xCA,
+ 0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,
+ 0xF4,0xF4,0xF4,0xF4,0xF4,0xF4,0xF4,0xF4,
+ 0xF7,0xF7,0xF7,0xF7,0xF7,0xF7,0xF7,0xF7,
+ 0xF6,0xF6,0xF6,0xF6,0xF6,0xF6,0xF6,0xF6,
+ 0xF1,0xF1,0xF1,0xF1,0xF1,0xF1,0xF1,0xF1,
+ 0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,
+ 0xF3,0xF3,0xF3,0xF3,0xF3,0xF3,0xF3,0xF3,
+ 0xF2,0xF2,0xF2,0xF2,0xF2,0xF2,0xF2,0xF2,
+ 0xFD,0xFD,0xFD,0xFD,0xFD,0xFD,0xFD,0xFD,
+ 0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,
+ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+ 0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,
+ 0xF9,0xF9,0xF9,0xF9,0xF9,0xF9,0xF9,0xF9,
+ 0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,
+ 0xFB,0xFB,0xFB,0xFB,0xFB,0xFB,0xFB,0xFB,
+ 0xFA,0xFA,0xFA,0xFA,0xFA,0xFA,0xFA,0xFA,
+ 0xE5,0xE5,0xE5,0xE5,0xE5,0xE5,0xE5,0xE5,
+ 0xE5,0xE5,0xE5,0xE5,0xE5,0xE5,0xE5,0xE5,
+ 0xE4,0xE4,0xE4,0xE4,0xE4,0xE4,0xE4,0xE4,
+ 0xE4,0xE4,0xE4,0xE4,0xE4,0xE4,0xE4,0xE4,
+ 0xE7,0xE7,0xE7,0xE7,0xE7,0xE7,0xE7,0xE7,
+ 0xE7,0xE7,0xE7,0xE7,0xE7,0xE7,0xE7,0xE7,
+ 0xE6,0xE6,0xE6,0xE6,0xE6,0xE6,0xE6,0xE6,
+ 0xE6,0xE6,0xE6,0xE6,0xE6,0xE6,0xE6,0xE6,
+ 0xE1,0xE1,0xE1,0xE1,0xE1,0xE1,0xE1,0xE1,
+ 0xE1,0xE1,0xE1,0xE1,0xE1,0xE1,0xE1,0xE1,
+ 0xE0,0xE0,0xE0,0xE0,0xE0,0xE0,0xE0,0xE0,
+ 0xE0,0xE0,0xE0,0xE0,0xE0,0xE0,0xE0,0xE0,
+ 0xE3,0xE3,0xE3,0xE3,0xE3,0xE3,0xE3,0xE3,
+ 0xE3,0xE3,0xE3,0xE3,0xE3,0xE3,0xE3,0xE3,
+ 0xE2,0xE2,0xE2,0xE2,0xE2,0xE2,0xE2,0xE2,
+ 0xE2,0xE2,0xE2,0xE2,0xE2,0xE2,0xE2,0xE2,
+ 0xED,0xED,0xED,0xED,0xED,0xED,0xED,0xED,
+ 0xED,0xED,0xED,0xED,0xED,0xED,0xED,0xED,
+ 0xEC,0xEC,0xEC,0xEC,0xEC,0xEC,0xEC,0xEC,
+ 0xEC,0xEC,0xEC,0xEC,0xEC,0xEC,0xEC,0xEC,
+ 0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,
+ 0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,
+ 0xEE,0xEE,0xEE,0xEE,0xEE,0xEE,0xEE,0xEE,
+ 0xEE,0xEE,0xEE,0xEE,0xEE,0xEE,0xEE,0xEE,
+ 0xE9,0xE9,0xE9,0xE9,0xE9,0xE9,0xE9,0xE9,
+ 0xE9,0xE9,0xE9,0xE9,0xE9,0xE9,0xE9,0xE9,
+ 0xE8,0xE8,0xE8,0xE8,0xE8,0xE8,0xE8,0xE8,
+ 0xE8,0xE8,0xE8,0xE8,0xE8,0xE8,0xE8,0xE8,
+ 0xEB,0xEB,0xEB,0xEB,0xEB,0xEB,0xEB,0xEB,
+ 0xEB,0xEB,0xEB,0xEB,0xEB,0xEB,0xEB,0xEB,
+ 0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,
+ 0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,0xEA,
+ 0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,
+ 0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,
+ 0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,
+ 0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,
+ 0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,
+ 0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,
+ 0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,
+ 0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,
+ 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x97,
+ 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x97,
+ 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x97,
+ 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x97,
+ 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,
+ 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,
+ 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,
+ 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,
+ 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,
+ 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,
+ 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,
+ 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,
+ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
+ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
+ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
+ 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
+ 0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+ 0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+ 0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+ 0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+ 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+ 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+ 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+ 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+ 0x9D,0x9D,0x9D,0x9D,0x9D,0x9D,0x9D,0x9D,
+ 0x9D,0x9D,0x9D,0x9D,0x9D,0x9D,0x9D,0x9D,
+ 0x9D,0x9D,0x9D,0x9D,0x9D,0x9D,0x9D,0x9D,
+ 0x9D,0x9D,0x9D,0x9D,0x9D,0x9D,0x9D,0x9D,
+ 0x9C,0x9C,0x9C,0x9C,0x9C,0x9C,0x9C,0x9C,
+ 0x9C,0x9C,0x9C,0x9C,0x9C,0x9C,0x9C,0x9C,
+ 0x9C,0x9C,0x9C,0x9C,0x9C,0x9C,0x9C,0x9C,
+ 0x9C,0x9C,0x9C,0x9C,0x9C,0x9C,0x9C,0x9C,
+ 0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,
+ 0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,
+ 0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,
+ 0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,
+ 0x9E,0x9E,0x9E,0x9E,0x9E,0x9E,0x9E,0x9E,
+ 0x9E,0x9E,0x9E,0x9E,0x9E,0x9E,0x9E,0x9E,
+ 0x9E,0x9E,0x9E,0x9E,0x9E,0x9E,0x9E,0x9E,
+ 0x9E,0x9E,0x9E,0x9E,0x9E,0x9E,0x9E,0x9E,
+ 0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,
+ 0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,
+ 0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,
+ 0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,
+ 0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x98,
+ 0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x98,
+ 0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x98,
+ 0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x98,
+ 0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,
+ 0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,
+ 0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,
+ 0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,0x9B,
+ 0x9A,0x9A,0x9A,0x9A,0x9A,0x9A,0x9A,0x9A,
+ 0x9A,0x9A,0x9A,0x9A,0x9A,0x9A,0x9A,0x9A,
+ 0x9A,0x9A,0x9A,0x9A,0x9A,0x9A,0x9A,0x9A,
+ 0x9A,0x9A,0x9A,0x9A,0x9A,0x9A,0x9A,0x9A,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82,
+ 0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,
+ 0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,
+ 0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,
+ 0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,
+ 0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,
+ 0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,
+ 0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,
+ 0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,0x8D,
+ 0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,
+ 0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,
+ 0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,
+ 0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,
+ 0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,
+ 0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,
+ 0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,
+ 0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,0x8C,
+ 0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,
+ 0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,
+ 0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,
+ 0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,
+ 0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,
+ 0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,
+ 0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,
+ 0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,
+ 0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,
+ 0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,
+ 0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,
+ 0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,
+ 0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,
+ 0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,
+ 0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,
+ 0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,0x8E,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,
+ 0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,
+ 0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,
+ 0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,
+ 0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,
+ 0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,
+ 0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,
+ 0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,
+ 0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,0x8B,
+ 0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,
+ 0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,
+ 0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,
+ 0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,
+ 0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,
+ 0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,
+ 0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,
+ 0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,0x8A,
+ 0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,
+ 0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,
+ 0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,
+ 0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,
+ 0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,
+ 0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,
+ 0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,
+ 0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,
+ 0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,
+ 0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,
+ 0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,
+ 0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,
+ 0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,
+ 0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,
+ 0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,
+ 0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,0xB5,
+ 0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,
+ 0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,
+ 0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,
+ 0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,
+ 0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,
+ 0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,
+ 0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,
+ 0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,
+ 0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,
+ 0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,
+ 0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,
+ 0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,
+ 0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,
+ 0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,
+ 0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,
+ 0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,0xB4,
+ 0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,
+ 0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,
+ 0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,
+ 0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,
+ 0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,
+ 0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,
+ 0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,
+ 0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,
+ 0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,
+ 0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,
+ 0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,
+ 0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,
+ 0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,
+ 0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,
+ 0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,
+ 0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,0xB7,
+ 0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,
+ 0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,
+ 0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,
+ 0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,
+ 0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,
+ 0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,
+ 0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,
+ 0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,
+ 0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,
+ 0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,
+ 0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,
+ 0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,
+ 0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,
+ 0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,
+ 0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,
+ 0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,0xB6,
+ 0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,
+ 0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,
+ 0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,
+ 0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,
+ 0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,
+ 0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,
+ 0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,
+ 0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,
+ 0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,
+ 0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,
+ 0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,
+ 0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,
+ 0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,
+ 0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,
+ 0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,
+ 0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,0xB1,
+ 0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,
+ 0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,
+ 0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,
+ 0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,
+ 0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,
+ 0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,
+ 0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,
+ 0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,
+ 0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,
+ 0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,
+ 0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,
+ 0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,
+ 0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,
+ 0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,
+ 0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,
+ 0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,0xB0,
+ 0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,
+ 0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,
+ 0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,
+ 0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,
+ 0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,
+ 0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,
+ 0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,
+ 0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,
+ 0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,
+ 0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,
+ 0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,
+ 0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,
+ 0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,
+ 0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,
+ 0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,
+ 0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,
+ 0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,
+ 0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,
+ 0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,
+ 0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,
+ 0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,
+ 0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,
+ 0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,
+ 0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,
+ 0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,
+ 0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,
+ 0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,
+ 0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,
+ 0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,
+ 0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,
+ 0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,
+ 0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,0xB2,
+ 0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,
+ 0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,
+ 0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,
+ 0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,
+ 0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,
+ 0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,
+ 0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,
+ 0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,
+ 0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,
+ 0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,
+ 0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,
+ 0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,
+ 0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,
+ 0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,
+ 0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,
+ 0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,
+ 0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,
+ 0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,
+ 0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,
+ 0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,
+ 0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,
+ 0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,
+ 0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,
+ 0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,
+ 0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,
+ 0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,
+ 0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,
+ 0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,
+ 0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,
+ 0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,
+ 0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,
+ 0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,
+ 0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,
+ 0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,
+ 0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,
+ 0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,
+ 0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,
+ 0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,
+ 0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,
+ 0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,
+ 0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,
+ 0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,
+ 0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,
+ 0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,
+ 0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,
+ 0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,
+ 0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,
+ 0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF,
+ 0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,
+ 0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,
+ 0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,
+ 0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,
+ 0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,
+ 0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,
+ 0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,
+ 0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,
+ 0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,
+ 0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,
+ 0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,
+ 0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,
+ 0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,
+ 0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,
+ 0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,
+ 0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,0xBE,
+ 0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,
+ 0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,
+ 0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,
+ 0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,
+ 0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,
+ 0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,
+ 0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,
+ 0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,
+ 0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,
+ 0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,
+ 0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,
+ 0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,
+ 0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,
+ 0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,
+ 0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,
+ 0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,0xB9,
+ 0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,
+ 0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,
+ 0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,
+ 0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,
+ 0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,
+ 0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,
+ 0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,
+ 0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,
+ 0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,
+ 0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,
+ 0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,
+ 0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,
+ 0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,
+ 0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,
+ 0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,
+ 0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,0xB8,
+ 0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,
+ 0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,
+ 0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,
+ 0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,
+ 0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,
+ 0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,
+ 0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,
+ 0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,
+ 0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,
+ 0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,
+ 0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,
+ 0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,
+ 0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,
+ 0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,
+ 0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,
+ 0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,0xBB,
+ 0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,
+ 0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,
+ 0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,
+ 0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,
+ 0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,
+ 0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,
+ 0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,
+ 0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,
+ 0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,
+ 0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,
+ 0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,
+ 0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,
+ 0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,
+ 0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,
+ 0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,
+ 0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,0xBA,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,0xA4,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,0xA7,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,0xA6,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,0xA0,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,0xA3,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,0xA2,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,0xAD,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,0xAF,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,0xAE,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,0xA8,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,0xAB,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
+ 0x2A,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,
+ 0x2B,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28,
+ 0x28,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,
+ 0x29,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,0x2E,
+ 0x2E,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,0x2F,
+ 0x2F,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,0x2C,
+ 0x2C,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+ 0x2D,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,
+ 0x22,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,
+ 0x23,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,
+ 0x21,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,
+ 0x26,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,
+ 0x27,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
+ 0x24,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,
+ 0x25,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,
+ 0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,
+ 0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,
+ 0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,
+ 0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,
+ 0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,
+ 0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,
+ 0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,
+ 0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,
+ 0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,
+ 0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,
+ 0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,
+ 0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,
+ 0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,
+ 0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,
+ 0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,0x3A,
+ 0x3A,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,
+ 0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,
+ 0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,
+ 0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,
+ 0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,
+ 0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,
+ 0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,
+ 0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,
+ 0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,
+ 0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,
+ 0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,
+ 0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,
+ 0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,
+ 0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,
+ 0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,
+ 0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,0x3B,
+ 0x3B,0x38,0x38,0x38,0x38,0x38,0x38,0x38,
+ 0x38,0x38,0x38,0x38,0x38,0x38,0x38,0x38,
+ 0x38,0x38,0x38,0x38,0x38,0x38,0x38,0x38,
+ 0x38,0x38,0x38,0x38,0x38,0x38,0x38,0x38,
+ 0x38,0x38,0x38,0x38,0x38,0x38,0x38,0x38,
+ 0x38,0x38,0x38,0x38,0x38,0x38,0x38,0x38,
+ 0x38,0x38,0x38,0x38,0x38,0x38,0x38,0x38,
+ 0x38,0x38,0x38,0x38,0x38,0x38,0x38,0x38,
+ 0x38,0x38,0x38,0x38,0x38,0x38,0x38,0x38,
+ 0x38,0x38,0x38,0x38,0x38,0x38,0x38,0x38,
+ 0x38,0x38,0x38,0x38,0x38,0x38,0x38,0x38,
+ 0x38,0x38,0x38,0x38,0x38,0x38,0x38,0x38,
+ 0x38,0x38,0x38,0x38,0x38,0x38,0x38,0x38,
+ 0x38,0x38,0x38,0x38,0x38,0x38,0x38,0x38,
+ 0x38,0x38,0x38,0x38,0x38,0x38,0x38,0x38,
+ 0x38,0x38,0x38,0x38,0x38,0x38,0x38,0x38,
+ 0x38,0x39,0x39,0x39,0x39,0x39,0x39,0x39,
+ 0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,
+ 0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,
+ 0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,
+ 0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,
+ 0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,
+ 0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,
+ 0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,
+ 0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,
+ 0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,
+ 0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,
+ 0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,
+ 0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,
+ 0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,
+ 0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,
+ 0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,
+ 0x39,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,
+ 0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,
+ 0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,
+ 0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,
+ 0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,
+ 0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,
+ 0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,
+ 0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,
+ 0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,
+ 0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,
+ 0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,
+ 0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,
+ 0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,
+ 0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,
+ 0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,
+ 0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,0x3E,
+ 0x3E,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
+ 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
+ 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
+ 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
+ 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
+ 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
+ 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
+ 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
+ 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
+ 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
+ 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
+ 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
+ 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
+ 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
+ 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
+ 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
+ 0x3F,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,
+ 0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,
+ 0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,
+ 0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,
+ 0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,
+ 0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,
+ 0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,
+ 0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,
+ 0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,
+ 0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,
+ 0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,
+ 0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,
+ 0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,
+ 0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,
+ 0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,
+ 0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,
+ 0x3C,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,
+ 0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,
+ 0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,
+ 0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,
+ 0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,
+ 0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,
+ 0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,
+ 0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,
+ 0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,
+ 0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,
+ 0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,
+ 0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,
+ 0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,
+ 0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,
+ 0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,
+ 0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,0x3D,
+ 0x3D,0x32,0x32,0x32,0x32,0x32,0x32,0x32,
+ 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,
+ 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,
+ 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,
+ 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,
+ 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,
+ 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,
+ 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,
+ 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,
+ 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,
+ 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,
+ 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,
+ 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,
+ 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,
+ 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,
+ 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x32,
+ 0x32,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
+ 0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
+ 0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
+ 0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
+ 0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
+ 0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
+ 0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
+ 0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
+ 0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
+ 0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
+ 0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
+ 0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
+ 0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
+ 0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
+ 0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
+ 0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
+ 0x33,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+ 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+ 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+ 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+ 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+ 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+ 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+ 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+ 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+ 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+ 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+ 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+ 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+ 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+ 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+ 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+ 0x30,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
+ 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
+ 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
+ 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
+ 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
+ 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
+ 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
+ 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
+ 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
+ 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
+ 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
+ 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
+ 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
+ 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
+ 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
+ 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
+ 0x31,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
+ 0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
+ 0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
+ 0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
+ 0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
+ 0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
+ 0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
+ 0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
+ 0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
+ 0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
+ 0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
+ 0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
+ 0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
+ 0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
+ 0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
+ 0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
+ 0x36,0x37,0x37,0x37,0x37,0x37,0x37,0x37,
+ 0x37,0x37,0x37,0x37,0x37,0x37,0x37,0x37,
+ 0x37,0x37,0x37,0x37,0x37,0x37,0x37,0x37,
+ 0x37,0x37,0x37,0x37,0x37,0x37,0x37,0x37,
+ 0x37,0x37,0x37,0x37,0x37,0x37,0x37,0x37,
+ 0x37,0x37,0x37,0x37,0x37,0x37,0x37,0x37,
+ 0x37,0x37,0x37,0x37,0x37,0x37,0x37,0x37,
+ 0x37,0x37,0x37,0x37,0x37,0x37,0x37,0x37,
+ 0x37,0x37,0x37,0x37,0x37,0x37,0x37,0x37,
+ 0x37,0x37,0x37,0x37,0x37,0x37,0x37,0x37,
+ 0x37,0x37,0x37,0x37,0x37,0x37,0x37,0x37,
+ 0x37,0x37,0x37,0x37,0x37,0x37,0x37,0x37,
+ 0x37,0x37,0x37,0x37,0x37,0x37,0x37,0x37,
+ 0x37,0x37,0x37,0x37,0x37,0x37,0x37,0x37,
+ 0x37,0x37,0x37,0x37,0x37,0x37,0x37,0x37,
+ 0x37,0x37,0x37,0x37,0x37,0x37,0x37,0x37,
+ 0x37,0x34,0x34,0x34,0x34,0x34,0x34,0x34,
+ 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,
+ 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,
+ 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,
+ 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,
+ 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,
+ 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,
+ 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,
+ 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,
+ 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,
+ 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,
+ 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,
+ 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,
+ 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,
+ 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,
+ 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,
+ 0x34,0x35,0x35,0x35,0x35,0x35,0x35,0x35,
+ 0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,
+ 0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,
+ 0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,
+ 0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,
+ 0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,
+ 0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,
+ 0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,
+ 0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,
+ 0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,
+ 0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,
+ 0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,
+ 0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,
+ 0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,
+ 0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,
+ 0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,
+ 0x35,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,
+ 0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,
+ 0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,
+ 0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,
+ 0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,
+ 0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,
+ 0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,
+ 0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,
+ 0x0A,0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,
+ 0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,
+ 0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,
+ 0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,
+ 0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,
+ 0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,
+ 0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,
+ 0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,
+ 0x0B,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
+ 0x08,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
+ 0x09,0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,
+ 0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,
+ 0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,
+ 0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,
+ 0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,
+ 0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,
+ 0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,
+ 0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,
+ 0x0E,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,
+ 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,
+ 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,
+ 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,
+ 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,
+ 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,
+ 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,
+ 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,
+ 0x0F,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,
+ 0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,
+ 0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,
+ 0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,
+ 0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,
+ 0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,
+ 0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,
+ 0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,
+ 0x0C,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,
+ 0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,
+ 0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,
+ 0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,
+ 0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,
+ 0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,
+ 0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,
+ 0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,
+ 0x0D,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
+ 0x02,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
+ 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+ 0x01,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
+ 0x06,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
+ 0x07,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,
+ 0x04,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+ 0x05,0x1A,0x1A,0x1A,0x1A,0x1A,0x1A,0x1A,
+ 0x1A,0x1A,0x1A,0x1A,0x1A,0x1A,0x1A,0x1A,
+ 0x1A,0x1A,0x1A,0x1A,0x1A,0x1A,0x1A,0x1A,
+ 0x1A,0x1A,0x1A,0x1A,0x1A,0x1A,0x1A,0x1A,
+ 0x1A,0x1B,0x1B,0x1B,0x1B,0x1B,0x1B,0x1B,
+ 0x1B,0x1B,0x1B,0x1B,0x1B,0x1B,0x1B,0x1B,
+ 0x1B,0x1B,0x1B,0x1B,0x1B,0x1B,0x1B,0x1B,
+ 0x1B,0x1B,0x1B,0x1B,0x1B,0x1B,0x1B,0x1B,
+ 0x1B,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
+ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
+ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
+ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
+ 0x18,0x19,0x19,0x19,0x19,0x19,0x19,0x19,
+ 0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,
+ 0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,
+ 0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,
+ 0x19,0x1E,0x1E,0x1E,0x1E,0x1E,0x1E,0x1E,
+ 0x1E,0x1E,0x1E,0x1E,0x1E,0x1E,0x1E,0x1E,
+ 0x1E,0x1E,0x1E,0x1E,0x1E,0x1E,0x1E,0x1E,
+ 0x1E,0x1E,0x1E,0x1E,0x1E,0x1E,0x1E,0x1E,
+ 0x1E,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,
+ 0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,
+ 0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,
+ 0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,
+ 0x1F,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,
+ 0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,
+ 0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,
+ 0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,
+ 0x1C,0x1D,0x1D,0x1D,0x1D,0x1D,0x1D,0x1D,
+ 0x1D,0x1D,0x1D,0x1D,0x1D,0x1D,0x1D,0x1D,
+ 0x1D,0x1D,0x1D,0x1D,0x1D,0x1D,0x1D,0x1D,
+ 0x1D,0x1D,0x1D,0x1D,0x1D,0x1D,0x1D,0x1D,
+ 0x1D,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
+ 0x12,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+ 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+ 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+ 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+ 0x13,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+ 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+ 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+ 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+ 0x10,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
+ 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
+ 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
+ 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
+ 0x11,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
+ 0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
+ 0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
+ 0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,
+ 0x16,0x17,0x17,0x17,0x17,0x17,0x17,0x17,
+ 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,
+ 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,
+ 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,
+ 0x17,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
+ 0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
+ 0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
+ 0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
+ 0x14,0x15,0x15,0x15,0x15,0x15,0x15,0x15,
+ 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,
+ 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,
+ 0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,
+ 0x15,0x6A,0x6A,0x6A,0x6A,0x6A,0x6A,0x6A,
+ 0x6A,0x6A,0x6A,0x6A,0x6A,0x6A,0x6A,0x6A,
+ 0x6A,0x6B,0x6B,0x6B,0x6B,0x6B,0x6B,0x6B,
+ 0x6B,0x6B,0x6B,0x6B,0x6B,0x6B,0x6B,0x6B,
+ 0x6B,0x68,0x68,0x68,0x68,0x68,0x68,0x68,
+ 0x68,0x68,0x68,0x68,0x68,0x68,0x68,0x68,
+ 0x68,0x69,0x69,0x69,0x69,0x69,0x69,0x69,
+ 0x69,0x69,0x69,0x69,0x69,0x69,0x69,0x69,
+ 0x69,0x6E,0x6E,0x6E,0x6E,0x6E,0x6E,0x6E,
+ 0x6E,0x6E,0x6E,0x6E,0x6E,0x6E,0x6E,0x6E,
+ 0x6E,0x6F,0x6F,0x6F,0x6F,0x6F,0x6F,0x6F,
+ 0x6F,0x6F,0x6F,0x6F,0x6F,0x6F,0x6F,0x6F,
+ 0x6F,0x6C,0x6C,0x6C,0x6C,0x6C,0x6C,0x6C,
+ 0x6C,0x6C,0x6C,0x6C,0x6C,0x6C,0x6C,0x6C,
+ 0x6C,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,
+ 0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,
+ 0x6D,0x62,0x62,0x62,0x62,0x62,0x62,0x62,
+ 0x62,0x62,0x62,0x62,0x62,0x62,0x62,0x62,
+ 0x62,0x63,0x63,0x63,0x63,0x63,0x63,0x63,
+ 0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,
+ 0x63,0x60,0x60,0x60,0x60,0x60,0x60,0x60,
+ 0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,
+ 0x60,0x61,0x61,0x61,0x61,0x61,0x61,0x61,
+ 0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,
+ 0x61,0x66,0x66,0x66,0x66,0x66,0x66,0x66,
+ 0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,
+ 0x66,0x67,0x67,0x67,0x67,0x67,0x67,0x67,
+ 0x67,0x67,0x67,0x67,0x67,0x67,0x67,0x67,
+ 0x67,0x64,0x64,0x64,0x64,0x64,0x64,0x64,
+ 0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,
+ 0x64,0x65,0x65,0x65,0x65,0x65,0x65,0x65,
+ 0x65,0x65,0x65,0x65,0x65,0x65,0x65,0x65,
+ 0x65,0x7A,0x7A,0x7A,0x7A,0x7A,0x7A,0x7A,
+ 0x7A,0x7B,0x7B,0x7B,0x7B,0x7B,0x7B,0x7B,
+ 0x7B,0x78,0x78,0x78,0x78,0x78,0x78,0x78,
+ 0x78,0x79,0x79,0x79,0x79,0x79,0x79,0x79,
+ 0x79,0x7E,0x7E,0x7E,0x7E,0x7E,0x7E,0x7E,
+ 0x7E,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
+ 0x7F,0x7C,0x7C,0x7C,0x7C,0x7C,0x7C,0x7C,
+ 0x7C,0x7D,0x7D,0x7D,0x7D,0x7D,0x7D,0x7D,
+ 0x7D,0x72,0x72,0x72,0x72,0x72,0x72,0x72,
+ 0x72,0x73,0x73,0x73,0x73,0x73,0x73,0x73,
+ 0x73,0x70,0x70,0x70,0x70,0x70,0x70,0x70,
+ 0x70,0x71,0x71,0x71,0x71,0x71,0x71,0x71,
+ 0x71,0x76,0x76,0x76,0x76,0x76,0x76,0x76,
+ 0x76,0x77,0x77,0x77,0x77,0x77,0x77,0x77,
+ 0x77,0x74,0x74,0x74,0x74,0x74,0x74,0x74,
+ 0x74,0x75,0x75,0x75,0x75,0x75,0x75,0x75,
+ 0x75,0x4A,0x4A,0x4A,0x4A,0x4B,0x4B,0x4B,
+ 0x4B,0x48,0x48,0x48,0x48,0x49,0x49,0x49,
+ 0x49,0x4E,0x4E,0x4E,0x4E,0x4F,0x4F,0x4F,
+ 0x4F,0x4C,0x4C,0x4C,0x4C,0x4D,0x4D,0x4D,
+ 0x4D,0x42,0x42,0x42,0x42,0x43,0x43,0x43,
+ 0x43,0x40,0x40,0x40,0x40,0x41,0x41,0x41,
+ 0x41,0x46,0x46,0x46,0x46,0x47,0x47,0x47,
+ 0x47,0x44,0x44,0x44,0x44,0x45,0x45,0x45,
+ 0x45,0x5A,0x5A,0x5A,0x5A,0x5B,0x5B,0x5B,
+ 0x5B,0x58,0x58,0x58,0x58,0x59,0x59,0x59,
+ 0x59,0x5E,0x5E,0x5E,0x5E,0x5F,0x5F,0x5F,
+ 0x5F,0x5C,0x5C,0x5C,0x5C,0x5D,0x5D,0x5D,
+ 0x5D,0x52,0x52,0x52,0x52,0x53,0x53,0x53,
+ 0x53,0x50,0x50,0x50,0x50,0x51,0x51,0x51,
+ 0x51,0x56,0x56,0x56,0x56,0x57,0x57,0x57,
+ 0x57,0x54,0x54,0x54,0x54,0x55,0x55,0x55
+};
+
+const pj_int16_t pjmedia_ulaw2linear_tab[256] =
+{
+ -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956,
+ -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764,
+ -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412,
+ -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316,
+ -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
+ -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
+ -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
+ -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
+ -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
+ -1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
+ -876, -844, -812, -780, -748, -716, -684, -652,
+ -620, -588, -556, -524, -492, -460, -428, -396,
+ -372, -356, -340, -324, -308, -292, -276, -260,
+ -244, -228, -212, -196, -180, -164, -148, -132,
+ -120, -112, -104, -96, -88, -80, -72, -64,
+ -56, -48, -40, -32, -24, -16, -8, 0,
+ 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
+ 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
+ 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
+ 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316,
+ 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140,
+ 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092,
+ 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004,
+ 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980,
+ 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436,
+ 1372, 1308, 1244, 1180, 1116, 1052, 988, 924,
+ 876, 844, 812, 780, 748, 716, 684, 652,
+ 620, 588, 556, 524, 492, 460, 428, 396,
+ 372, 356, 340, 324, 308, 292, 276, 260,
+ 244, 228, 212, 196, 180, 164, 148, 132,
+ 120, 112, 104, 96, 88, 80, 72, 64,
+ 56, 48, 40, 32, 24, 16, 8, 0
+};
+
+const pj_int16_t pjmedia_alaw2linear_tab[256] =
+{
+ -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736,
+ -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784,
+ -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368,
+ -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392,
+ -22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944,
+ -30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136,
+ -11008,-10496,-12032,-11520, -8960, -8448, -9984, -9472,
+ -15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568,
+ -344, -328, -376, -360, -280, -264, -312, -296,
+ -472, -456, -504, -488, -408, -392, -440, -424,
+ -88, -72, -120, -104, -24, -8, -56, -40,
+ -216, -200, -248, -232, -152, -136, -184, -168,
+ -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184,
+ -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696,
+ -688, -656, -752, -720, -560, -528, -624, -592,
+ -944, -912, -1008, -976, -816, -784, -880, -848,
+ 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736,
+ 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784,
+ 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368,
+ 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392,
+ 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944,
+ 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136,
+ 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472,
+ 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568,
+ 344, 328, 376, 360, 280, 264, 312, 296,
+ 472, 456, 504, 488, 408, 392, 440, 424,
+ 88, 72, 120, 104, 24, 8, 56, 40,
+ 216, 200, 248, 232, 152, 136, 184, 168,
+ 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184,
+ 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696,
+ 688, 656, 752, 720, 560, 528, 624, 592,
+ 944, 912, 1008, 976, 816, 784, 880, 848
+};
+
+#endif
+
diff --git a/pjmedia/src/pjmedia/avi_player.c b/pjmedia/src/pjmedia/avi_player.c
new file mode 100644
index 0000000..6cd4059
--- /dev/null
+++ b/pjmedia/src/pjmedia/avi_player.c
@@ -0,0 +1,794 @@
+/* $Id: avi_player.c 4057 2012-04-17 06:54:50Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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
+ */
+
+/**
+ * Default file player/writer buffer size.
+ */
+#include <pjmedia/avi_stream.h>
+#include <pjmedia/avi.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/wave.h>
+#include <pj/assert.h>
+#include <pj/file_access.h>
+#include <pj/file_io.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+
+#define THIS_FILE "avi_player.c"
+
+#define AVIF_MUSTUSEINDEX 0x00000020
+#define AVIF_ISINTERLEAVED 0x00000100
+#define AVISF_DISABLED 0x00000001
+#define AVISF_VIDEO_PALCHANGES 0x00010000
+
+#define AVI_EOF 0xFFEEFFEE
+
+#define COMPARE_TAG(doc_tag, tag) (doc_tag == *((pj_uint32_t *)avi_tags[tag]))
+
+#define SIGNATURE PJMEDIA_SIG_PORT_VID_AVI_PLAYER
+
+#define VIDEO_CLOCK_RATE 90000
+
+#if 0
+# define TRACE_(x) PJ_LOG(4,x)
+#else
+# define TRACE_(x)
+#endif
+
+#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0
+ static void data_to_host(void *data, pj_uint8_t bits, unsigned count)
+ {
+ unsigned i;
+
+ count /= (bits == 32? 4 : 2);
+
+ if (bits == 32) {
+ pj_int32_t *data32 = (pj_int32_t *)data;
+ for (i=0; i<count; ++i)
+ data32[i] = pj_swap32(data32[i]);
+ } else {
+ pj_int16_t *data16 = (pj_int16_t *)data;
+ for (i=0; i<count; ++i)
+ data16[i] = pj_swap16(data16[i]);
+ }
+
+ }
+ static void data_to_host2(void *data, pj_uint8_t nsizes,
+ pj_uint8_t *sizes)
+ {
+ unsigned i;
+ pj_int8_t *datap = (pj_int8_t *)data;
+ for (i = 0; i < nsizes; i++) {
+ data_to_host(datap, 32, sizes[i]);
+ datap += sizes[i++];
+ if (i >= nsizes)
+ break;
+ data_to_host(datap, 16, sizes[i]);
+ datap += sizes[i];
+ }
+ }
+#else
+# define data_to_host(data, bits, count)
+# define data_to_host2(data, nsizes, sizes)
+#endif
+
+typedef struct avi_fmt_info
+{
+ pjmedia_format_id fmt_id;
+ pjmedia_format_id eff_fmt_id;
+} avi_fmt_info;
+
+static avi_fmt_info avi_fmts[] =
+{
+ {PJMEDIA_FORMAT_MJPEG}, {PJMEDIA_FORMAT_H264},
+ {PJMEDIA_FORMAT_UYVY}, {PJMEDIA_FORMAT_YUY2},
+ {PJMEDIA_FORMAT_IYUV}, {PJMEDIA_FORMAT_I420},
+ {PJMEDIA_FORMAT_DIB}, {PJMEDIA_FORMAT_RGB24},
+ {PJMEDIA_FORMAT_RGB32},
+ {PJMEDIA_FORMAT_PACK('X','V','I','D'), PJMEDIA_FORMAT_MPEG4},
+ {PJMEDIA_FORMAT_PACK('x','v','i','d'), PJMEDIA_FORMAT_MPEG4},
+ {PJMEDIA_FORMAT_PACK('D','I','V','X'), PJMEDIA_FORMAT_MPEG4},
+ {PJMEDIA_FORMAT_PACK('F','M','P','4'), PJMEDIA_FORMAT_MPEG4},
+ {PJMEDIA_FORMAT_PACK('D','X','5','0'), PJMEDIA_FORMAT_MPEG4}
+};
+
+struct pjmedia_avi_streams
+{
+ unsigned num_streams;
+ pjmedia_port **streams;
+};
+
+struct avi_reader_port
+{
+ pjmedia_port base;
+ unsigned stream_id;
+ unsigned options;
+ pjmedia_format_id fmt_id;
+ unsigned usec_per_frame;
+ pj_uint16_t bits_per_sample;
+ pj_bool_t eof;
+ pj_off_t fsize;
+ pj_off_t start_data;
+ pj_uint8_t pad;
+ pj_oshandle_t fd;
+ pj_ssize_t size_left;
+ pj_timestamp next_ts;
+
+ pj_status_t (*cb)(pjmedia_port*, void*);
+};
+
+
+static pj_status_t avi_get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t avi_on_destroy(pjmedia_port *this_port);
+
+static struct avi_reader_port *create_avi_port(pj_pool_t *pool)
+{
+ const pj_str_t name = pj_str("file");
+ struct avi_reader_port *port;
+
+ port = PJ_POOL_ZALLOC_T(pool, struct avi_reader_port);
+ if (!port)
+ return NULL;
+
+ /* Put in default values.
+ * These will be overriden once the file is read.
+ */
+ pjmedia_port_info_init(&port->base.info, &name, SIGNATURE,
+ 8000, 1, 16, 80);
+
+ port->fd = (pj_oshandle_t)-1;
+ port->base.get_frame = &avi_get_frame;
+ port->base.on_destroy = &avi_on_destroy;
+
+ return port;
+}
+
+#define file_read(fd, data, size) file_read2(fd, data, size, 32)
+#define file_read2(fd, data, size, bits) file_read3(fd, data, size, bits, NULL)
+
+static pj_status_t file_read3(pj_oshandle_t fd, void *data, pj_ssize_t size,
+ pj_uint16_t bits, pj_ssize_t *psz_read)
+{
+ pj_ssize_t size_read = size, size_to_read = size;
+ pj_status_t status = pj_file_read(fd, data, &size_read);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Normalize AVI header fields values from little-endian to host
+ * byte order.
+ */
+ if (bits > 0)
+ data_to_host(data, bits, size_read);
+
+ if (size_read != size_to_read) {
+ if (psz_read)
+ *psz_read = size_read;
+ return AVI_EOF;
+ }
+
+ return status;
+}
+
+/*
+ * Create AVI player port.
+ */
+PJ_DEF(pj_status_t)
+pjmedia_avi_player_create_streams(pj_pool_t *pool,
+ const char *filename,
+ unsigned options,
+ pjmedia_avi_streams **p_streams)
+{
+ pjmedia_avi_hdr avi_hdr;
+ struct avi_reader_port *fport[PJMEDIA_AVI_MAX_NUM_STREAMS];
+ pj_off_t pos;
+ unsigned i, nstr = 0;
+ pj_status_t status = PJ_SUCCESS;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(pool && filename && p_streams, PJ_EINVAL);
+
+ /* Check the file really exists. */
+ if (!pj_file_exists(filename)) {
+ return PJ_ENOTFOUND;
+ }
+
+ /* Create fport instance. */
+ fport[0] = create_avi_port(pool);
+ if (!fport[0]) {
+ return PJ_ENOMEM;
+ }
+
+ /* Get the file size. */
+ fport[0]->fsize = pj_file_size(filename);
+
+ /* Size must be more than AVI header size */
+ if (fport[0]->fsize <= sizeof(riff_hdr_t) + sizeof(avih_hdr_t) +
+ sizeof(strl_hdr_t))
+ {
+ return PJMEDIA_EINVALIMEDIATYPE;
+ }
+
+ /* Open file. */
+ status = pj_file_open(pool, filename, PJ_O_RDONLY, &fport[0]->fd);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Read the RIFF + AVIH header. */
+ status = file_read(fport[0]->fd, &avi_hdr,
+ sizeof(riff_hdr_t) + sizeof(avih_hdr_t));
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Validate AVI file. */
+ if (!COMPARE_TAG(avi_hdr.riff_hdr.riff, PJMEDIA_AVI_RIFF_TAG) ||
+ !COMPARE_TAG(avi_hdr.riff_hdr.avi, PJMEDIA_AVI_AVI_TAG) ||
+ !COMPARE_TAG(avi_hdr.avih_hdr.list_tag, PJMEDIA_AVI_LIST_TAG) ||
+ !COMPARE_TAG(avi_hdr.avih_hdr.hdrl_tag, PJMEDIA_AVI_HDRL_TAG) ||
+ !COMPARE_TAG(avi_hdr.avih_hdr.avih, PJMEDIA_AVI_AVIH_TAG))
+ {
+ status = PJMEDIA_EINVALIMEDIATYPE;
+ goto on_error;
+ }
+
+ PJ_LOG(5, (THIS_FILE, "The AVI file has %d streams.",
+ avi_hdr.avih_hdr.num_streams));
+
+ /* Unsupported AVI format. */
+ if (avi_hdr.avih_hdr.num_streams > PJMEDIA_AVI_MAX_NUM_STREAMS) {
+ status = PJMEDIA_EAVIUNSUPP;
+ goto on_error;
+ }
+
+ /**
+ * TODO: Possibly unsupported AVI format.
+ * If you encounter this warning, verify whether the avi player
+ * is working properly.
+ */
+ if (avi_hdr.avih_hdr.flags & AVIF_MUSTUSEINDEX ||
+ avi_hdr.avih_hdr.pad > 1)
+ {
+ PJ_LOG(3, (THIS_FILE, "Warning!!! Possibly unsupported AVI format: "
+ "flags:%d, pad:%d", avi_hdr.avih_hdr.flags,
+ avi_hdr.avih_hdr.pad));
+ }
+
+ /* Read the headers of each stream. */
+ for (i = 0; i < avi_hdr.avih_hdr.num_streams; i++) {
+ pj_size_t elem = 0;
+ pj_ssize_t size_to_read;
+
+ /* Read strl header */
+ status = file_read(fport[0]->fd, &avi_hdr.strl_hdr[i],
+ sizeof(strl_hdr_t));
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ elem = COMPARE_TAG(avi_hdr.strl_hdr[i].data_type,
+ PJMEDIA_AVI_VIDS_TAG) ?
+ sizeof(strf_video_hdr_t) :
+ COMPARE_TAG(avi_hdr.strl_hdr[i].data_type,
+ PJMEDIA_AVI_AUDS_TAG) ?
+ sizeof(strf_audio_hdr_t) : 0;
+
+ /* Read strf header */
+ status = file_read2(fport[0]->fd, &avi_hdr.strf_hdr[i],
+ elem, 0);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Normalize the endian */
+ if (elem == sizeof(strf_video_hdr_t))
+ data_to_host2(&avi_hdr.strf_hdr[i],
+ sizeof(strf_video_hdr_sizes)/
+ sizeof(strf_video_hdr_sizes[0]),
+ strf_video_hdr_sizes);
+ else if (elem == sizeof(strf_audio_hdr_t))
+ data_to_host2(&avi_hdr.strf_hdr[i],
+ sizeof(strf_audio_hdr_sizes)/
+ sizeof(strf_audio_hdr_sizes[0]),
+ strf_audio_hdr_sizes);
+
+ /* Skip the remainder of the header */
+ size_to_read = avi_hdr.strl_hdr[i].list_sz - (sizeof(strl_hdr_t) -
+ 8) - elem;
+ status = pj_file_setpos(fport[0]->fd, size_to_read, PJ_SEEK_CUR);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+ }
+
+ /* Finish reading the AVIH header */
+ status = pj_file_setpos(fport[0]->fd, avi_hdr.avih_hdr.list_sz +
+ sizeof(riff_hdr_t) + 8, PJ_SEEK_SET);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ /* Skip any JUNK or LIST INFO until we get MOVI tag */
+ do {
+ pjmedia_avi_subchunk ch;
+ int read = 0;
+
+ status = file_read(fport[0]->fd, &ch, sizeof(pjmedia_avi_subchunk));
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ if (COMPARE_TAG(ch.id, PJMEDIA_AVI_LIST_TAG))
+ {
+ read = 4;
+ status = file_read(fport[0]->fd, &ch, read);
+ if (COMPARE_TAG(ch.id, PJMEDIA_AVI_MOVI_TAG))
+ break;
+ }
+
+ status = pj_file_setpos(fport[0]->fd, ch.len-read, PJ_SEEK_CUR);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+ } while(1);
+
+ status = pj_file_getpos(fport[0]->fd, &pos);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ for (i = 0, nstr = 0; i < avi_hdr.avih_hdr.num_streams; i++) {
+ pjmedia_format_id fmt_id;
+
+ /* Skip non-audio, non-video, or disabled streams) */
+ if ((!COMPARE_TAG(avi_hdr.strl_hdr[i].data_type,
+ PJMEDIA_AVI_VIDS_TAG) &&
+ !COMPARE_TAG(avi_hdr.strl_hdr[i].data_type,
+ PJMEDIA_AVI_AUDS_TAG)) ||
+ avi_hdr.strl_hdr[i].flags & AVISF_DISABLED)
+ {
+ continue;
+ }
+
+ if (COMPARE_TAG(avi_hdr.strl_hdr[i].data_type,
+ PJMEDIA_AVI_VIDS_TAG))
+ {
+ int j;
+
+ if (avi_hdr.strl_hdr[i].flags & AVISF_VIDEO_PALCHANGES) {
+ PJ_LOG(4, (THIS_FILE, "Unsupported video stream"));
+ continue;
+ }
+
+ fmt_id = avi_hdr.strl_hdr[i].codec;
+ for (j = sizeof(avi_fmts)/sizeof(avi_fmts[0])-1; j >= 0; j--) {
+ /* Check supported video formats here */
+ if (fmt_id == avi_fmts[j].fmt_id) {
+ if (avi_fmts[j].eff_fmt_id)
+ fmt_id = avi_fmts[j].eff_fmt_id;
+ break;
+ }
+ }
+
+ if (j < 0) {
+ PJ_LOG(4, (THIS_FILE, "Unsupported video stream"));
+ continue;
+ }
+ } else {
+ /* Check supported audio formats here */
+ if ((avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_PCM &&
+ avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_ALAW &&
+ avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_ULAW &&
+ avi_hdr.strl_hdr[i].codec != PJMEDIA_WAVE_FMT_TAG_PCM) ||
+ avi_hdr.strf_hdr[i].strf_audio_hdr.bits_per_sample != 16)
+ {
+ PJ_LOG(4, (THIS_FILE, "Unsupported audio stream"));
+ continue;
+ }
+ /* Normalize format ID */
+ fmt_id = avi_hdr.strl_hdr[i].codec;
+ if (avi_hdr.strl_hdr[i].codec == PJMEDIA_WAVE_FMT_TAG_PCM)
+ fmt_id = PJMEDIA_FORMAT_PCM;
+ }
+
+ if (nstr > 0) {
+ /* Create fport instance. */
+ fport[nstr] = create_avi_port(pool);
+ if (!fport[nstr]) {
+ status = PJ_ENOMEM;
+ goto on_error;
+ }
+
+ /* Open file. */
+ status = pj_file_open(pool, filename, PJ_O_RDONLY,
+ &fport[nstr]->fd);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Set the file position */
+ status = pj_file_setpos(fport[nstr]->fd, pos, PJ_SEEK_SET);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+ }
+
+ fport[nstr]->stream_id = i;
+ fport[nstr]->fmt_id = fmt_id;
+
+ nstr++;
+ }
+
+ if (nstr == 0) {
+ status = PJMEDIA_EAVIUNSUPP;
+ goto on_error;
+ }
+
+ for (i = 0; i < nstr; i++) {
+ strl_hdr_t *strl_hdr = &avi_hdr.strl_hdr[fport[i]->stream_id];
+
+ /* Initialize */
+ fport[i]->options = options;
+ fport[i]->fsize = fport[0]->fsize;
+ /* Current file position now points to start of data */
+ fport[i]->start_data = pos;
+
+ if (COMPARE_TAG(strl_hdr->data_type, PJMEDIA_AVI_VIDS_TAG)) {
+ strf_video_hdr_t *strf_hdr =
+ &avi_hdr.strf_hdr[fport[i]->stream_id].strf_video_hdr;
+ const pjmedia_video_format_info *vfi;
+
+ vfi = pjmedia_get_video_format_info(
+ pjmedia_video_format_mgr_instance(),
+ strl_hdr->codec);
+
+ fport[i]->bits_per_sample = (vfi ? vfi->bpp : 0);
+ fport[i]->usec_per_frame = avi_hdr.avih_hdr.usec_per_frame;
+ pjmedia_format_init_video(&fport[i]->base.info.fmt,
+ fport[i]->fmt_id,
+ strf_hdr->biWidth,
+ strf_hdr->biHeight,
+ strl_hdr->rate,
+ strl_hdr->scale);
+#if 0
+ /* The calculation below is wrong. strf_hdr->biSizeImage shows
+ * uncompressed size. Looks like we need to go the ugly way to
+ * get the bitrage:
+ * http://www.virtualdub.org/blog/pivot/entry.php?id=159
+ */
+ bps = strf_hdr->biSizeImage * 8 * strl_hdr->rate / strl_hdr->scale;
+ if (bps==0) {
+ /* strf_hdr->biSizeImage may be zero for uncompressed RGB */
+ bps = strf_hdr->biWidth * strf_hdr->biHeight *
+ strf_hdr->biBitCount *
+ strl_hdr->rate / strl_hdr->scale;
+ }
+ fport[i]->base.info.fmt.det.vid.avg_bps = bps;
+ fport[i]->base.info.fmt.det.vid.max_bps = bps;
+#endif
+ } else {
+ strf_audio_hdr_t *strf_hdr =
+ &avi_hdr.strf_hdr[fport[i]->stream_id].strf_audio_hdr;
+
+ fport[i]->bits_per_sample = strf_hdr->bits_per_sample;
+ fport[i]->usec_per_frame = avi_hdr.avih_hdr.usec_per_frame;
+ pjmedia_format_init_audio(&fport[i]->base.info.fmt,
+ fport[i]->fmt_id,
+ strf_hdr->sample_rate,
+ strf_hdr->nchannels,
+ strf_hdr->bits_per_sample,
+ 20000 /* fport[i]->usec_per_frame */,
+ strf_hdr->bytes_per_sec * 8,
+ strf_hdr->bytes_per_sec * 8);
+ }
+
+ pj_strdup2(pool, &fport[i]->base.info.name, filename);
+ }
+
+ /* Done. */
+ *p_streams = pj_pool_alloc(pool, sizeof(pjmedia_avi_streams));
+ (*p_streams)->num_streams = nstr;
+ (*p_streams)->streams = pj_pool_calloc(pool, (*p_streams)->num_streams,
+ sizeof(pjmedia_port *));
+ for (i = 0; i < nstr; i++)
+ (*p_streams)->streams[i] = &fport[i]->base;
+
+ PJ_LOG(4,(THIS_FILE,
+ "AVI file player '%.*s' created with "
+ "%d media ports",
+ (int)fport[0]->base.info.name.slen,
+ fport[0]->base.info.name.ptr,
+ (*p_streams)->num_streams));
+
+ return PJ_SUCCESS;
+
+on_error:
+ fport[0]->base.on_destroy(&fport[0]->base);
+ for (i = 1; i < nstr; i++)
+ fport[i]->base.on_destroy(&fport[i]->base);
+ if (status == AVI_EOF)
+ return PJMEDIA_EINVALIMEDIATYPE;
+ return status;
+}
+
+PJ_DEF(unsigned)
+pjmedia_avi_streams_get_num_streams(pjmedia_avi_streams *streams)
+{
+ pj_assert(streams);
+ return streams->num_streams;
+}
+
+PJ_DEF(pjmedia_avi_stream *)
+pjmedia_avi_streams_get_stream(pjmedia_avi_streams *streams,
+ unsigned idx)
+{
+ pj_assert(streams);
+ return (idx >=0 && idx < streams->num_streams ?
+ streams->streams[idx] : NULL);
+}
+
+PJ_DEF(pjmedia_avi_stream *)
+pjmedia_avi_streams_get_stream_by_media(pjmedia_avi_streams *streams,
+ unsigned start_idx,
+ pjmedia_type media_type)
+{
+ unsigned i;
+
+ pj_assert(streams);
+ for (i = start_idx; i < streams->num_streams; i++)
+ if (streams->streams[i]->info.fmt.type == media_type)
+ return streams->streams[i];
+ return NULL;
+}
+
+
+/*
+ * Get the data length, in bytes.
+ */
+PJ_DEF(pj_ssize_t) pjmedia_avi_stream_get_len(pjmedia_avi_stream *stream)
+{
+ struct avi_reader_port *fport;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(stream, -PJ_EINVAL);
+
+ /* Check that this is really a player port */
+ PJ_ASSERT_RETURN(stream->info.signature == SIGNATURE, -PJ_EINVALIDOP);
+
+ fport = (struct avi_reader_port*) stream;
+
+ return (pj_ssize_t)(fport->fsize - fport->start_data);
+}
+
+
+/*
+ * Register a callback to be called when the file reading has reached the
+ * end of file.
+ */
+PJ_DEF(pj_status_t)
+pjmedia_avi_stream_set_eof_cb( pjmedia_avi_stream *stream,
+ void *user_data,
+ pj_status_t (*cb)(pjmedia_avi_stream *stream,
+ void *usr_data))
+{
+ struct avi_reader_port *fport;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(stream, -PJ_EINVAL);
+
+ /* Check that this is really a player port */
+ PJ_ASSERT_RETURN(stream->info.signature == SIGNATURE, -PJ_EINVALIDOP);
+
+ fport = (struct avi_reader_port*) stream;
+
+ fport->base.port_data.pdata = user_data;
+ fport->cb = cb;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get frame from file.
+ */
+static pj_status_t avi_get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ struct avi_reader_port *fport = (struct avi_reader_port*)this_port;
+ pj_status_t status;
+ pj_ssize_t size_read = 0, size_to_read = 0;
+
+ pj_assert(fport->base.info.signature == SIGNATURE);
+
+ /* We encountered end of file */
+ if (fport->eof) {
+ pj_status_t status = PJ_SUCCESS;
+
+ 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->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_AVI_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_file_setpos(fport->fd, fport->start_data, PJ_SEEK_SET);
+ }
+
+ /* Fill frame buffer. */
+ size_to_read = frame->size;
+ do {
+ pjmedia_avi_subchunk ch = {0, 0};
+ char *cid;
+ unsigned stream_id;
+
+ /* We need to read data from the file past the chunk boundary */
+ if (fport->size_left > 0 && fport->size_left < size_to_read) {
+ status = file_read3(fport->fd, frame->buf, fport->size_left,
+ fport->bits_per_sample, &size_read);
+ if (status != PJ_SUCCESS)
+ goto on_error2;
+ size_to_read -= fport->size_left;
+ fport->size_left = 0;
+ }
+
+ /* Read new chunk data */
+ if (fport->size_left == 0) {
+ pj_off_t pos;
+ pj_file_getpos(fport->fd, &pos);
+
+ /* Data is padded to the nearest WORD boundary */
+ if (fport->pad) {
+ status = pj_file_setpos(fport->fd, fport->pad, PJ_SEEK_CUR);
+ fport->pad = 0;
+ }
+
+ status = file_read(fport->fd, &ch, sizeof(pjmedia_avi_subchunk));
+ if (status != PJ_SUCCESS) {
+ size_read = 0;
+ goto on_error2;
+ }
+
+ cid = (char *)&ch.id;
+ if (cid[0] >= '0' && cid[0] <= '9' &&
+ cid[1] >= '0' && cid[1] <= '9')
+ {
+ stream_id = (cid[0] - '0') * 10 + (cid[1] - '0');
+ } else
+ stream_id = 100;
+ fport->pad = (pj_uint8_t)ch.len & 1;
+
+ TRACE_((THIS_FILE, "Reading movi data at pos %u (%x), id: %.*s, "
+ "length: %u", (unsigned long)pos,
+ (unsigned long)pos, 4, cid, ch.len));
+
+ /* We are only interested in data with our stream id */
+ if (stream_id != fport->stream_id) {
+ if (COMPARE_TAG(ch.id, PJMEDIA_AVI_LIST_TAG))
+ PJ_LOG(5, (THIS_FILE, "Unsupported LIST tag found in "
+ "the movi data."));
+ else if (COMPARE_TAG(ch.id, PJMEDIA_AVI_RIFF_TAG)) {
+ PJ_LOG(3, (THIS_FILE, "Unsupported format: multiple "
+ "AVIs in a single file."));
+ status = AVI_EOF;
+ goto on_error2;
+ }
+
+ status = pj_file_setpos(fport->fd, ch.len,
+ PJ_SEEK_CUR);
+ continue;
+ }
+ fport->size_left = ch.len;
+ }
+
+ frame->type = (fport->base.info.fmt.type == PJMEDIA_TYPE_VIDEO ?
+ PJMEDIA_FRAME_TYPE_VIDEO : PJMEDIA_FRAME_TYPE_AUDIO);
+
+ if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+ if (size_to_read > fport->size_left)
+ size_to_read = fport->size_left;
+ status = file_read3(fport->fd, (char *)frame->buf + frame->size -
+ size_to_read, size_to_read,
+ fport->bits_per_sample, &size_read);
+ if (status != PJ_SUCCESS)
+ goto on_error2;
+ fport->size_left -= size_to_read;
+ } else {
+ pj_assert(frame->size >= ch.len);
+ status = file_read3(fport->fd, frame->buf, ch.len,
+ 0, &size_read);
+ if (status != PJ_SUCCESS)
+ goto on_error2;
+ frame->size = ch.len;
+ fport->size_left = 0;
+ }
+
+ break;
+
+ } while(1);
+
+ frame->timestamp.u64 = fport->next_ts.u64;
+ if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+ if (fport->usec_per_frame) {
+ fport->next_ts.u64 += (fport->usec_per_frame *
+ fport->base.info.fmt.det.aud.clock_rate /
+ 1000000);
+ } else {
+ fport->next_ts.u64 += (frame->size *
+ fport->base.info.fmt.det.aud.clock_rate /
+ (fport->base.info.fmt.det.aud.avg_bps / 8));
+ }
+ } else {
+ if (fport->usec_per_frame) {
+ fport->next_ts.u64 += (fport->usec_per_frame * VIDEO_CLOCK_RATE /
+ 1000000);
+ } else {
+ fport->next_ts.u64 += (frame->size * VIDEO_CLOCK_RATE /
+ (fport->base.info.fmt.det.vid.avg_bps / 8));
+ }
+ }
+
+ return PJ_SUCCESS;
+
+on_error2:
+ if (status == AVI_EOF) {
+ size_to_read -= size_read;
+ pj_bzero((char *)frame->buf + frame->size - size_to_read,
+ size_to_read);
+ fport->eof = PJ_TRUE;
+
+ return PJ_SUCCESS;
+ }
+
+ return status;
+}
+
+/*
+ * Destroy port.
+ */
+static pj_status_t avi_on_destroy(pjmedia_port *this_port)
+{
+ struct avi_reader_port *fport = (struct avi_reader_port*) this_port;
+
+ pj_assert(this_port->info.signature == SIGNATURE);
+
+ if (fport->fd != (pj_oshandle_t) -1)
+ pj_file_close(fport->fd);
+ return PJ_SUCCESS;
+}
+
+
+#endif /* PJMEDIA_HAS_VIDEO */
diff --git a/pjmedia/src/pjmedia/bidirectional.c b/pjmedia/src/pjmedia/bidirectional.c
new file mode 100644
index 0000000..1382858
--- /dev/null
+++ b/pjmedia/src/pjmedia/bidirectional.c
@@ -0,0 +1,78 @@
+/* $Id: bidirectional.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/bidirectional.h>
+#include <pj/pool.h>
+
+
+#define THIS_FILE "bidirectional.c"
+#define SIGNATURE PJMEDIA_SIG_PORT_BIDIR
+
+struct bidir_port
+{
+ pjmedia_port base;
+ pjmedia_port *get_port;
+ pjmedia_port *put_port;
+};
+
+
+static pj_status_t put_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ struct bidir_port *p = (struct bidir_port*)this_port;
+ return pjmedia_port_put_frame(p->put_port, frame);
+}
+
+
+static pj_status_t get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ struct bidir_port *p = (struct bidir_port*)this_port;
+ return pjmedia_port_get_frame(p->get_port, frame);
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_bidirectional_port_create( pj_pool_t *pool,
+ pjmedia_port *get_port,
+ pjmedia_port *put_port,
+ pjmedia_port **p_port )
+{
+ struct bidir_port *port;
+ const pjmedia_audio_format_detail *gafd;
+
+ port = PJ_POOL_ZALLOC_T(pool, struct bidir_port);
+ gafd = pjmedia_format_get_audio_format_detail(&get_port->info.fmt, 1);
+
+ pjmedia_port_info_init(&port->base.info, &get_port->info.name, SIGNATURE,
+ gafd->clock_rate,
+ gafd->channel_count,
+ gafd->bits_per_sample,
+ PJMEDIA_AFD_SPF(gafd));
+
+ port->get_port = get_port;
+ port->put_port = put_port;
+
+ port->base.get_frame = &get_frame;
+ port->base.put_frame = &put_frame;
+
+ *p_port = &port->base;
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjmedia/src/pjmedia/clock_thread.c b/pjmedia/src/pjmedia/clock_thread.c
new file mode 100644
index 0000000..45e3db6
--- /dev/null
+++ b/pjmedia/src/pjmedia/clock_thread.c
@@ -0,0 +1,426 @@
+/* $Id: clock_thread.c 4160 2012-06-07 04:10:22Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/clock.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/lock.h>
+#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 > PJ_UINT64(0x3FFFFFFFFFFFFF))
+ 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
+}
+
+
+/*
+ * Implementation of media clock with OS thread.
+ */
+
+struct pjmedia_clock
+{
+ pj_pool_t *pool;
+ pj_timestamp freq;
+ pj_timestamp interval;
+ pj_timestamp next_tick;
+ pj_timestamp timestamp;
+ unsigned timestamp_inc;
+ unsigned options;
+ pj_uint64_t max_jump;
+ pjmedia_clock_callback *cb;
+ void *user_data;
+ pj_thread_t *thread;
+ pj_bool_t running;
+ pj_bool_t quitting;
+ pj_lock_t *lock;
+};
+
+
+static int clock_thread(void *arg);
+
+#define MAX_JUMP_MSEC 500
+#define USEC_IN_SEC (pj_uint64_t)1000000
+
+/*
+ * Create media clock.
+ */
+PJ_DEF(pj_status_t) pjmedia_clock_create( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned options,
+ pjmedia_clock_callback *cb,
+ void *user_data,
+ pjmedia_clock **p_clock)
+{
+ pjmedia_clock_param param;
+
+ param.usec_interval = (unsigned)(samples_per_frame * USEC_IN_SEC /
+ channel_count / clock_rate);
+ param.clock_rate = clock_rate;
+ return pjmedia_clock_create2(pool, &param, options, cb,
+ user_data, p_clock);
+}
+
+PJ_DEF(pj_status_t) pjmedia_clock_create2(pj_pool_t *pool,
+ const pjmedia_clock_param *param,
+ unsigned options,
+ pjmedia_clock_callback *cb,
+ void *user_data,
+ pjmedia_clock **p_clock)
+{
+ pjmedia_clock *clock;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pool && param->usec_interval && param->clock_rate &&
+ p_clock, PJ_EINVAL);
+
+ clock = PJ_POOL_ALLOC_T(pool, pjmedia_clock);
+ clock->pool = pj_pool_create(pool->factory, "clock%p", 512, 512, NULL);
+
+ status = pj_get_timestamp_freq(&clock->freq);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ clock->interval.u64 = param->usec_interval * clock->freq.u64 /
+ USEC_IN_SEC;
+ clock->next_tick.u64 = 0;
+ clock->timestamp.u64 = 0;
+ clock->max_jump = MAX_JUMP_MSEC * clock->freq.u64 / 1000;
+ clock->timestamp_inc = (unsigned)(param->usec_interval *
+ param->clock_rate /
+ USEC_IN_SEC);
+ clock->options = options;
+ clock->cb = cb;
+ clock->user_data = user_data;
+ clock->thread = NULL;
+ clock->running = PJ_FALSE;
+ clock->quitting = PJ_FALSE;
+
+ /* I don't think we need a mutex, so we'll use null. */
+ status = pj_lock_create_null_mutex(pool, "clock", &clock->lock);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ *p_clock = clock;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Start the clock.
+ */
+PJ_DEF(pj_status_t) pjmedia_clock_start(pjmedia_clock *clock)
+{
+ pj_timestamp now;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(clock != NULL, PJ_EINVAL);
+
+ if (clock->running)
+ return PJ_SUCCESS;
+
+ status = pj_get_timestamp(&now);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ clock->next_tick.u64 = now.u64 + clock->interval.u64;
+ clock->running = PJ_TRUE;
+ clock->quitting = PJ_FALSE;
+
+ if ((clock->options & PJMEDIA_CLOCK_NO_ASYNC) == 0 && !clock->thread) {
+ status = pj_thread_create(clock->pool, "clock", &clock_thread, clock,
+ 0, 0, &clock->thread);
+ if (status != PJ_SUCCESS) {
+ clock->running = PJ_FALSE;
+ return status;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Stop the clock.
+ */
+PJ_DEF(pj_status_t) pjmedia_clock_stop(pjmedia_clock *clock)
+{
+ PJ_ASSERT_RETURN(clock != NULL, PJ_EINVAL);
+
+ clock->running = PJ_FALSE;
+ clock->quitting = PJ_TRUE;
+
+ if (clock->thread) {
+ if (pj_thread_join(clock->thread) == PJ_SUCCESS) {
+ pj_thread_destroy(clock->thread);
+ clock->thread = NULL;
+ pj_pool_reset(clock->pool);
+ } else {
+ clock->quitting = PJ_FALSE;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Update the clock.
+ */
+PJ_DEF(pj_status_t) pjmedia_clock_modify(pjmedia_clock *clock,
+ const pjmedia_clock_param *param)
+{
+ clock->interval.u64 = param->usec_interval * clock->freq.u64 /
+ USEC_IN_SEC;
+ clock->timestamp_inc = (unsigned)(param->usec_interval *
+ param->clock_rate /
+ USEC_IN_SEC);
+
+ return PJ_SUCCESS;
+}
+
+
+/* Calculate next tick */
+PJ_INLINE(void) clock_calc_next_tick(pjmedia_clock *clock,
+ pj_timestamp *now)
+{
+ if (clock->next_tick.u64+clock->max_jump < now->u64) {
+ /* Timestamp has made large jump, adjust next_tick */
+ clock->next_tick.u64 = now->u64;
+ }
+ clock->next_tick.u64 += clock->interval.u64;
+
+}
+
+/*
+ * Poll the clock.
+ */
+PJ_DEF(pj_bool_t) pjmedia_clock_wait( pjmedia_clock *clock,
+ pj_bool_t wait,
+ pj_timestamp *ts)
+{
+ pj_timestamp now;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(clock != NULL, PJ_FALSE);
+ PJ_ASSERT_RETURN((clock->options & PJMEDIA_CLOCK_NO_ASYNC) != 0,
+ PJ_FALSE);
+ PJ_ASSERT_RETURN(clock->running, PJ_FALSE);
+
+ status = pj_get_timestamp(&now);
+ if (status != PJ_SUCCESS)
+ return PJ_FALSE;
+
+ /* Wait for the next tick to happen */
+ if (now.u64 < clock->next_tick.u64) {
+ unsigned msec;
+
+ if (!wait)
+ return PJ_FALSE;
+
+ msec = pj_elapsed_msec(&now, &clock->next_tick);
+ pj_thread_sleep(msec);
+ }
+
+ /* Call callback, if any */
+ if (clock->cb)
+ (*clock->cb)(&clock->timestamp, clock->user_data);
+
+ /* Report timestamp to caller */
+ if (ts)
+ ts->u64 = clock->timestamp.u64;
+
+ /* Increment timestamp */
+ clock->timestamp.u64 += clock->timestamp_inc;
+
+ /* Calculate next tick */
+ clock_calc_next_tick(clock, &now);
+
+ /* Done */
+ return PJ_TRUE;
+}
+
+
+/*
+ * Clock thread
+ */
+static int clock_thread(void *arg)
+{
+ pj_timestamp now;
+ pjmedia_clock *clock = (pjmedia_clock*) arg;
+
+ /* Set thread priority to maximum unless not wanted. */
+ if ((clock->options & PJMEDIA_CLOCK_NO_HIGHEST_PRIO) == 0) {
+ int max = pj_thread_get_prio_max(pj_thread_this());
+ if (max > 0)
+ pj_thread_set_prio(pj_thread_this(), max);
+ }
+
+ /* Get the first tick */
+ pj_get_timestamp(&clock->next_tick);
+ clock->next_tick.u64 += clock->interval.u64;
+
+
+ while (!clock->quitting) {
+
+ pj_get_timestamp(&now);
+
+ /* Wait for the next tick to happen */
+ if (now.u64 < clock->next_tick.u64) {
+ unsigned msec;
+ msec = pj_elapsed_msec(&now, &clock->next_tick);
+ pj_thread_sleep(msec);
+ }
+
+ /* Skip if not running */
+ if (!clock->running) {
+ /* Calculate next tick */
+ clock_calc_next_tick(clock, &now);
+ continue;
+ }
+
+ pj_lock_acquire(clock->lock);
+
+ /* Call callback, if any */
+ if (clock->cb)
+ (*clock->cb)(&clock->timestamp, clock->user_data);
+
+ /* Best effort way to detect if we've been destroyed in the callback */
+ if (clock->quitting)
+ break;
+
+ /* Increment timestamp */
+ clock->timestamp.u64 += clock->timestamp_inc;
+
+ /* Calculate next tick */
+ clock_calc_next_tick(clock, &now);
+
+ pj_lock_release(clock->lock);
+ }
+
+ return 0;
+}
+
+
+/*
+ * Destroy the clock.
+ */
+PJ_DEF(pj_status_t) pjmedia_clock_destroy(pjmedia_clock *clock)
+{
+ PJ_ASSERT_RETURN(clock != NULL, PJ_EINVAL);
+
+ clock->running = PJ_FALSE;
+ clock->quitting = PJ_TRUE;
+
+ if (clock->thread) {
+ pj_thread_join(clock->thread);
+ pj_thread_destroy(clock->thread);
+ clock->thread = NULL;
+ }
+
+ if (clock->lock) {
+ pj_lock_destroy(clock->lock);
+ clock->lock = NULL;
+ }
+
+ if (clock->pool) {
+ pj_pool_t *pool = clock->pool;
+ clock->pool = NULL;
+ pj_pool_release(pool);
+ }
+ return PJ_SUCCESS;
+}
+
+
diff --git a/pjmedia/src/pjmedia/codec.c b/pjmedia/src/pjmedia/codec.c
new file mode 100644
index 0000000..db8cad2
--- /dev/null
+++ b/pjmedia/src/pjmedia/codec.c
@@ -0,0 +1,639 @@
+/* $Id: codec.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/codec.h>
+#include <pjmedia/errno.h>
+#include <pj/array.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/string.h>
+
+#define THIS_FILE "codec.c"
+
+
+
+/* Definition of default codecs parameters */
+struct pjmedia_codec_default_param
+{
+ pj_pool_t *pool;
+ pjmedia_codec_param *param;
+};
+
+
+/* Sort codecs in codec manager based on priorities */
+static void sort_codecs(pjmedia_codec_mgr *mgr);
+
+
+/*
+ * Initialize codec manager.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_mgr_init (pjmedia_codec_mgr *mgr,
+ pj_pool_factory *pf)
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(mgr && pf, PJ_EINVAL);
+
+ /* Init codec manager */
+ pj_bzero(mgr, sizeof(pjmedia_codec_mgr));
+ mgr->pf = pf;
+ pj_list_init (&mgr->factory_list);
+ mgr->codec_cnt = 0;
+
+ /* Create pool */
+ mgr->pool = pj_pool_create(mgr->pf, "codec-mgr", 256, 256, NULL);
+
+ /* Create mutex */
+ status = pj_mutex_create_recursive(mgr->pool, "codec-mgr", &mgr->mutex);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Initialize codec manager.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_mgr_destroy (pjmedia_codec_mgr *mgr)
+{
+ pjmedia_codec_factory *factory;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+ /* Destroy all factories in the list */
+ factory = mgr->factory_list.next;
+ while (factory != &mgr->factory_list) {
+ pjmedia_codec_factory *next = factory->next;
+ (*factory->op->destroy)();
+ factory = next;
+ }
+
+ /* Cleanup all pools of all codec default params */
+ for (i=0; i<mgr->codec_cnt; ++i) {
+ if (mgr->codec_desc[i].param) {
+ pj_assert(mgr->codec_desc[i].param->pool);
+ pj_pool_release(mgr->codec_desc[i].param->pool);
+ }
+ }
+
+ /* Destroy mutex */
+ if (mgr->mutex)
+ pj_mutex_destroy(mgr->mutex);
+
+ /* Release pool */
+ if (mgr->pool)
+ pj_pool_release(mgr->pool);
+
+ /* Just for safety, set codec manager states to zero */
+ pj_bzero(mgr, sizeof(pjmedia_codec_mgr));
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Register a codec factory.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_mgr_register_factory( pjmedia_codec_mgr *mgr,
+ pjmedia_codec_factory *factory)
+{
+ pjmedia_codec_info info[PJMEDIA_CODEC_MGR_MAX_CODECS];
+ unsigned i, count;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(mgr && factory, PJ_EINVAL);
+
+ /* Since 2.0 we require codec factory to implement "destroy" op. Please
+ * see: https://trac.pjsip.org/repos/ticket/1294
+ *
+ * Really! Please do see it.
+ */
+ PJ_ASSERT_RETURN(factory->op->destroy != NULL, PJ_ENOTSUP);
+
+ /* Enum codecs */
+ count = PJ_ARRAY_SIZE(info);
+ status = factory->op->enum_info(factory, &count, info);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_mutex_lock(mgr->mutex);
+
+ /* Check codec count */
+ if (count + mgr->codec_cnt > PJ_ARRAY_SIZE(mgr->codec_desc)) {
+ pj_mutex_unlock(mgr->mutex);
+ return PJ_ETOOMANY;
+ }
+
+
+ /* Save the codecs */
+ for (i=0; i<count; ++i) {
+ pj_memcpy( &mgr->codec_desc[mgr->codec_cnt+i],
+ &info[i], sizeof(pjmedia_codec_info));
+ mgr->codec_desc[mgr->codec_cnt+i].prio = PJMEDIA_CODEC_PRIO_NORMAL;
+ mgr->codec_desc[mgr->codec_cnt+i].factory = factory;
+ pjmedia_codec_info_to_id( &info[i],
+ mgr->codec_desc[mgr->codec_cnt+i].id,
+ sizeof(pjmedia_codec_id));
+ }
+
+ /* Update count */
+ mgr->codec_cnt += count;
+
+ /* Re-sort codec based on priorities */
+ sort_codecs(mgr);
+
+ /* Add factory to the list */
+ pj_list_push_back(&mgr->factory_list, factory);
+
+ pj_mutex_unlock(mgr->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Unregister a codec factory.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_mgr_unregister_factory(
+ pjmedia_codec_mgr *mgr,
+ pjmedia_codec_factory *factory)
+{
+ unsigned i;
+ PJ_ASSERT_RETURN(mgr && factory, PJ_EINVAL);
+
+ pj_mutex_lock(mgr->mutex);
+
+ /* Factory must be registered. */
+ if (pj_list_find_node(&mgr->factory_list, factory) != factory) {
+ pj_mutex_unlock(mgr->mutex);
+ return PJ_ENOTFOUND;
+ }
+
+ /* Erase factory from the factory list */
+ pj_list_erase(factory);
+
+
+ /* Remove all supported codecs from the codec manager that were created
+ * by the specified factory.
+ */
+ for (i=0; i<mgr->codec_cnt; ) {
+
+ if (mgr->codec_desc[i].factory == factory) {
+ /* Release pool of codec default param */
+ if (mgr->codec_desc[i].param) {
+ pj_assert(mgr->codec_desc[i].param->pool);
+ pj_pool_release(mgr->codec_desc[i].param->pool);
+ }
+
+ /* Remove the codec from array of codec descriptions */
+ pj_array_erase(mgr->codec_desc, sizeof(mgr->codec_desc[0]),
+ mgr->codec_cnt, i);
+ --mgr->codec_cnt;
+
+ } else {
+ ++i;
+ }
+ }
+
+ pj_mutex_unlock(mgr->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Enum all codecs.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_mgr_enum_codecs(pjmedia_codec_mgr *mgr,
+ unsigned *count,
+ pjmedia_codec_info codecs[],
+ unsigned *prio)
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(mgr && count && codecs, PJ_EINVAL);
+
+ pj_mutex_lock(mgr->mutex);
+
+ if (*count > mgr->codec_cnt)
+ *count = mgr->codec_cnt;
+
+ for (i=0; i<*count; ++i) {
+ pj_memcpy(&codecs[i],
+ &mgr->codec_desc[i].info,
+ sizeof(pjmedia_codec_info));
+ }
+
+ if (prio) {
+ for (i=0; i < *count; ++i)
+ prio[i] = mgr->codec_desc[i].prio;
+ }
+
+ pj_mutex_unlock(mgr->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get codec info for static payload type.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_mgr_get_codec_info( pjmedia_codec_mgr *mgr,
+ unsigned pt,
+ const pjmedia_codec_info **p_info)
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(mgr && p_info && pt>=0 && pt < 96, PJ_EINVAL);
+
+ pj_mutex_lock(mgr->mutex);
+
+ for (i=0; i<mgr->codec_cnt; ++i) {
+ if (mgr->codec_desc[i].info.pt == pt) {
+ *p_info = &mgr->codec_desc[i].info;
+
+ pj_mutex_unlock(mgr->mutex);
+ return PJ_SUCCESS;
+ }
+ }
+
+ pj_mutex_unlock(mgr->mutex);
+
+ return PJMEDIA_CODEC_EUNSUP;
+}
+
+
+/*
+ * Convert codec info struct into a unique codec identifier.
+ * A codec identifier looks something like "L16/44100/2".
+ */
+PJ_DEF(char*) pjmedia_codec_info_to_id( const pjmedia_codec_info *info,
+ char *id, unsigned max_len )
+{
+ int len;
+
+ PJ_ASSERT_RETURN(info && id && max_len, NULL);
+
+ len = pj_ansi_snprintf(id, max_len, "%.*s/%u/%u",
+ (int)info->encoding_name.slen,
+ info->encoding_name.ptr,
+ info->clock_rate,
+ info->channel_cnt);
+
+ if (len < 1 || len >= (int)max_len) {
+ id[0] = '\0';
+ return NULL;
+ }
+
+ return id;
+}
+
+
+/*
+ * Find codecs by the unique codec identifier. This function will find
+ * all codecs that match the codec identifier prefix. For example, if
+ * "L16" is specified, then it will find "L16/8000/1", "L16/16000/1",
+ * and so on, up to the maximum count specified in the argument.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_mgr_find_codecs_by_id( pjmedia_codec_mgr *mgr,
+ const pj_str_t *codec_id,
+ unsigned *count,
+ const pjmedia_codec_info *p_info[],
+ unsigned prio[])
+{
+ unsigned i, found = 0;
+
+ PJ_ASSERT_RETURN(mgr && codec_id && count && *count, PJ_EINVAL);
+
+ pj_mutex_lock(mgr->mutex);
+
+ for (i=0; i<mgr->codec_cnt; ++i) {
+
+ if (codec_id->slen == 0 ||
+ pj_strnicmp2(codec_id, mgr->codec_desc[i].id,
+ codec_id->slen) == 0)
+ {
+
+ if (p_info)
+ p_info[found] = &mgr->codec_desc[i].info;
+ if (prio)
+ prio[found] = mgr->codec_desc[i].prio;
+
+ ++found;
+
+ if (found >= *count)
+ break;
+ }
+
+ }
+
+ pj_mutex_unlock(mgr->mutex);
+
+ *count = found;
+
+ return found ? PJ_SUCCESS : PJ_ENOTFOUND;
+}
+
+
+/* Swap two codecs positions in codec manager */
+static void swap_codec(pjmedia_codec_mgr *mgr, unsigned i, unsigned j)
+{
+ struct pjmedia_codec_desc tmp;
+
+ pj_memcpy(&tmp, &mgr->codec_desc[i], sizeof(struct pjmedia_codec_desc));
+
+ pj_memcpy(&mgr->codec_desc[i], &mgr->codec_desc[j],
+ sizeof(struct pjmedia_codec_desc));
+
+ pj_memcpy(&mgr->codec_desc[j], &tmp, sizeof(struct pjmedia_codec_desc));
+}
+
+
+/* Sort codecs in codec manager based on priorities */
+static void sort_codecs(pjmedia_codec_mgr *mgr)
+{
+ unsigned i;
+
+ /* Re-sort */
+ for (i=0; i<mgr->codec_cnt; ++i) {
+ unsigned j, max;
+
+ for (max=i, j=i+1; j<mgr->codec_cnt; ++j) {
+ if (mgr->codec_desc[j].prio > mgr->codec_desc[max].prio)
+ max = j;
+ }
+
+ if (max != i)
+ swap_codec(mgr, i, max);
+ }
+
+ /* Change PJMEDIA_CODEC_PRIO_HIGHEST codecs to NEXT_HIGHER */
+ for (i=0; i<mgr->codec_cnt; ++i) {
+ if (mgr->codec_desc[i].prio == PJMEDIA_CODEC_PRIO_HIGHEST)
+ mgr->codec_desc[i].prio = PJMEDIA_CODEC_PRIO_NEXT_HIGHER;
+ else
+ break;
+ }
+}
+
+
+/**
+ * Set codec priority. The codec priority determines the order of
+ * the codec in the SDP created by the endpoint. If more than one codecs
+ * are found with the same codec_id prefix, then the function sets the
+ * priorities of all those codecs.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_mgr_set_codec_priority(
+ pjmedia_codec_mgr *mgr,
+ const pj_str_t *codec_id,
+ pj_uint8_t prio)
+{
+ unsigned i, found = 0;
+
+ PJ_ASSERT_RETURN(mgr && codec_id, PJ_EINVAL);
+
+ pj_mutex_lock(mgr->mutex);
+
+ /* Update the priorities of affected codecs */
+ for (i=0; i<mgr->codec_cnt; ++i)
+ {
+ if (codec_id->slen == 0 ||
+ pj_strnicmp2(codec_id, mgr->codec_desc[i].id,
+ codec_id->slen) == 0)
+ {
+ mgr->codec_desc[i].prio = (pjmedia_codec_priority) prio;
+ ++found;
+ }
+ }
+
+ if (!found) {
+ pj_mutex_unlock(mgr->mutex);
+ return PJ_ENOTFOUND;
+ }
+
+ /* Re-sort codecs */
+ sort_codecs(mgr);
+
+ pj_mutex_unlock(mgr->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Allocate one codec.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_mgr_alloc_codec(pjmedia_codec_mgr *mgr,
+ const pjmedia_codec_info *info,
+ pjmedia_codec **p_codec)
+{
+ pjmedia_codec_factory *factory;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(mgr && info && p_codec, PJ_EINVAL);
+
+ *p_codec = NULL;
+
+ pj_mutex_lock(mgr->mutex);
+
+ factory = mgr->factory_list.next;
+ while (factory != &mgr->factory_list) {
+
+ if ( (*factory->op->test_alloc)(factory, info) == PJ_SUCCESS ) {
+
+ status = (*factory->op->alloc_codec)(factory, info, p_codec);
+ if (status == PJ_SUCCESS) {
+ pj_mutex_unlock(mgr->mutex);
+ return PJ_SUCCESS;
+ }
+
+ }
+
+ factory = factory->next;
+ }
+
+ pj_mutex_unlock(mgr->mutex);
+
+ return PJMEDIA_CODEC_EUNSUP;
+}
+
+
+/*
+ * Get default codec parameter.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_mgr_get_default_param( pjmedia_codec_mgr *mgr,
+ const pjmedia_codec_info *info,
+ pjmedia_codec_param *param )
+{
+ pjmedia_codec_factory *factory;
+ pj_status_t status;
+ pjmedia_codec_id codec_id;
+ struct pjmedia_codec_desc *codec_desc = NULL;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(mgr && info && param, PJ_EINVAL);
+
+ if (!pjmedia_codec_info_to_id(info, (char*)&codec_id, sizeof(codec_id)))
+ return PJ_EINVAL;
+
+ pj_mutex_lock(mgr->mutex);
+
+ /* First, lookup default param in codec desc */
+ for (i=0; i < mgr->codec_cnt; ++i) {
+ if (pj_ansi_stricmp(codec_id, mgr->codec_desc[i].id) == 0) {
+ codec_desc = &mgr->codec_desc[i];
+ break;
+ }
+ }
+
+ /* If we found the codec and its default param is set, return it */
+ if (codec_desc && codec_desc->param) {
+ pj_assert(codec_desc->param->param);
+ pj_memcpy(param, codec_desc->param->param,
+ sizeof(pjmedia_codec_param));
+
+ pj_mutex_unlock(mgr->mutex);
+ return PJ_SUCCESS;
+ }
+
+ /* Otherwise query the default param from codec factory */
+ factory = mgr->factory_list.next;
+ while (factory != &mgr->factory_list) {
+
+ if ( (*factory->op->test_alloc)(factory, info) == PJ_SUCCESS ) {
+
+ status = (*factory->op->default_attr)(factory, info, param);
+ if (status == PJ_SUCCESS) {
+ /* Check for invalid max_bps. */
+ if (param->info.max_bps < param->info.avg_bps)
+ param->info.max_bps = param->info.avg_bps;
+
+ pj_mutex_unlock(mgr->mutex);
+ return PJ_SUCCESS;
+ }
+
+ }
+
+ factory = factory->next;
+ }
+
+ pj_mutex_unlock(mgr->mutex);
+
+
+ return PJMEDIA_CODEC_EUNSUP;
+}
+
+
+/*
+ * Set default codec parameter.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_mgr_set_default_param(
+ pjmedia_codec_mgr *mgr,
+ const pjmedia_codec_info *info,
+ const pjmedia_codec_param *param )
+{
+ unsigned i;
+ pjmedia_codec_id codec_id;
+ pj_pool_t *pool, *old_pool = NULL;
+ struct pjmedia_codec_desc *codec_desc = NULL;
+ pjmedia_codec_default_param *p;
+
+ PJ_ASSERT_RETURN(mgr && info, PJ_EINVAL);
+
+ if (!pjmedia_codec_info_to_id(info, (char*)&codec_id, sizeof(codec_id)))
+ return PJ_EINVAL;
+
+ pj_mutex_lock(mgr->mutex);
+
+ /* Lookup codec desc */
+ for (i=0; i < mgr->codec_cnt; ++i) {
+ if (pj_ansi_stricmp(codec_id, mgr->codec_desc[i].id) == 0) {
+ codec_desc = &mgr->codec_desc[i];
+ break;
+ }
+ }
+
+ /* Codec not found */
+ if (!codec_desc) {
+ pj_mutex_unlock(mgr->mutex);
+ return PJMEDIA_CODEC_EUNSUP;
+ }
+
+ /* If codec param is previously set, reset the codec param but release
+ * the codec param pool later after the new param is set (ticket #1171).
+ */
+ if (codec_desc->param) {
+ pj_assert(codec_desc->param->pool);
+ old_pool = codec_desc->param->pool;
+ codec_desc->param = NULL;
+ }
+
+ /* When param is set to NULL, i.e: setting default codec param to library
+ * default setting, just return PJ_SUCCESS.
+ */
+ if (NULL == param) {
+ pj_mutex_unlock(mgr->mutex);
+ if (old_pool)
+ pj_pool_release(old_pool);
+ return PJ_SUCCESS;
+ }
+
+ /* Instantiate and initialize codec param */
+ pool = pj_pool_create(mgr->pf, (char*)codec_id, 256, 256, NULL);
+ codec_desc->param = PJ_POOL_ZALLOC_T(pool, pjmedia_codec_default_param);
+ p = codec_desc->param;
+ p->pool = pool;
+ p->param = PJ_POOL_ZALLOC_T(pool, pjmedia_codec_param);
+
+ /* Update codec param */
+ pj_memcpy(p->param, param, sizeof(pjmedia_codec_param));
+ for (i = 0; i < param->setting.dec_fmtp.cnt; ++i) {
+ pj_strdup(pool, &p->param->setting.dec_fmtp.param[i].name,
+ &param->setting.dec_fmtp.param[i].name);
+ pj_strdup(pool, &p->param->setting.dec_fmtp.param[i].val,
+ &param->setting.dec_fmtp.param[i].val);
+ }
+ for (i = 0; i < param->setting.enc_fmtp.cnt; ++i) {
+ pj_strdup(pool, &p->param->setting.enc_fmtp.param[i].name,
+ &param->setting.enc_fmtp.param[i].name);
+ pj_strdup(pool, &p->param->setting.enc_fmtp.param[i].val,
+ &param->setting.enc_fmtp.param[i].val);
+ }
+
+ pj_mutex_unlock(mgr->mutex);
+
+ if (old_pool)
+ pj_pool_release(old_pool);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Dealloc codec.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_mgr_dealloc_codec(pjmedia_codec_mgr *mgr,
+ pjmedia_codec *codec)
+{
+ PJ_ASSERT_RETURN(mgr && codec, PJ_EINVAL);
+
+ return (*codec->factory->op->dealloc_codec)(codec->factory, codec);
+}
+
diff --git a/pjmedia/src/pjmedia/conf_switch.c b/pjmedia/src/pjmedia/conf_switch.c
new file mode 100644
index 0000000..9814f6c
--- /dev/null
+++ b/pjmedia/src/pjmedia/conf_switch.c
@@ -0,0 +1,1580 @@
+/* $Id: conf_switch.c 4122 2012-05-14 11:04:46Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/conference.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/port.h>
+#include <pjmedia/silencedet.h>
+#include <pjmedia/sound_port.h>
+#include <pj/array.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#if defined(PJMEDIA_CONF_USE_SWITCH_BOARD) && PJMEDIA_CONF_USE_SWITCH_BOARD!=0
+
+/* CONF_DEBUG enables detailed operation of the conference bridge.
+ * Beware that it prints large amounts of logs (several lines per frame).
+ */
+//#define CONF_DEBUG
+#ifdef CONF_DEBUG
+# include <stdio.h>
+# define TRACE_(x) PJ_LOG(5,x)
+#else
+# define TRACE_(x)
+#endif
+
+#define THIS_FILE "conf_switch.c"
+
+#define SIGNATURE PJMEDIA_CONF_SWITCH_SIGNATURE
+#define SIGNATURE_PORT PJMEDIA_PORT_SIGNATURE('S', 'W', 'T', 'P')
+#define NORMAL_LEVEL 128
+#define SLOT_TYPE unsigned
+#define INVALID_SLOT ((SLOT_TYPE)-1)
+#define BUFFER_SIZE PJMEDIA_MAX_MTU
+#define MAX_LEVEL (32767)
+#define MIN_LEVEL (-32768)
+
+/*
+ * DON'T GET CONFUSED WITH TX/RX!!
+ *
+ * TX and RX directions are always viewed from the conference bridge's point
+ * of view, and NOT from the port's point of view. So TX means the bridge
+ * is transmitting to the port, RX means the bridge is receiving from the
+ * port.
+ */
+
+
+/**
+ * This is a port connected to conference bridge.
+ */
+struct conf_port
+{
+ SLOT_TYPE slot; /**< Array of listeners. */
+ pj_str_t name; /**< Port name. */
+ pjmedia_port *port; /**< get_frame() and put_frame() */
+ pjmedia_port_op rx_setting; /**< Can we receive from this port */
+ pjmedia_port_op tx_setting; /**< Can we transmit to this port */
+ unsigned listener_cnt; /**< Number of listeners. */
+ SLOT_TYPE *listener_slots;/**< Array of listeners. */
+ unsigned transmitter_cnt;/**<Number of transmitters. */
+
+ /* Shortcut for port info. */
+ pjmedia_port_info *info;
+ unsigned samples_per_frame;
+
+ /* Calculated signal levels: */
+ unsigned tx_level; /**< Last tx level to this port. */
+ unsigned rx_level; /**< Last rx level from this port. */
+
+ /* The normalized signal level adjustment.
+ * A value of 128 (NORMAL_LEVEL) means there's no adjustment.
+ */
+ unsigned tx_adj_level; /**< Adjustment for TX. */
+ unsigned rx_adj_level; /**< Adjustment for RX. */
+
+ pj_timestamp ts_clock;
+ pj_timestamp ts_rx;
+ pj_timestamp ts_tx;
+
+ /* Tx buffer is a temporary buffer to be used when there's mismatch
+ * between port's ptime with conference's ptime. This buffer is used as
+ * the source to buffer the samples until there are enough samples to
+ * fulfill a complete frame to be transmitted to the port.
+ */
+ pj_uint8_t tx_buf[BUFFER_SIZE]; /**< Tx buffer. */
+};
+
+
+/*
+ * Conference bridge.
+ */
+struct pjmedia_conf
+{
+ unsigned options; /**< Bitmask options. */
+ unsigned max_ports; /**< Maximum ports. */
+ unsigned port_cnt; /**< Current number of ports. */
+ unsigned connect_cnt; /**< Total number of connections */
+ pjmedia_port *master_port; /**< Port zero's port. */
+ char master_name_buf[80]; /**< Port0 name buffer. */
+ pj_mutex_t *mutex; /**< Conference mutex. */
+ struct conf_port **ports; /**< Array of ports. */
+ pj_uint8_t buf[BUFFER_SIZE]; /**< Common buffer. */
+};
+
+
+/* Prototypes */
+static pj_status_t put_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t destroy_port(pjmedia_port *this_port);
+
+
+/*
+ * Create port.
+ */
+static pj_status_t create_conf_port( pj_pool_t *pool,
+ pjmedia_conf *conf,
+ pjmedia_port *port,
+ const pj_str_t *name,
+ struct conf_port **p_conf_port)
+{
+ struct conf_port *conf_port;
+ pjmedia_frame *f;
+
+ PJ_ASSERT_RETURN(pool && conf && port && name && p_conf_port, PJ_EINVAL);
+
+ /* Create port. */
+ conf_port = PJ_POOL_ZALLOC_T(pool, struct conf_port);
+
+ /* Set name */
+ pj_strdup_with_null(pool, &conf_port->name, name);
+
+ /* Default has tx and rx enabled. */
+ conf_port->rx_setting = PJMEDIA_PORT_ENABLE;
+ conf_port->tx_setting = PJMEDIA_PORT_ENABLE;
+
+ /* Default level adjustment is 128 (which means no adjustment) */
+ conf_port->tx_adj_level = NORMAL_LEVEL;
+ conf_port->rx_adj_level = NORMAL_LEVEL;
+
+ /* Create transmit flag array */
+ conf_port->listener_slots = (SLOT_TYPE*)
+ pj_pool_zalloc(pool,
+ conf->max_ports * sizeof(SLOT_TYPE));
+ PJ_ASSERT_RETURN(conf_port->listener_slots, PJ_ENOMEM);
+
+ /* Save some port's infos, for convenience. */
+ conf_port->port = port;
+ conf_port->info = &port->info;
+ conf_port->samples_per_frame = PJMEDIA_PIA_SPF(&port->info);
+
+ /* Init pjmedia_frame structure in the TX buffer. */
+ f = (pjmedia_frame*)conf_port->tx_buf;
+ f->buf = conf_port->tx_buf + sizeof(pjmedia_frame);
+
+ /* Done */
+ *p_conf_port = conf_port;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Create port zero for the sound device.
+ */
+static pj_status_t create_sound_port( pj_pool_t *pool,
+ pjmedia_conf *conf )
+{
+ struct conf_port *conf_port;
+ pj_str_t name = { "Master/sound", 12 };
+ pj_status_t status;
+
+ status = create_conf_port(pool, conf, conf->master_port, &name, &conf_port);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Add the port to the bridge */
+ conf_port->slot = 0;
+ conf->ports[0] = conf_port;
+ conf->port_cnt++;
+
+ PJ_LOG(5,(THIS_FILE, "Sound device successfully created for port 0"));
+ return PJ_SUCCESS;
+}
+
+/*
+ * Create conference bridge.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool,
+ unsigned max_ports,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ unsigned options,
+ pjmedia_conf **p_conf )
+{
+ pjmedia_conf *conf;
+ const pj_str_t name = { "Conf", 4 };
+ pj_status_t status;
+
+ /* Can only accept 16bits per sample, for now.. */
+ PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL);
+
+ PJ_LOG(5,(THIS_FILE, "Creating conference bridge with %d ports",
+ max_ports));
+
+ /* Create and init conf structure. */
+ conf = PJ_POOL_ZALLOC_T(pool, pjmedia_conf);
+ PJ_ASSERT_RETURN(conf, PJ_ENOMEM);
+
+ conf->ports = (struct conf_port**)
+ pj_pool_zalloc(pool, max_ports*sizeof(void*));
+ PJ_ASSERT_RETURN(conf->ports, PJ_ENOMEM);
+
+ conf->options = options;
+ conf->max_ports = max_ports;
+
+ /* Create and initialize the master port interface. */
+ conf->master_port = PJ_POOL_ZALLOC_T(pool, pjmedia_port);
+ PJ_ASSERT_RETURN(conf->master_port, PJ_ENOMEM);
+
+ pjmedia_port_info_init(&conf->master_port->info, &name, SIGNATURE,
+ clock_rate, channel_count, bits_per_sample,
+ samples_per_frame);
+
+ conf->master_port->port_data.pdata = conf;
+ conf->master_port->port_data.ldata = 0;
+
+ conf->master_port->get_frame = &get_frame;
+ conf->master_port->put_frame = &put_frame;
+ conf->master_port->on_destroy = &destroy_port;
+
+
+ /* Create port zero for sound device. */
+ status = create_sound_port(pool, conf);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Create mutex. */
+ status = pj_mutex_create_recursive(pool, "conf", &conf->mutex);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Done */
+
+ *p_conf = conf;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Pause sound device.
+ */
+static pj_status_t pause_sound( pjmedia_conf *conf )
+{
+ /* Do nothing. */
+ PJ_UNUSED_ARG(conf);
+ return PJ_SUCCESS;
+}
+
+/*
+ * Resume sound device.
+ */
+static pj_status_t resume_sound( pjmedia_conf *conf )
+{
+ /* Do nothing. */
+ PJ_UNUSED_ARG(conf);
+ return PJ_SUCCESS;
+}
+
+
+/**
+ * Destroy conference bridge.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf )
+{
+ PJ_ASSERT_RETURN(conf != NULL, PJ_EINVAL);
+
+ /* Destroy mutex */
+ pj_mutex_destroy(conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy the master port (will destroy the conference)
+ */
+static pj_status_t destroy_port(pjmedia_port *this_port)
+{
+ pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata;
+ return pjmedia_conf_destroy(conf);
+}
+
+/*
+ * Get port zero interface.
+ */
+PJ_DEF(pjmedia_port*) pjmedia_conf_get_master_port(pjmedia_conf *conf)
+{
+ /* Sanity check. */
+ PJ_ASSERT_RETURN(conf != NULL, NULL);
+
+ /* Can only return port interface when PJMEDIA_CONF_NO_DEVICE was
+ * present in the option.
+ */
+ PJ_ASSERT_RETURN((conf->options & PJMEDIA_CONF_NO_DEVICE) != 0, NULL);
+
+ return conf->master_port;
+}
+
+
+/*
+ * Set master port name.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_set_port0_name(pjmedia_conf *conf,
+ const pj_str_t *name)
+{
+ unsigned len;
+
+ /* Sanity check. */
+ PJ_ASSERT_RETURN(conf != NULL && name != NULL, PJ_EINVAL);
+
+ len = name->slen;
+ if (len > sizeof(conf->master_name_buf))
+ len = sizeof(conf->master_name_buf);
+
+ if (len > 0) pj_memcpy(conf->master_name_buf, name->ptr, len);
+
+ conf->ports[0]->name.ptr = conf->master_name_buf;
+ conf->ports[0]->name.slen = len;
+
+ conf->master_port->info.name = conf->ports[0]->name;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Add stream port to the conference bridge.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_add_port( pjmedia_conf *conf,
+ pj_pool_t *pool,
+ pjmedia_port *strm_port,
+ const pj_str_t *port_name,
+ unsigned *p_port )
+{
+ struct conf_port *conf_port;
+ unsigned index;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(conf && pool && strm_port, PJ_EINVAL);
+ /*
+ PJ_ASSERT_RETURN(conf->clock_rate == strm_port->info.clock_rate,
+ PJMEDIA_ENCCLOCKRATE);
+ PJ_ASSERT_RETURN(conf->channel_count == strm_port->info.channel_count,
+ PJMEDIA_ENCCHANNEL);
+ PJ_ASSERT_RETURN(conf->bits_per_sample == strm_port->info.bits_per_sample,
+ PJMEDIA_ENCBITS);
+ */
+
+ /* Port's samples per frame should be equal to or multiplication of
+ * conference's samples per frame.
+ */
+ /*
+ Not sure if this is needed!
+ PJ_ASSERT_RETURN((conf->samples_per_frame %
+ strm_port->info.samples_per_frame==0) ||
+ (strm_port->info.samples_per_frame %
+ conf->samples_per_frame==0),
+ PJMEDIA_ENCSAMPLESPFRAME);
+ */
+
+ /* If port_name is not specified, use the port's name */
+ if (!port_name)
+ port_name = &strm_port->info.name;
+
+ pj_mutex_lock(conf->mutex);
+
+ if (conf->port_cnt >= conf->max_ports) {
+ pj_assert(!"Too many ports");
+ pj_mutex_unlock(conf->mutex);
+ return PJ_ETOOMANY;
+ }
+
+ /* Find empty port in the conference bridge. */
+ for (index=0; index < conf->max_ports; ++index) {
+ if (conf->ports[index] == NULL)
+ break;
+ }
+
+ pj_assert(index != conf->max_ports);
+
+ /* Create conf port structure. */
+ status = create_conf_port(pool, conf, strm_port, port_name, &conf_port);
+ if (status != PJ_SUCCESS) {
+ pj_mutex_unlock(conf->mutex);
+ return status;
+ }
+
+ /* Put the port. */
+ conf_port->slot = index;
+ conf->ports[index] = conf_port;
+ conf->port_cnt++;
+
+ /* Done. */
+ if (p_port) {
+ *p_port = index;
+ }
+
+ pj_mutex_unlock(conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Add passive port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_add_passive_port( pjmedia_conf *conf,
+ pj_pool_t *pool,
+ const pj_str_t *name,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ unsigned options,
+ unsigned *p_slot,
+ pjmedia_port **p_port )
+{
+ PJ_UNUSED_ARG(conf);
+ PJ_UNUSED_ARG(pool);
+ PJ_UNUSED_ARG(name);
+ PJ_UNUSED_ARG(clock_rate);
+ PJ_UNUSED_ARG(channel_count);
+ PJ_UNUSED_ARG(samples_per_frame);
+ PJ_UNUSED_ARG(bits_per_sample);
+ PJ_UNUSED_ARG(options);
+ PJ_UNUSED_ARG(p_slot);
+ PJ_UNUSED_ARG(p_port);
+
+ return PJ_ENOTSUP;
+}
+
+
+
+/*
+ * Change TX and RX settings for the port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_configure_port( pjmedia_conf *conf,
+ unsigned slot,
+ pjmedia_port_op tx,
+ pjmedia_port_op rx)
+{
+ struct conf_port *conf_port;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);
+
+ pj_mutex_lock(conf->mutex);
+
+ /* Port must be valid. */
+ conf_port = conf->ports[slot];
+ if (conf_port == NULL) {
+ pj_mutex_unlock(conf->mutex);
+ return PJ_EINVAL;
+ }
+
+ if (tx != PJMEDIA_PORT_NO_CHANGE)
+ conf_port->tx_setting = tx;
+
+ if (rx != PJMEDIA_PORT_NO_CHANGE)
+ conf_port->rx_setting = rx;
+
+ pj_mutex_unlock(conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Connect port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_connect_port( pjmedia_conf *conf,
+ unsigned src_slot,
+ unsigned sink_slot,
+ int level )
+{
+ struct conf_port *src_port, *dst_port;
+ pj_bool_t start_sound = PJ_FALSE;
+ pjmedia_audio_format_detail *src_afd, *dst_afd;
+ unsigned i;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && src_slot<conf->max_ports &&
+ sink_slot<conf->max_ports, PJ_EINVAL);
+
+ /* For now, level MUST be zero. */
+ PJ_ASSERT_RETURN(level == 0, PJ_EINVAL);
+
+ pj_mutex_lock(conf->mutex);
+
+ /* Ports must be valid. */
+ src_port = conf->ports[src_slot];
+ dst_port = conf->ports[sink_slot];
+ if (!src_port || !dst_port) {
+ pj_mutex_unlock(conf->mutex);
+ return PJ_EINVAL;
+ }
+
+ src_afd = pjmedia_format_get_audio_format_detail(&src_port->info->fmt, 1);
+ dst_afd = pjmedia_format_get_audio_format_detail(&dst_port->info->fmt, 1);
+
+ /* Format must match. */
+ if (src_port->info->fmt.id != dst_port->info->fmt.id ||
+ src_afd->avg_bps != dst_afd->avg_bps)
+ {
+ pj_mutex_unlock(conf->mutex);
+ return PJMEDIA_ENOTCOMPATIBLE;
+ }
+
+ /* Clock rate must match. */
+ if (src_afd->clock_rate != dst_afd->clock_rate) {
+ pj_mutex_unlock(conf->mutex);
+ return PJMEDIA_ENCCLOCKRATE;
+ }
+
+ /* Channel count must match. */
+ if (src_afd->channel_count != dst_afd->channel_count) {
+ pj_mutex_unlock(conf->mutex);
+ return PJMEDIA_ENCCHANNEL;
+ }
+
+ /* Source and sink ptime must be equal or a multiplication factor. */
+ if ((src_afd->frame_time_usec % dst_afd->frame_time_usec != 0) &&
+ (dst_afd->frame_time_usec % src_afd->frame_time_usec != 0))
+ {
+ pj_mutex_unlock(conf->mutex);
+ return PJMEDIA_ENCSAMPLESPFRAME;
+ }
+
+ /* If sink is currently listening to other ports, it needs to be released
+ * first before the new connection made.
+ */
+ if (dst_port->transmitter_cnt > 0) {
+ unsigned j;
+ pj_bool_t transmitter_found = PJ_FALSE;
+
+ pj_assert(dst_port->transmitter_cnt == 1);
+ for (j=0; j<conf->max_ports && !transmitter_found; ++j) {
+ if (conf->ports[j]) {
+ unsigned k;
+
+ for (k=0; k < conf->ports[j]->listener_cnt; ++k) {
+ if (conf->ports[j]->listener_slots[k] == sink_slot) {
+ PJ_LOG(2,(THIS_FILE, "Connection [%d->%d] is "
+ "disconnected for new connection [%d->%d]",
+ j, sink_slot, src_slot, sink_slot));
+ pjmedia_conf_disconnect_port(conf, j, sink_slot);
+ transmitter_found = PJ_TRUE;
+ break;
+ }
+ }
+ }
+ }
+ pj_assert(dst_port->transmitter_cnt == 0);
+ }
+
+ /* Check if connection has been made */
+ for (i=0; i<src_port->listener_cnt; ++i) {
+ if (src_port->listener_slots[i] == sink_slot)
+ break;
+ }
+
+ /* Update master port info shortcut, note that application may update
+ * the master port info when the audio device needs to be reopened with
+ * a new format to match to ports connection format.
+ */
+ conf->ports[0]->samples_per_frame = PJMEDIA_PIA_SPF(conf->ports[0]->info);
+
+ if (i == src_port->listener_cnt) {
+ src_port->listener_slots[src_port->listener_cnt] = sink_slot;
+ ++conf->connect_cnt;
+ ++src_port->listener_cnt;
+ ++dst_port->transmitter_cnt;
+
+ if (conf->connect_cnt == 1)
+ start_sound = 1;
+
+ PJ_LOG(4,(THIS_FILE,"Port %d (%.*s) transmitting to port %d (%.*s)",
+ src_slot,
+ (int)src_port->name.slen,
+ src_port->name.ptr,
+ sink_slot,
+ (int)dst_port->name.slen,
+ dst_port->name.ptr));
+ }
+
+ pj_mutex_unlock(conf->mutex);
+
+ /* Sound device must be started without mutex, otherwise the
+ * sound thread will deadlock (?)
+ */
+ if (start_sound)
+ resume_sound(conf);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Disconnect port
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_disconnect_port( pjmedia_conf *conf,
+ unsigned src_slot,
+ unsigned sink_slot )
+{
+ struct conf_port *src_port, *dst_port;
+ unsigned i;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && src_slot<conf->max_ports &&
+ sink_slot<conf->max_ports, PJ_EINVAL);
+
+ pj_mutex_lock(conf->mutex);
+
+ /* Ports must be valid. */
+ src_port = conf->ports[src_slot];
+ dst_port = conf->ports[sink_slot];
+ if (!src_port || !dst_port) {
+ pj_mutex_unlock(conf->mutex);
+ return PJ_EINVAL;
+ }
+
+ /* Check if connection has been made */
+ for (i=0; i<src_port->listener_cnt; ++i) {
+ if (src_port->listener_slots[i] == sink_slot)
+ break;
+ }
+
+ if (i != src_port->listener_cnt) {
+ pjmedia_frame_ext *f;
+
+ pj_assert(src_port->listener_cnt > 0 &&
+ src_port->listener_cnt < conf->max_ports);
+ pj_assert(dst_port->transmitter_cnt > 0 &&
+ dst_port->transmitter_cnt < conf->max_ports);
+ pj_array_erase(src_port->listener_slots, sizeof(SLOT_TYPE),
+ src_port->listener_cnt, i);
+ --conf->connect_cnt;
+ --src_port->listener_cnt;
+ --dst_port->transmitter_cnt;
+
+ /* Cleanup listener TX buffer. */
+ f = (pjmedia_frame_ext*)dst_port->tx_buf;
+ f->base.type = PJMEDIA_FRAME_TYPE_NONE;
+ f->base.size = 0;
+ f->samples_cnt = 0;
+ f->subframe_cnt = 0;
+
+ PJ_LOG(4,(THIS_FILE,
+ "Port %d (%.*s) stop transmitting to port %d (%.*s)",
+ src_slot,
+ (int)src_port->name.slen,
+ src_port->name.ptr,
+ sink_slot,
+ (int)dst_port->name.slen,
+ dst_port->name.ptr));
+ }
+
+ pj_mutex_unlock(conf->mutex);
+
+ if (conf->connect_cnt == 0) {
+ pause_sound(conf);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get number of ports currently registered to the conference bridge.
+ */
+PJ_DEF(unsigned) pjmedia_conf_get_port_count(pjmedia_conf *conf)
+{
+ return conf->port_cnt;
+}
+
+/*
+ * Get total number of ports connections currently set up in the bridge.
+ */
+PJ_DEF(unsigned) pjmedia_conf_get_connect_count(pjmedia_conf *conf)
+{
+ return conf->connect_cnt;
+}
+
+
+/*
+ * Remove the specified port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_remove_port( pjmedia_conf *conf,
+ unsigned port )
+{
+ struct conf_port *conf_port;
+ unsigned i;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && port < conf->max_ports, PJ_EINVAL);
+
+ /* Suspend the sound devices.
+ * Don't want to remove port while port is being accessed by sound
+ * device's threads!
+ */
+
+ pj_mutex_lock(conf->mutex);
+
+ /* Port must be valid. */
+ conf_port = conf->ports[port];
+ if (conf_port == NULL) {
+ pj_mutex_unlock(conf->mutex);
+ return PJ_EINVAL;
+ }
+
+ conf_port->tx_setting = PJMEDIA_PORT_DISABLE;
+ conf_port->rx_setting = PJMEDIA_PORT_DISABLE;
+
+ /* Remove this port from transmit array of other ports. */
+ for (i=0; i<conf->max_ports; ++i) {
+ unsigned j;
+ struct conf_port *src_port;
+
+ src_port = conf->ports[i];
+
+ if (!src_port)
+ continue;
+
+ if (src_port->listener_cnt == 0)
+ continue;
+
+ for (j=0; j<src_port->listener_cnt; ++j) {
+ if (src_port->listener_slots[j] == port) {
+ pj_array_erase(src_port->listener_slots, sizeof(SLOT_TYPE),
+ src_port->listener_cnt, j);
+ pj_assert(conf->connect_cnt > 0);
+ --conf->connect_cnt;
+ --src_port->listener_cnt;
+ break;
+ }
+ }
+ }
+
+ /* Update transmitter_cnt of ports we're transmitting to */
+ while (conf_port->listener_cnt) {
+ unsigned dst_slot;
+ struct conf_port *dst_port;
+ pjmedia_frame_ext *f;
+
+ dst_slot = conf_port->listener_slots[conf_port->listener_cnt-1];
+ dst_port = conf->ports[dst_slot];
+ --dst_port->transmitter_cnt;
+ --conf_port->listener_cnt;
+ pj_assert(conf->connect_cnt > 0);
+ --conf->connect_cnt;
+
+ /* Cleanup & reinit listener TX buffer. */
+ f = (pjmedia_frame_ext*)dst_port->tx_buf;
+ f->base.type = PJMEDIA_FRAME_TYPE_NONE;
+ f->base.size = 0;
+ f->samples_cnt = 0;
+ f->subframe_cnt = 0;
+ }
+
+ /* Remove the port. */
+ conf->ports[port] = NULL;
+ --conf->port_cnt;
+
+ pj_mutex_unlock(conf->mutex);
+
+
+ /* Stop sound if there's no connection. */
+ if (conf->connect_cnt == 0) {
+ pause_sound(conf);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Enum ports.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_enum_ports( pjmedia_conf *conf,
+ unsigned ports[],
+ unsigned *p_count )
+{
+ unsigned i, count=0;
+
+ PJ_ASSERT_RETURN(conf && p_count && ports, PJ_EINVAL);
+
+ /* Lock mutex */
+ pj_mutex_lock(conf->mutex);
+
+ for (i=0; i<conf->max_ports && count<*p_count; ++i) {
+ if (!conf->ports[i])
+ continue;
+
+ ports[count++] = i;
+ }
+
+ /* Unlock mutex */
+ pj_mutex_unlock(conf->mutex);
+
+ *p_count = count;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get port info
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_get_port_info( pjmedia_conf *conf,
+ unsigned slot,
+ pjmedia_conf_port_info *info)
+{
+ struct conf_port *conf_port;
+ const pjmedia_audio_format_detail *afd;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);
+
+ /* Lock mutex */
+ pj_mutex_lock(conf->mutex);
+
+ /* Port must be valid. */
+ conf_port = conf->ports[slot];
+ if (conf_port == NULL) {
+ pj_mutex_unlock(conf->mutex);
+ return PJ_EINVAL;
+ }
+
+ afd = pjmedia_format_get_audio_format_detail(&conf_port->info->fmt, 1);
+
+ pj_bzero(info, sizeof(pjmedia_conf_port_info));
+
+ info->slot = slot;
+ info->name = conf_port->name;
+ info->tx_setting = conf_port->tx_setting;
+ info->rx_setting = conf_port->rx_setting;
+ info->listener_cnt = conf_port->listener_cnt;
+ info->listener_slots = conf_port->listener_slots;
+ info->transmitter_cnt = conf_port->transmitter_cnt;
+ info->clock_rate = afd->clock_rate;
+ info->channel_count = afd->channel_count;
+ info->samples_per_frame = conf_port->samples_per_frame;
+ info->bits_per_sample = afd->bits_per_sample;
+ info->format = conf_port->port->info.fmt;
+ info->tx_adj_level = conf_port->tx_adj_level - NORMAL_LEVEL;
+ info->rx_adj_level = conf_port->rx_adj_level - NORMAL_LEVEL;
+
+ /* Unlock mutex */
+ pj_mutex_unlock(conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_conf_get_ports_info(pjmedia_conf *conf,
+ unsigned *size,
+ pjmedia_conf_port_info info[])
+{
+ unsigned i, count=0;
+
+ PJ_ASSERT_RETURN(conf && size && info, PJ_EINVAL);
+
+ /* Lock mutex */
+ pj_mutex_lock(conf->mutex);
+
+ for (i=0; i<conf->max_ports && count<*size; ++i) {
+ if (!conf->ports[i])
+ continue;
+
+ pjmedia_conf_get_port_info(conf, i, &info[count]);
+ ++count;
+ }
+
+ /* Unlock mutex */
+ pj_mutex_unlock(conf->mutex);
+
+ *size = count;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get signal level.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_get_signal_level( pjmedia_conf *conf,
+ unsigned slot,
+ unsigned *tx_level,
+ unsigned *rx_level)
+{
+ struct conf_port *conf_port;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);
+
+ /* Lock mutex */
+ pj_mutex_lock(conf->mutex);
+
+ /* Port must be valid. */
+ conf_port = conf->ports[slot];
+ if (conf_port == NULL) {
+ pj_mutex_unlock(conf->mutex);
+ return PJ_EINVAL;
+ }
+
+ if (tx_level != NULL) {
+ *tx_level = conf_port->tx_level;
+ }
+
+ if (rx_level != NULL)
+ *rx_level = conf_port->rx_level;
+
+ /* Unlock mutex */
+ pj_mutex_unlock(conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Adjust RX level of individual port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_adjust_rx_level( pjmedia_conf *conf,
+ unsigned slot,
+ int adj_level )
+{
+ struct conf_port *conf_port;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);
+
+ /* Value must be from -128 to +127 */
+ /* Disabled, you can put more than +127, at your own risk:
+ PJ_ASSERT_RETURN(adj_level >= -128 && adj_level <= 127, PJ_EINVAL);
+ */
+ PJ_ASSERT_RETURN(adj_level >= -128, PJ_EINVAL);
+
+ /* Lock mutex */
+ pj_mutex_lock(conf->mutex);
+
+ /* Port must be valid. */
+ conf_port = conf->ports[slot];
+ if (conf_port == NULL) {
+ pj_mutex_unlock(conf->mutex);
+ return PJ_EINVAL;
+ }
+
+ /* Level adjustment is applicable only for ports that work with raw PCM. */
+ PJ_ASSERT_RETURN(conf_port->info->fmt.id == PJMEDIA_FORMAT_L16,
+ PJ_EIGNORED);
+
+ /* Set normalized adjustment level. */
+ conf_port->rx_adj_level = adj_level + NORMAL_LEVEL;
+
+ /* Unlock mutex */
+ pj_mutex_unlock(conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Adjust TX level of individual port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_adjust_tx_level( pjmedia_conf *conf,
+ unsigned slot,
+ int adj_level )
+{
+ struct conf_port *conf_port;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);
+
+ /* Value must be from -128 to +127 */
+ /* Disabled, you can put more than +127,, at your own risk:
+ PJ_ASSERT_RETURN(adj_level >= -128 && adj_level <= 127, PJ_EINVAL);
+ */
+ PJ_ASSERT_RETURN(adj_level >= -128, PJ_EINVAL);
+
+ /* Lock mutex */
+ pj_mutex_lock(conf->mutex);
+
+ /* Port must be valid. */
+ conf_port = conf->ports[slot];
+ if (conf_port == NULL) {
+ pj_mutex_unlock(conf->mutex);
+ return PJ_EINVAL;
+ }
+
+ /* Level adjustment is applicable only for ports that work with raw PCM. */
+ PJ_ASSERT_RETURN(conf_port->info->fmt.id == PJMEDIA_FORMAT_L16,
+ PJ_EIGNORED);
+
+ /* Set normalized adjustment level. */
+ conf_port->tx_adj_level = adj_level + NORMAL_LEVEL;
+
+ /* Unlock mutex */
+ pj_mutex_unlock(conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+/* Deliver frm_src to a listener port, eventually call port's put_frame()
+ * when samples count in the frm_dst are equal to port's samples_per_frame.
+ */
+static pj_status_t write_frame(struct conf_port *cport_dst,
+ const pjmedia_frame *frm_src)
+{
+ pjmedia_frame *frm_dst = (pjmedia_frame*)cport_dst->tx_buf;
+
+ PJ_TODO(MAKE_SURE_DEST_FRAME_HAS_ENOUGH_SPACE);
+
+ frm_dst->type = frm_src->type;
+ frm_dst->timestamp = cport_dst->ts_tx;
+
+ if (frm_src->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+
+ pjmedia_frame_ext *f_src = (pjmedia_frame_ext*)frm_src;
+ pjmedia_frame_ext *f_dst = (pjmedia_frame_ext*)frm_dst;
+ unsigned i;
+
+ for (i = 0; i < f_src->subframe_cnt; ++i) {
+ pjmedia_frame_ext_subframe *sf;
+
+ /* Copy frame to listener's TX buffer. */
+ sf = pjmedia_frame_ext_get_subframe(f_src, i);
+ pjmedia_frame_ext_append_subframe(f_dst, sf->data, sf->bitlen,
+ f_src->samples_cnt /
+ f_src->subframe_cnt);
+
+ /* Check if it's time to deliver the TX buffer to listener,
+ * i.e: samples count in TX buffer equal to listener's
+ * samples per frame.
+ */
+ if (f_dst->samples_cnt >= cport_dst->samples_per_frame)
+ {
+ if (cport_dst->slot) {
+ pjmedia_port_put_frame(cport_dst->port,
+ (pjmedia_frame*)f_dst);
+
+ /* Reset TX buffer. */
+ f_dst->subframe_cnt = 0;
+ f_dst->samples_cnt = 0;
+ }
+
+ /* Update TX timestamp. */
+ pj_add_timestamp32(&cport_dst->ts_tx,
+ cport_dst->samples_per_frame);
+ }
+ }
+
+ } else if (frm_src->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+
+ pj_int16_t *f_start, *f_end;
+
+ f_start = (pj_int16_t*)frm_src->buf;
+ f_end = f_start + (frm_src->size >> 1);
+
+ while (f_start < f_end) {
+ unsigned nsamples_to_copy, nsamples_req;
+
+ /* Copy frame to listener's TX buffer.
+ * Note that if the destination is port 0, just copy the whole
+ * available samples.
+ */
+ nsamples_to_copy = f_end - f_start;
+ nsamples_req = cport_dst->samples_per_frame -
+ (frm_dst->size>>1);
+ if (cport_dst->slot && nsamples_to_copy > nsamples_req)
+ nsamples_to_copy = nsamples_req;
+
+ /* Adjust TX level. */
+ if (cport_dst->tx_adj_level != NORMAL_LEVEL) {
+ pj_int16_t *p, *p_end;
+
+ p = f_start;
+ p_end = p + nsamples_to_copy;
+ while (p < p_end) {
+ pj_int32_t itemp = *p;
+
+ /* Adjust the level */
+ itemp = (itemp * cport_dst->tx_adj_level) >> 7;
+
+ /* Clip the signal if it's too loud */
+ if (itemp > MAX_LEVEL) itemp = MAX_LEVEL;
+ else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL;
+
+ /* Put back in the buffer. */
+ *p = (pj_int16_t)itemp;
+ ++p;
+ }
+ }
+
+ pjmedia_copy_samples((pj_int16_t*)frm_dst->buf + (frm_dst->size>>1),
+ f_start,
+ nsamples_to_copy);
+ frm_dst->size += nsamples_to_copy << 1;
+ f_start += nsamples_to_copy;
+
+ /* Check if it's time to deliver the TX buffer to listener,
+ * i.e: samples count in TX buffer equal to listener's
+ * samples per frame. Note that for destination port 0 this
+ * function will just populate all samples in the TX buffer.
+ */
+ if (cport_dst->slot == 0) {
+ /* Update TX timestamp. */
+ pj_add_timestamp32(&cport_dst->ts_tx, nsamples_to_copy);
+ } else if ((frm_dst->size >> 1) ==
+ cport_dst->samples_per_frame)
+ {
+ pjmedia_port_put_frame(cport_dst->port, frm_dst);
+
+ /* Reset TX buffer. */
+ frm_dst->size = 0;
+
+ /* Update TX timestamp. */
+ pj_add_timestamp32(&cport_dst->ts_tx,
+ cport_dst->samples_per_frame);
+ }
+ }
+
+ } else if (frm_src->type == PJMEDIA_FRAME_TYPE_NONE) {
+
+ /* Check port format. */
+ if (cport_dst->port &&
+ cport_dst->port->info.fmt.id == PJMEDIA_FORMAT_L16)
+ {
+ /* When there is already some samples in listener's TX buffer,
+ * pad the buffer with "zero samples".
+ */
+ if (frm_dst->size != 0) {
+ pjmedia_zero_samples((pj_int16_t*)frm_dst->buf,
+ cport_dst->samples_per_frame -
+ (frm_dst->size>>1));
+
+ frm_dst->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frm_dst->size = cport_dst->samples_per_frame << 1;
+ if (cport_dst->slot) {
+ pjmedia_port_put_frame(cport_dst->port, frm_dst);
+
+ /* Reset TX buffer. */
+ frm_dst->size = 0;
+ }
+
+ /* Update TX timestamp. */
+ pj_add_timestamp32(&cport_dst->ts_tx,
+ cport_dst->samples_per_frame);
+ }
+ } else {
+ pjmedia_frame_ext *f_dst = (pjmedia_frame_ext*)frm_dst;
+
+ if (f_dst->samples_cnt != 0) {
+ frm_dst->type = PJMEDIA_FRAME_TYPE_EXTENDED;
+ pjmedia_frame_ext_append_subframe(f_dst, NULL, 0, (pj_uint16_t)
+ (cport_dst->samples_per_frame - f_dst->samples_cnt));
+ if (cport_dst->slot) {
+ pjmedia_port_put_frame(cport_dst->port, frm_dst);
+
+ /* Reset TX buffer. */
+ f_dst->subframe_cnt = 0;
+ f_dst->samples_cnt = 0;
+ }
+
+ /* Update TX timestamp. */
+ pj_add_timestamp32(&cport_dst->ts_tx,
+ cport_dst->samples_per_frame);
+ }
+ }
+
+ /* Synchronize clock. */
+ while (pj_cmp_timestamp(&cport_dst->ts_clock,
+ &cport_dst->ts_tx) > 0)
+ {
+ frm_dst->type = PJMEDIA_FRAME_TYPE_NONE;
+ frm_dst->timestamp = cport_dst->ts_tx;
+ if (cport_dst->slot)
+ pjmedia_port_put_frame(cport_dst->port, frm_dst);
+
+ /* Update TX timestamp. */
+ pj_add_timestamp32(&cport_dst->ts_tx, cport_dst->samples_per_frame);
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Player callback.
+ */
+static pj_status_t get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata;
+ unsigned ci, i;
+
+ /* Lock mutex */
+ pj_mutex_lock(conf->mutex);
+
+ /* Call get_frame() from all ports (except port 0) that has
+ * receiver and distribute the frame (put the frame to the destination
+ * port's buffer to accommodate different ptime, and ultimately call
+ * put_frame() of that port) to ports that are receiving from this port.
+ */
+ for (i=1, ci=1; i<conf->max_ports && ci<conf->port_cnt; ++i) {
+ struct conf_port *cport = conf->ports[i];
+ unsigned master_samples_per_frame;
+
+ /* Skip empty port. */
+ if (!cport)
+ continue;
+
+ /* Var "ci" is to count how many ports have been visited so far. */
+ ++ci;
+
+ master_samples_per_frame = PJMEDIA_PIA_SPF(&conf->master_port->info);
+
+ /* Update clock of the port. */
+ pj_add_timestamp32(&cport->ts_clock, master_samples_per_frame);
+
+ /* Skip if we're not allowed to receive from this port or
+ * the port doesn't have listeners.
+ */
+ if (cport->rx_setting == PJMEDIA_PORT_DISABLE ||
+ cport->listener_cnt == 0)
+ {
+ cport->rx_level = 0;
+ pj_add_timestamp32(&cport->ts_rx, master_samples_per_frame);
+ continue;
+ }
+
+ /* Get frame from each port, put it to the listener TX buffer,
+ * and eventually call put_frame() of the listener. This loop
+ * will also make sure the ptime between conf & port synchronized.
+ */
+ while (pj_cmp_timestamp(&cport->ts_clock, &cport->ts_rx) > 0) {
+ pjmedia_frame *f = (pjmedia_frame*)conf->buf;
+ pj_status_t status;
+ unsigned j;
+ pj_int32_t level = 0;
+
+ pj_add_timestamp32(&cport->ts_rx, cport->samples_per_frame);
+
+ f->buf = &conf->buf[sizeof(pjmedia_frame)];
+ f->size = cport->samples_per_frame<<1;
+
+ /* Get frame from port. */
+ status = pjmedia_port_get_frame(cport->port, f);
+ if (status != PJ_SUCCESS)
+ continue;
+
+ /* Calculate & adjust RX level. */
+ if (f->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+ if (cport->rx_adj_level != NORMAL_LEVEL) {
+ pj_int16_t *p = (pj_int16_t*)f->buf;
+ pj_int16_t *end;
+
+ end = p + (f->size >> 1);
+ while (p < end) {
+ pj_int32_t itemp = *p;
+
+ /* Adjust the level */
+ itemp = (itemp * cport->rx_adj_level) >> 7;
+
+ /* Clip the signal if it's too loud */
+ if (itemp > MAX_LEVEL) itemp = MAX_LEVEL;
+ else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL;
+
+ level += PJ_ABS(itemp);
+
+ /* Put back in the buffer. */
+ *p = (pj_int16_t)itemp;
+ ++p;
+ }
+ level /= (f->size >> 1);
+ } else {
+ level = pjmedia_calc_avg_signal((const pj_int16_t*)f->buf,
+ f->size >> 1);
+ }
+ } else if (f->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ /* For extended frame, level is unknown, so we just set
+ * it to NORMAL_LEVEL.
+ */
+ level = NORMAL_LEVEL;
+ }
+
+ cport->rx_level = pjmedia_linear2ulaw(level) ^ 0xff;
+
+ /* Put the frame to all listeners. */
+ for (j=0; j < cport->listener_cnt; ++j)
+ {
+ struct conf_port *listener;
+
+ listener = conf->ports[cport->listener_slots[j]];
+
+ /* Skip if this listener doesn't want to receive audio */
+ if (listener->tx_setting == PJMEDIA_PORT_DISABLE) {
+ pj_add_timestamp32(&listener->ts_tx,
+ listener->samples_per_frame);
+ listener->tx_level = 0;
+ continue;
+ }
+
+ status = write_frame(listener, f);
+ if (status != PJ_SUCCESS) {
+ listener->tx_level = 0;
+ continue;
+ }
+
+ /* Set listener TX level based on transmitter RX level &
+ * listener TX level.
+ */
+ listener->tx_level = (cport->rx_level * listener->tx_adj_level)
+ >> 8;
+ }
+ }
+ }
+
+ /* Keep alive. Update TX timestamp and send frame type NONE to all
+ * underflow ports at their own clock.
+ */
+ for (i=1, ci=1; i<conf->max_ports && ci<conf->port_cnt; ++i) {
+ struct conf_port *cport = conf->ports[i];
+
+ /* Skip empty port. */
+ if (!cport)
+ continue;
+
+ /* Var "ci" is to count how many ports have been visited so far. */
+ ++ci;
+
+ if (cport->tx_setting==PJMEDIA_PORT_MUTE || cport->transmitter_cnt==0)
+ {
+ pjmedia_frame_ext *f;
+
+ /* Clear left-over samples in tx_buffer, if any, so that it won't
+ * be transmitted next time we have audio signal.
+ */
+ f = (pjmedia_frame_ext*)cport->tx_buf;
+ f->base.type = PJMEDIA_FRAME_TYPE_NONE;
+ f->base.size = 0;
+ f->samples_cnt = 0;
+ f->subframe_cnt = 0;
+
+ cport->tx_level = 0;
+
+ while (pj_cmp_timestamp(&cport->ts_clock, &cport->ts_tx) > 0)
+ {
+ if (cport->tx_setting == PJMEDIA_PORT_ENABLE) {
+ pjmedia_frame tmp_f;
+
+ tmp_f.timestamp = cport->ts_tx;
+ tmp_f.type = PJMEDIA_FRAME_TYPE_NONE;
+ tmp_f.buf = NULL;
+ tmp_f.size = 0;
+
+ pjmedia_port_put_frame(cport->port, &tmp_f);
+ pj_add_timestamp32(&cport->ts_tx, cport->samples_per_frame);
+ }
+ }
+ }
+ }
+
+ /* Return sound playback frame. */
+ do {
+ struct conf_port *this_cport = conf->ports[this_port->port_data.ldata];
+ pjmedia_frame *f_src = (pjmedia_frame*) this_cport->tx_buf;
+
+ frame->type = f_src->type;
+
+ if (f_src->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ pjmedia_frame_ext *f_src_ = (pjmedia_frame_ext*)f_src;
+ pjmedia_frame_ext *f_dst = (pjmedia_frame_ext*)frame;
+ pjmedia_frame_ext_subframe *sf;
+ unsigned samples_per_subframe;
+
+ if (f_src_->samples_cnt < this_cport->samples_per_frame) {
+ f_dst->base.type = PJMEDIA_FRAME_TYPE_NONE;
+ f_dst->samples_cnt = 0;
+ f_dst->subframe_cnt = 0;
+ break;
+ }
+
+ f_dst->samples_cnt = 0;
+ f_dst->subframe_cnt = 0;
+ i = 0;
+ samples_per_subframe = f_src_->samples_cnt / f_src_->subframe_cnt;
+
+
+ while (f_dst->samples_cnt < this_cport->samples_per_frame) {
+ sf = pjmedia_frame_ext_get_subframe(f_src_, i++);
+ pj_assert(sf);
+ pjmedia_frame_ext_append_subframe(f_dst, sf->data, sf->bitlen,
+ samples_per_subframe);
+ }
+
+ /* Shift left TX buffer. */
+ pjmedia_frame_ext_pop_subframes(f_src_, i);
+
+ } else if (f_src->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+ if ((f_src->size>>1) < this_cport->samples_per_frame) {
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+ frame->size = 0;
+ break;
+ }
+
+ pjmedia_copy_samples((pj_int16_t*)frame->buf,
+ (pj_int16_t*)f_src->buf,
+ this_cport->samples_per_frame);
+ frame->size = this_cport->samples_per_frame << 1;
+
+ /* Shift left TX buffer. */
+ f_src->size -= frame->size;
+ if (f_src->size)
+ pjmedia_move_samples((pj_int16_t*)f_src->buf,
+ (pj_int16_t*)f_src->buf +
+ this_cport->samples_per_frame,
+ f_src->size >> 1);
+ } else { /* PJMEDIA_FRAME_TYPE_NONE */
+ pjmedia_frame_ext *f_src_ = (pjmedia_frame_ext*)f_src;
+
+ /* Reset source/TX buffer */
+ f_src_->base.size = 0;
+ f_src_->samples_cnt = 0;
+ f_src_->subframe_cnt = 0;
+ }
+ } while (0);
+
+ /* Unlock mutex */
+ pj_mutex_unlock(conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Recorder callback.
+ */
+static pj_status_t put_frame(pjmedia_port *this_port,
+ pjmedia_frame *f)
+{
+ pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata;
+ struct conf_port *cport;
+ unsigned j;
+ pj_int32_t level = 0;
+
+ /* Lock mutex */
+ pj_mutex_lock(conf->mutex);
+
+ /* Get conf port of this port */
+ cport = conf->ports[this_port->port_data.ldata];
+ if (cport == NULL) {
+ /* Unlock mutex */
+ pj_mutex_unlock(conf->mutex);
+ return PJ_SUCCESS;
+ }
+
+ pj_add_timestamp32(&cport->ts_rx, cport->samples_per_frame);
+
+ /* Skip if this port is muted/disabled. */
+ if (cport->rx_setting == PJMEDIA_PORT_DISABLE) {
+ cport->rx_level = 0;
+ /* Unlock mutex */
+ pj_mutex_unlock(conf->mutex);
+ return PJ_SUCCESS;
+ }
+
+ /* Skip if no port is listening to the microphone */
+ if (cport->listener_cnt == 0) {
+ cport->rx_level = 0;
+ /* Unlock mutex */
+ pj_mutex_unlock(conf->mutex);
+ return PJ_SUCCESS;
+ }
+
+ /* Calculate & adjust RX level. */
+ if (f->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+ if (cport->rx_adj_level != NORMAL_LEVEL) {
+ pj_int16_t *p = (pj_int16_t*)f->buf;
+ pj_int16_t *end;
+
+ end = p + (f->size >> 1);
+ while (p < end) {
+ pj_int32_t itemp = *p;
+
+ /* Adjust the level */
+ itemp = (itemp * cport->rx_adj_level) >> 7;
+
+ /* Clip the signal if it's too loud */
+ if (itemp > MAX_LEVEL) itemp = MAX_LEVEL;
+ else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL;
+
+ level += PJ_ABS(itemp);
+
+ /* Put back in the buffer. */
+ *p = (pj_int16_t)itemp;
+ ++p;
+ }
+ level /= (f->size >> 1);
+ } else {
+ level = pjmedia_calc_avg_signal((const pj_int16_t*)f->buf,
+ f->size >> 1);
+ }
+ } else if (f->type == PJMEDIA_FRAME_TYPE_EXTENDED) {
+ /* For extended frame, level is unknown, so we just set
+ * it to NORMAL_LEVEL.
+ */
+ level = NORMAL_LEVEL;
+ }
+
+ cport->rx_level = pjmedia_linear2ulaw(level) ^ 0xff;
+
+ /* Put the frame to all listeners. */
+ for (j=0; j < cport->listener_cnt; ++j)
+ {
+ struct conf_port *listener;
+ pj_status_t status;
+
+ listener = conf->ports[cport->listener_slots[j]];
+
+ /* Skip if this listener doesn't want to receive audio */
+ if (listener->tx_setting == PJMEDIA_PORT_DISABLE) {
+ pj_add_timestamp32(&listener->ts_tx,
+ listener->samples_per_frame);
+ listener->tx_level = 0;
+ continue;
+ }
+
+ /* Skip loopback for now. */
+ if (listener == cport) {
+ pj_add_timestamp32(&listener->ts_tx,
+ listener->samples_per_frame);
+ listener->tx_level = 0;
+ continue;
+ }
+
+ status = write_frame(listener, f);
+ if (status != PJ_SUCCESS) {
+ listener->tx_level = 0;
+ continue;
+ }
+
+ /* Set listener TX level based on transmitter RX level & listener
+ * TX level.
+ */
+ listener->tx_level = (cport->rx_level * listener->tx_adj_level) >> 8;
+ }
+
+ /* Unlock mutex */
+ pj_mutex_unlock(conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+#endif
diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c
new file mode 100644
index 0000000..a08e7b0
--- /dev/null
+++ b/pjmedia/src/pjmedia/conference.c
@@ -0,0 +1,2098 @@
+/* $Id: conference.c 4162 2012-06-11 04:17:54Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/conference.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pjmedia/delaybuf.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/port.h>
+#include <pjmedia/resample.h>
+#include <pjmedia/silencedet.h>
+#include <pjmedia/sound_port.h>
+#include <pjmedia/stereo.h>
+#include <pj/array.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#if !defined(PJMEDIA_CONF_USE_SWITCH_BOARD) || PJMEDIA_CONF_USE_SWITCH_BOARD==0
+
+/* CONF_DEBUG enables detailed operation of the conference bridge.
+ * Beware that it prints large amounts of logs (several lines per frame).
+ */
+//#define CONF_DEBUG
+#ifdef CONF_DEBUG
+# include <stdio.h>
+# define TRACE_(x) PJ_LOG(5,x)
+#else
+# define TRACE_(x)
+#endif
+
+
+/* REC_FILE macro enables recording of the samples written to the sound
+ * device. The file contains RAW PCM data with no header, and has the
+ * same settings (clock rate etc) as the conference bridge.
+ * This should only be enabled when debugging audio quality *only*.
+ */
+//#define REC_FILE "confrec.pcm"
+#ifdef REC_FILE
+static FILE *fhnd_rec;
+#endif
+
+
+#define THIS_FILE "conference.c"
+
+#define RX_BUF_COUNT PJMEDIA_SOUND_BUFFER_COUNT
+
+#define BYTES_PER_SAMPLE 2
+
+#define SIGNATURE PJMEDIA_CONF_BRIDGE_SIGNATURE
+#define SIGNATURE_PORT PJMEDIA_SIG_PORT_CONF_PASV
+/* Normal level is hardcodec to 128 in all over places */
+#define NORMAL_LEVEL 128
+#define SLOT_TYPE unsigned
+#define INVALID_SLOT ((SLOT_TYPE)-1)
+
+
+/* These are settings to control the adaptivity of changes in the
+ * signal level of the ports, so that sudden change in signal level
+ * in the port does not cause misaligned signal (which causes noise).
+ */
+#define ATTACK_A (conf->clock_rate / conf->samples_per_frame)
+#define ATTACK_B 1
+#define DECAY_A 0
+#define DECAY_B 1
+
+#define SIMPLE_AGC(last, target) \
+ if (target >= last) \
+ target = (ATTACK_A*(last+1)+ATTACK_B*target)/(ATTACK_A+ATTACK_B); \
+ else \
+ target = (DECAY_A*last+DECAY_B*target)/(DECAY_A+DECAY_B)
+
+#define MAX_LEVEL (32767)
+#define MIN_LEVEL (-32768)
+
+#define IS_OVERFLOW(s) ((s > MAX_LEVEL) || (s < MIN_LEVEL))
+
+
+/*
+ * DON'T GET CONFUSED WITH TX/RX!!
+ *
+ * TX and RX directions are always viewed from the conference bridge's point
+ * of view, and NOT from the port's point of view. So TX means the bridge
+ * is transmitting to the port, RX means the bridge is receiving from the
+ * port.
+ */
+
+
+/**
+ * This is a port connected to conference bridge.
+ */
+struct conf_port
+{
+ pj_str_t name; /**< Port name. */
+ pjmedia_port *port; /**< get_frame() and put_frame() */
+ pjmedia_port_op rx_setting; /**< Can we receive from this port */
+ pjmedia_port_op tx_setting; /**< Can we transmit to this port */
+ unsigned listener_cnt; /**< Number of listeners. */
+ SLOT_TYPE *listener_slots;/**< Array of listeners. */
+ unsigned transmitter_cnt;/**<Number of transmitters. */
+
+ /* Shortcut for port info. */
+ unsigned clock_rate; /**< Port's clock rate. */
+ unsigned samples_per_frame; /**< Port's samples per frame. */
+ unsigned channel_count; /**< Port's channel count. */
+
+ /* Calculated signal levels: */
+ unsigned tx_level; /**< Last tx level to this port. */
+ unsigned rx_level; /**< Last rx level from this port. */
+
+ /* The normalized signal level adjustment.
+ * A value of 128 (NORMAL_LEVEL) means there's no adjustment.
+ */
+ unsigned tx_adj_level; /**< Adjustment for TX. */
+ unsigned rx_adj_level; /**< Adjustment for RX. */
+
+ /* Resample, for converting clock rate, if they're different. */
+ pjmedia_resample *rx_resample;
+ pjmedia_resample *tx_resample;
+
+ /* RX buffer is temporary buffer to be used when there is mismatch
+ * between port's sample rate or ptime with conference's sample rate
+ * or ptime. The buffer is used for sampling rate conversion AND/OR to
+ * buffer the samples until there are enough samples to fulfill a
+ * complete frame to be processed by the bridge.
+ *
+ * When both sample rate AND ptime of the port match the conference
+ * settings, this buffer will not be created.
+ *
+ * This buffer contains samples at port's clock rate.
+ * The size of this buffer is the sum between port's samples per frame
+ * and bridge's samples per frame.
+ */
+ pj_int16_t *rx_buf; /**< The RX buffer. */
+ unsigned rx_buf_cap; /**< Max size, in samples */
+ unsigned rx_buf_count; /**< # of samples in the buf. */
+
+ /* Mix buf is a temporary buffer used to mix all signal received
+ * by this port from all other ports. The mixed signal will be
+ * automatically adjusted to the appropriate level whenever
+ * there is possibility of clipping.
+ *
+ * This buffer contains samples at bridge's clock rate.
+ * The size of this buffer is equal to samples per frame of the bridge.
+ */
+
+ int mix_adj; /**< Adjustment level for mix_buf. */
+ int last_mix_adj; /**< Last adjustment level. */
+ pj_int32_t *mix_buf; /**< Total sum of signal. */
+
+ /* Tx buffer is a temporary buffer to be used when there's mismatch
+ * between port's clock rate or ptime with conference's sample rate
+ * or ptime. This buffer is used as the source of the sampling rate
+ * conversion AND/OR to buffer the samples until there are enough
+ * samples to fulfill a complete frame to be transmitted to the port.
+ *
+ * When both sample rate and ptime of the port match the bridge's
+ * settings, this buffer will not be created.
+ *
+ * This buffer contains samples at port's clock rate.
+ * The size of this buffer is the sum between port's samples per frame
+ * and bridge's samples per frame.
+ */
+ pj_int16_t *tx_buf; /**< Tx buffer. */
+ unsigned tx_buf_cap; /**< Max size, in samples. */
+ unsigned tx_buf_count; /**< # of samples in the buffer. */
+
+ /* When the port is not receiving signal from any other ports (e.g. when
+ * no other ports is transmitting to this port), the bridge periodically
+ * transmit NULL frame to the port to keep the port "alive" (for example,
+ * a stream port needs this heart-beat to periodically transmit silence
+ * frame to keep NAT binding alive).
+ *
+ * This NULL frame should be sent to the port at the port's ptime rate.
+ * So if the port's ptime is greater than the bridge's ptime, the bridge
+ * needs to delay the NULL frame until it's the right time to do so.
+ *
+ * This variable keeps track of how many pending NULL samples are being
+ * "held" for this port. Once this value reaches samples_per_frame
+ * value of the port, a NULL frame is sent. The samples value on this
+ * variable is clocked at the port's clock rate.
+ */
+ unsigned tx_heart_beat;
+
+ /* Delay buffer is a special buffer for sound device port (port 0, master
+ * port) and other passive ports (sound device port is also passive port).
+ *
+ * We need the delay buffer because we can not expect the mic and speaker
+ * thread to run equally after one another. In most systems, each thread
+ * will run multiple times before the other thread gains execution time.
+ * For example, in my system, mic thread is called three times, then
+ * speaker thread is called three times, and so on. This we call burst.
+ *
+ * There is also possibility of drift, unbalanced rate between put_frame
+ * and get_frame operation, in passive ports. If drift happens, snd_buf
+ * needs to be expanded or shrinked.
+ *
+ * Burst and drift are handled by delay buffer.
+ */
+ pjmedia_delay_buf *delay_buf;
+};
+
+
+/*
+ * Conference bridge.
+ */
+struct pjmedia_conf
+{
+ unsigned options; /**< Bitmask options. */
+ unsigned max_ports; /**< Maximum ports. */
+ unsigned port_cnt; /**< Current number of ports. */
+ unsigned connect_cnt; /**< Total number of connections */
+ pjmedia_snd_port *snd_dev_port; /**< Sound device port. */
+ pjmedia_port *master_port; /**< Port zero's port. */
+ char master_name_buf[80]; /**< Port0 name buffer. */
+ pj_mutex_t *mutex; /**< Conference mutex. */
+ struct conf_port **ports; /**< Array of ports. */
+ unsigned clock_rate; /**< Sampling rate. */
+ unsigned channel_count;/**< Number of channels (1=mono). */
+ unsigned samples_per_frame; /**< Samples per frame. */
+ unsigned bits_per_sample; /**< Bits per sample. */
+};
+
+
+/* Prototypes */
+static pj_status_t put_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t get_frame_pasv(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t destroy_port(pjmedia_port *this_port);
+static pj_status_t destroy_port_pasv(pjmedia_port *this_port);
+
+
+/*
+ * Create port.
+ */
+static pj_status_t create_conf_port( pj_pool_t *pool,
+ pjmedia_conf *conf,
+ pjmedia_port *port,
+ const pj_str_t *name,
+ struct conf_port **p_conf_port)
+{
+ struct conf_port *conf_port;
+ pj_status_t status;
+
+ /* Create port. */
+ conf_port = PJ_POOL_ZALLOC_T(pool, struct conf_port);
+ PJ_ASSERT_RETURN(conf_port, PJ_ENOMEM);
+
+ /* Set name */
+ pj_strdup_with_null(pool, &conf_port->name, name);
+
+ /* Default has tx and rx enabled. */
+ conf_port->rx_setting = PJMEDIA_PORT_ENABLE;
+ conf_port->tx_setting = PJMEDIA_PORT_ENABLE;
+
+ /* Default level adjustment is 128 (which means no adjustment) */
+ conf_port->tx_adj_level = NORMAL_LEVEL;
+ conf_port->rx_adj_level = NORMAL_LEVEL;
+
+ /* Create transmit flag array */
+ conf_port->listener_slots = (SLOT_TYPE*)
+ pj_pool_zalloc(pool,
+ conf->max_ports * sizeof(SLOT_TYPE));
+ PJ_ASSERT_RETURN(conf_port->listener_slots, PJ_ENOMEM);
+
+ /* Save some port's infos, for convenience. */
+ if (port) {
+ pjmedia_audio_format_detail *afd;
+
+ afd = pjmedia_format_get_audio_format_detail(&port->info.fmt, 1);
+ conf_port->port = port;
+ conf_port->clock_rate = afd->clock_rate;
+ conf_port->samples_per_frame = PJMEDIA_AFD_SPF(afd);
+ conf_port->channel_count = afd->channel_count;
+ } else {
+ conf_port->port = NULL;
+ conf_port->clock_rate = conf->clock_rate;
+ conf_port->samples_per_frame = conf->samples_per_frame;
+ conf_port->channel_count = conf->channel_count;
+ }
+
+ /* If port's clock rate is different than conference's clock rate,
+ * create a resample sessions.
+ */
+ if (conf_port->clock_rate != conf->clock_rate) {
+
+ pj_bool_t high_quality;
+ pj_bool_t large_filter;
+
+ high_quality = ((conf->options & PJMEDIA_CONF_USE_LINEAR)==0);
+ large_filter = ((conf->options & PJMEDIA_CONF_SMALL_FILTER)==0);
+
+ /* Create resample for rx buffer. */
+ status = pjmedia_resample_create( pool,
+ high_quality,
+ large_filter,
+ conf->channel_count,
+ conf_port->clock_rate,/* Rate in */
+ conf->clock_rate, /* Rate out */
+ conf->samples_per_frame *
+ conf_port->clock_rate /
+ conf->clock_rate,
+ &conf_port->rx_resample);
+ if (status != PJ_SUCCESS)
+ return status;
+
+
+ /* Create resample for tx buffer. */
+ status = pjmedia_resample_create(pool,
+ high_quality,
+ large_filter,
+ conf->channel_count,
+ conf->clock_rate, /* Rate in */
+ conf_port->clock_rate, /* Rate out */
+ conf->samples_per_frame,
+ &conf_port->tx_resample);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /*
+ * Initialize rx and tx buffer, only when port's samples per frame or
+ * port's clock rate or channel number is different then the conference
+ * bridge settings.
+ */
+ if (conf_port->clock_rate != conf->clock_rate ||
+ conf_port->channel_count != conf->channel_count ||
+ conf_port->samples_per_frame != conf->samples_per_frame)
+ {
+ unsigned port_ptime, conf_ptime, buff_ptime;
+
+ port_ptime = conf_port->samples_per_frame / conf_port->channel_count *
+ 1000 / conf_port->clock_rate;
+ conf_ptime = conf->samples_per_frame / conf->channel_count *
+ 1000 / conf->clock_rate;
+
+ /* Calculate the size (in ptime) for the port buffer according to
+ * this formula:
+ * - if either ptime is an exact multiple of the other, then use
+ * the larger ptime (e.g. 20ms and 40ms, use 40ms).
+ * - if not, then the ptime is sum of both ptimes (e.g. 20ms
+ * and 30ms, use 50ms)
+ */
+ if (port_ptime > conf_ptime) {
+ buff_ptime = port_ptime;
+ if (port_ptime % conf_ptime)
+ buff_ptime += conf_ptime;
+ } else {
+ buff_ptime = conf_ptime;
+ if (conf_ptime % port_ptime)
+ buff_ptime += port_ptime;
+ }
+
+ /* Create RX buffer. */
+ //conf_port->rx_buf_cap = (unsigned)(conf_port->samples_per_frame +
+ // conf->samples_per_frame *
+ // conf_port->clock_rate * 1.0 /
+ // conf->clock_rate + 0.5);
+ conf_port->rx_buf_cap = conf_port->clock_rate * buff_ptime / 1000;
+ if (conf_port->channel_count > conf->channel_count)
+ conf_port->rx_buf_cap *= conf_port->channel_count;
+ else
+ conf_port->rx_buf_cap *= conf->channel_count;
+
+ conf_port->rx_buf_count = 0;
+ conf_port->rx_buf = (pj_int16_t*)
+ pj_pool_alloc(pool, conf_port->rx_buf_cap *
+ sizeof(conf_port->rx_buf[0]));
+ PJ_ASSERT_RETURN(conf_port->rx_buf, PJ_ENOMEM);
+
+ /* Create TX buffer. */
+ conf_port->tx_buf_cap = conf_port->rx_buf_cap;
+ conf_port->tx_buf_count = 0;
+ conf_port->tx_buf = (pj_int16_t*)
+ pj_pool_alloc(pool, conf_port->tx_buf_cap *
+ sizeof(conf_port->tx_buf[0]));
+ PJ_ASSERT_RETURN(conf_port->tx_buf, PJ_ENOMEM);
+ }
+
+
+ /* Create mix buffer. */
+ conf_port->mix_buf = (pj_int32_t*)
+ pj_pool_zalloc(pool, conf->samples_per_frame *
+ sizeof(conf_port->mix_buf[0]));
+ PJ_ASSERT_RETURN(conf_port->mix_buf, PJ_ENOMEM);
+ conf_port->last_mix_adj = NORMAL_LEVEL;
+
+
+ /* Done */
+ *p_conf_port = conf_port;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Add passive port.
+ */
+static pj_status_t create_pasv_port( pjmedia_conf *conf,
+ pj_pool_t *pool,
+ const pj_str_t *name,
+ pjmedia_port *port,
+ struct conf_port **p_conf_port)
+{
+ struct conf_port *conf_port;
+ pj_status_t status;
+ unsigned ptime;
+
+ /* Create port */
+ status = create_conf_port(pool, conf, port, name, &conf_port);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Passive port has delay buf. */
+ ptime = conf->samples_per_frame * 1000 / conf->clock_rate /
+ conf->channel_count;
+ status = pjmedia_delay_buf_create(pool, name->ptr,
+ conf->clock_rate,
+ conf->samples_per_frame,
+ conf->channel_count,
+ RX_BUF_COUNT * ptime, /* max delay */
+ 0, /* options */
+ &conf_port->delay_buf);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ *p_conf_port = conf_port;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create port zero for the sound device.
+ */
+static pj_status_t create_sound_port( pj_pool_t *pool,
+ pjmedia_conf *conf )
+{
+ struct conf_port *conf_port;
+ pj_str_t name = { "Master/sound", 12 };
+ pj_status_t status;
+
+
+ status = create_pasv_port(conf, pool, &name, NULL, &conf_port);
+ if (status != PJ_SUCCESS)
+ return status;
+
+
+ /* Create sound device port: */
+
+ if ((conf->options & PJMEDIA_CONF_NO_DEVICE) == 0) {
+ pjmedia_aud_stream *strm;
+ pjmedia_aud_param param;
+
+ /*
+ * If capture is disabled then create player only port.
+ * Otherwise create bidirectional sound device port.
+ */
+ if (conf->options & PJMEDIA_CONF_NO_MIC) {
+ status = pjmedia_snd_port_create_player(pool, -1, conf->clock_rate,
+ conf->channel_count,
+ conf->samples_per_frame,
+ conf->bits_per_sample,
+ 0, /* options */
+ &conf->snd_dev_port);
+
+ } else {
+ status = pjmedia_snd_port_create( pool, -1, -1, conf->clock_rate,
+ conf->channel_count,
+ conf->samples_per_frame,
+ conf->bits_per_sample,
+ 0, /* Options */
+ &conf->snd_dev_port);
+
+ }
+
+ if (status != PJ_SUCCESS)
+ return status;
+
+ strm = pjmedia_snd_port_get_snd_stream(conf->snd_dev_port);
+ status = pjmedia_aud_stream_get_param(strm, &param);
+ if (status == PJ_SUCCESS) {
+ pjmedia_aud_dev_info snd_dev_info;
+ if (conf->options & PJMEDIA_CONF_NO_MIC)
+ pjmedia_aud_dev_get_info(param.play_id, &snd_dev_info);
+ else
+ pjmedia_aud_dev_get_info(param.rec_id, &snd_dev_info);
+ pj_strdup2_with_null(pool, &conf_port->name, snd_dev_info.name);
+ }
+
+ PJ_LOG(5,(THIS_FILE, "Sound device successfully created for port 0"));
+ }
+
+
+ /* Add the port to the bridge */
+ conf->ports[0] = conf_port;
+ conf->port_cnt++;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Create conference bridge.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool,
+ unsigned max_ports,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ unsigned options,
+ pjmedia_conf **p_conf )
+{
+ pjmedia_conf *conf;
+ const pj_str_t name = { "Conf", 4 };
+ pj_status_t status;
+
+ /* Can only accept 16bits per sample, for now.. */
+ PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL);
+
+ PJ_LOG(5,(THIS_FILE, "Creating conference bridge with %d ports",
+ max_ports));
+
+ /* Create and init conf structure. */
+ conf = PJ_POOL_ZALLOC_T(pool, pjmedia_conf);
+ PJ_ASSERT_RETURN(conf, PJ_ENOMEM);
+
+ conf->ports = (struct conf_port**)
+ pj_pool_zalloc(pool, max_ports*sizeof(void*));
+ PJ_ASSERT_RETURN(conf->ports, PJ_ENOMEM);
+
+ conf->options = options;
+ conf->max_ports = max_ports;
+ conf->clock_rate = clock_rate;
+ conf->channel_count = channel_count;
+ conf->samples_per_frame = samples_per_frame;
+ conf->bits_per_sample = bits_per_sample;
+
+
+ /* Create and initialize the master port interface. */
+ conf->master_port = PJ_POOL_ZALLOC_T(pool, pjmedia_port);
+ PJ_ASSERT_RETURN(conf->master_port, PJ_ENOMEM);
+
+ pjmedia_port_info_init(&conf->master_port->info, &name, SIGNATURE,
+ clock_rate, channel_count, bits_per_sample,
+ samples_per_frame);
+
+ conf->master_port->port_data.pdata = conf;
+ conf->master_port->port_data.ldata = 0;
+
+ conf->master_port->get_frame = &get_frame;
+ conf->master_port->put_frame = &put_frame;
+ conf->master_port->on_destroy = &destroy_port;
+
+
+ /* Create port zero for sound device. */
+ status = create_sound_port(pool, conf);
+ if (status != PJ_SUCCESS) {
+ pjmedia_conf_destroy(conf);
+ return status;
+ }
+
+ /* Create mutex. */
+ status = pj_mutex_create_recursive(pool, "conf", &conf->mutex);
+ if (status != PJ_SUCCESS) {
+ pjmedia_conf_destroy(conf);
+ return status;
+ }
+
+ /* If sound device was created, connect sound device to the
+ * master port.
+ */
+ if (conf->snd_dev_port) {
+ status = pjmedia_snd_port_connect( conf->snd_dev_port,
+ conf->master_port );
+ if (status != PJ_SUCCESS) {
+ pjmedia_conf_destroy(conf);
+ return status;
+ }
+ }
+
+
+ /* Done */
+
+ *p_conf = conf;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Pause sound device.
+ */
+static pj_status_t pause_sound( pjmedia_conf *conf )
+{
+ /* Do nothing. */
+ PJ_UNUSED_ARG(conf);
+ return PJ_SUCCESS;
+}
+
+/*
+ * Resume sound device.
+ */
+static pj_status_t resume_sound( pjmedia_conf *conf )
+{
+ /* Do nothing. */
+ PJ_UNUSED_ARG(conf);
+ return PJ_SUCCESS;
+}
+
+
+/**
+ * Destroy conference bridge.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf )
+{
+ unsigned i, ci;
+
+ PJ_ASSERT_RETURN(conf != NULL, PJ_EINVAL);
+
+ /* Destroy sound device port. */
+ if (conf->snd_dev_port) {
+ pjmedia_snd_port_destroy(conf->snd_dev_port);
+ conf->snd_dev_port = NULL;
+ }
+
+ /* Destroy delay buf of all (passive) ports. */
+ for (i=0, ci=0; i<conf->max_ports && ci<conf->port_cnt; ++i) {
+ struct conf_port *cport;
+
+ cport = conf->ports[i];
+ if (!cport)
+ continue;
+
+ ++ci;
+ if (cport->delay_buf) {
+ pjmedia_delay_buf_destroy(cport->delay_buf);
+ cport->delay_buf = NULL;
+ }
+ }
+
+ /* Destroy mutex */
+ if (conf->mutex)
+ pj_mutex_destroy(conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy the master port (will destroy the conference)
+ */
+static pj_status_t destroy_port(pjmedia_port *this_port)
+{
+ pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata;
+ return pjmedia_conf_destroy(conf);
+}
+
+static pj_status_t destroy_port_pasv(pjmedia_port *this_port) {
+ pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata;
+ struct conf_port *port = conf->ports[this_port->port_data.ldata];
+ pj_status_t status;
+
+ status = pjmedia_delay_buf_destroy(port->delay_buf);
+ if (status == PJ_SUCCESS)
+ port->delay_buf = NULL;
+
+ return status;
+}
+
+/*
+ * Get port zero interface.
+ */
+PJ_DEF(pjmedia_port*) pjmedia_conf_get_master_port(pjmedia_conf *conf)
+{
+ /* Sanity check. */
+ PJ_ASSERT_RETURN(conf != NULL, NULL);
+
+ /* Can only return port interface when PJMEDIA_CONF_NO_DEVICE was
+ * present in the option.
+ */
+ PJ_ASSERT_RETURN((conf->options & PJMEDIA_CONF_NO_DEVICE) != 0, NULL);
+
+ return conf->master_port;
+}
+
+
+/*
+ * Set master port name.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_set_port0_name(pjmedia_conf *conf,
+ const pj_str_t *name)
+{
+ unsigned len;
+
+ /* Sanity check. */
+ PJ_ASSERT_RETURN(conf != NULL && name != NULL, PJ_EINVAL);
+
+ len = name->slen;
+ if (len > sizeof(conf->master_name_buf))
+ len = sizeof(conf->master_name_buf);
+
+ if (len > 0) pj_memcpy(conf->master_name_buf, name->ptr, len);
+
+ conf->ports[0]->name.ptr = conf->master_name_buf;
+ conf->ports[0]->name.slen = len;
+
+ if (conf->master_port)
+ conf->master_port->info.name = conf->ports[0]->name;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Add stream port to the conference bridge.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_add_port( pjmedia_conf *conf,
+ pj_pool_t *pool,
+ pjmedia_port *strm_port,
+ const pj_str_t *port_name,
+ unsigned *p_port )
+{
+ struct conf_port *conf_port;
+ unsigned index;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(conf && pool && strm_port, PJ_EINVAL);
+
+ /* If port_name is not specified, use the port's name */
+ if (!port_name)
+ port_name = &strm_port->info.name;
+
+ /* For this version of PJMEDIA, channel(s) number MUST be:
+ * - same between port & conference bridge.
+ * - monochannel on port or conference bridge.
+ */
+ if (PJMEDIA_PIA_CCNT(&strm_port->info) != conf->channel_count &&
+ (PJMEDIA_PIA_CCNT(&strm_port->info) != 1 &&
+ conf->channel_count != 1))
+ {
+ pj_assert(!"Number of channels mismatch");
+ return PJMEDIA_ENCCHANNEL;
+ }
+
+ pj_mutex_lock(conf->mutex);
+
+ if (conf->port_cnt >= conf->max_ports) {
+ pj_assert(!"Too many ports");
+ pj_mutex_unlock(conf->mutex);
+ return PJ_ETOOMANY;
+ }
+
+ /* Find empty port in the conference bridge. */
+ for (index=0; index < conf->max_ports; ++index) {
+ if (conf->ports[index] == NULL)
+ break;
+ }
+
+ pj_assert(index != conf->max_ports);
+
+ /* Create conf port structure. */
+ status = create_conf_port(pool, conf, strm_port, port_name, &conf_port);
+ if (status != PJ_SUCCESS) {
+ pj_mutex_unlock(conf->mutex);
+ return status;
+ }
+
+ /* Put the port. */
+ conf->ports[index] = conf_port;
+ conf->port_cnt++;
+
+ /* Done. */
+ if (p_port) {
+ *p_port = index;
+ }
+
+ pj_mutex_unlock(conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Add passive port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_add_passive_port( pjmedia_conf *conf,
+ pj_pool_t *pool,
+ const pj_str_t *name,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ unsigned options,
+ unsigned *p_slot,
+ pjmedia_port **p_port )
+{
+ struct conf_port *conf_port;
+ pjmedia_port *port;
+ unsigned index;
+ pj_str_t tmp;
+ pj_status_t status;
+
+ PJ_LOG(1, (THIS_FILE, "This API has been deprecated since 1.3 and will "
+ "be removed in the future release!"));
+
+ PJ_ASSERT_RETURN(conf && pool, PJ_EINVAL);
+
+ /* For this version of PJMEDIA, channel(s) number MUST be:
+ * - same between port & conference bridge.
+ * - monochannel on port or conference bridge.
+ */
+ if (channel_count != conf->channel_count &&
+ (channel_count != 1 && conf->channel_count != 1))
+ {
+ pj_assert(!"Number of channels mismatch");
+ return PJMEDIA_ENCCHANNEL;
+ }
+
+ /* For this version, options must be zero */
+ PJ_ASSERT_RETURN(options == 0, PJ_EINVAL);
+ PJ_UNUSED_ARG(options);
+
+ pj_mutex_lock(conf->mutex);
+
+ if (conf->port_cnt >= conf->max_ports) {
+ pj_assert(!"Too many ports");
+ pj_mutex_unlock(conf->mutex);
+ return PJ_ETOOMANY;
+ }
+
+ /* Find empty port in the conference bridge. */
+ for (index=0; index < conf->max_ports; ++index) {
+ if (conf->ports[index] == NULL)
+ break;
+ }
+
+ pj_assert(index != conf->max_ports);
+
+ if (name == NULL) {
+ name = &tmp;
+
+ tmp.ptr = (char*) pj_pool_alloc(pool, 32);
+ tmp.slen = pj_ansi_snprintf(tmp.ptr, 32, "ConfPort#%d", index);
+ }
+
+ /* Create and initialize the media port structure. */
+ port = PJ_POOL_ZALLOC_T(pool, pjmedia_port);
+ PJ_ASSERT_RETURN(port, PJ_ENOMEM);
+
+ pjmedia_port_info_init(&port->info, name, SIGNATURE_PORT,
+ clock_rate, channel_count, bits_per_sample,
+ samples_per_frame);
+
+ port->port_data.pdata = conf;
+ port->port_data.ldata = index;
+
+ port->get_frame = &get_frame_pasv;
+ port->put_frame = &put_frame;
+ port->on_destroy = &destroy_port_pasv;
+
+
+ /* Create conf port structure. */
+ status = create_pasv_port(conf, pool, name, port, &conf_port);
+ if (status != PJ_SUCCESS) {
+ pj_mutex_unlock(conf->mutex);
+ return status;
+ }
+
+
+ /* Put the port. */
+ conf->ports[index] = conf_port;
+ conf->port_cnt++;
+
+ /* Done. */
+ if (p_slot)
+ *p_slot = index;
+ if (p_port)
+ *p_port = port;
+
+ pj_mutex_unlock(conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Change TX and RX settings for the port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_configure_port( pjmedia_conf *conf,
+ unsigned slot,
+ pjmedia_port_op tx,
+ pjmedia_port_op rx)
+{
+ struct conf_port *conf_port;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);
+
+ pj_mutex_lock(conf->mutex);
+
+ /* Port must be valid. */
+ conf_port = conf->ports[slot];
+ if (conf_port == NULL) {
+ pj_mutex_unlock(conf->mutex);
+ return PJ_EINVAL;
+ }
+
+ conf_port = conf->ports[slot];
+
+ if (tx != PJMEDIA_PORT_NO_CHANGE)
+ conf_port->tx_setting = tx;
+
+ if (rx != PJMEDIA_PORT_NO_CHANGE)
+ conf_port->rx_setting = rx;
+
+ pj_mutex_unlock(conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Connect port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_connect_port( pjmedia_conf *conf,
+ unsigned src_slot,
+ unsigned sink_slot,
+ int level )
+{
+ struct conf_port *src_port, *dst_port;
+ pj_bool_t start_sound = PJ_FALSE;
+ unsigned i;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && src_slot<conf->max_ports &&
+ sink_slot<conf->max_ports, PJ_EINVAL);
+
+ /* For now, level MUST be zero. */
+ PJ_ASSERT_RETURN(level == 0, PJ_EINVAL);
+
+ pj_mutex_lock(conf->mutex);
+
+ /* Ports must be valid. */
+ src_port = conf->ports[src_slot];
+ dst_port = conf->ports[sink_slot];
+ if (!src_port || !dst_port) {
+ pj_mutex_unlock(conf->mutex);
+ return PJ_EINVAL;
+ }
+
+ /* Check if connection has been made */
+ for (i=0; i<src_port->listener_cnt; ++i) {
+ if (src_port->listener_slots[i] == sink_slot)
+ break;
+ }
+
+ if (i == src_port->listener_cnt) {
+ src_port->listener_slots[src_port->listener_cnt] = sink_slot;
+ ++conf->connect_cnt;
+ ++src_port->listener_cnt;
+ ++dst_port->transmitter_cnt;
+
+ if (conf->connect_cnt == 1)
+ start_sound = 1;
+
+ PJ_LOG(4,(THIS_FILE,"Port %d (%.*s) transmitting to port %d (%.*s)",
+ src_slot,
+ (int)src_port->name.slen,
+ src_port->name.ptr,
+ sink_slot,
+ (int)dst_port->name.slen,
+ dst_port->name.ptr));
+ }
+
+ pj_mutex_unlock(conf->mutex);
+
+ /* Sound device must be started without mutex, otherwise the
+ * sound thread will deadlock (?)
+ */
+ if (start_sound)
+ resume_sound(conf);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Disconnect port
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_disconnect_port( pjmedia_conf *conf,
+ unsigned src_slot,
+ unsigned sink_slot )
+{
+ struct conf_port *src_port, *dst_port;
+ unsigned i;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && src_slot<conf->max_ports &&
+ sink_slot<conf->max_ports, PJ_EINVAL);
+
+ pj_mutex_lock(conf->mutex);
+
+ /* Ports must be valid. */
+ src_port = conf->ports[src_slot];
+ dst_port = conf->ports[sink_slot];
+ if (!src_port || !dst_port) {
+ pj_mutex_unlock(conf->mutex);
+ return PJ_EINVAL;
+ }
+
+ /* Check if connection has been made */
+ for (i=0; i<src_port->listener_cnt; ++i) {
+ if (src_port->listener_slots[i] == sink_slot)
+ break;
+ }
+
+ if (i != src_port->listener_cnt) {
+ pj_assert(src_port->listener_cnt > 0 &&
+ src_port->listener_cnt < conf->max_ports);
+ pj_assert(dst_port->transmitter_cnt > 0 &&
+ dst_port->transmitter_cnt < conf->max_ports);
+ pj_array_erase(src_port->listener_slots, sizeof(SLOT_TYPE),
+ src_port->listener_cnt, i);
+ --conf->connect_cnt;
+ --src_port->listener_cnt;
+ --dst_port->transmitter_cnt;
+
+ PJ_LOG(4,(THIS_FILE,
+ "Port %d (%.*s) stop transmitting to port %d (%.*s)",
+ src_slot,
+ (int)src_port->name.slen,
+ src_port->name.ptr,
+ sink_slot,
+ (int)dst_port->name.slen,
+ dst_port->name.ptr));
+
+ /* if source port is passive port and has no listener, reset delaybuf */
+ if (src_port->delay_buf && src_port->listener_cnt == 0)
+ pjmedia_delay_buf_reset(src_port->delay_buf);
+ }
+
+ pj_mutex_unlock(conf->mutex);
+
+ if (conf->connect_cnt == 0) {
+ pause_sound(conf);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get number of ports currently registered to the conference bridge.
+ */
+PJ_DEF(unsigned) pjmedia_conf_get_port_count(pjmedia_conf *conf)
+{
+ return conf->port_cnt;
+}
+
+/*
+ * Get total number of ports connections currently set up in the bridge.
+ */
+PJ_DEF(unsigned) pjmedia_conf_get_connect_count(pjmedia_conf *conf)
+{
+ return conf->connect_cnt;
+}
+
+
+/*
+ * Remove the specified port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_remove_port( pjmedia_conf *conf,
+ unsigned port )
+{
+ struct conf_port *conf_port;
+ unsigned i;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && port < conf->max_ports, PJ_EINVAL);
+
+ /* Suspend the sound devices.
+ * Don't want to remove port while port is being accessed by sound
+ * device's threads!
+ */
+
+ pj_mutex_lock(conf->mutex);
+
+ /* Port must be valid. */
+ conf_port = conf->ports[port];
+ if (conf_port == NULL) {
+ pj_mutex_unlock(conf->mutex);
+ return PJ_EINVAL;
+ }
+
+ conf_port->tx_setting = PJMEDIA_PORT_DISABLE;
+ conf_port->rx_setting = PJMEDIA_PORT_DISABLE;
+
+ /* Remove this port from transmit array of other ports. */
+ for (i=0; i<conf->max_ports; ++i) {
+ unsigned j;
+ struct conf_port *src_port;
+
+ src_port = conf->ports[i];
+
+ if (!src_port)
+ continue;
+
+ if (src_port->listener_cnt == 0)
+ continue;
+
+ for (j=0; j<src_port->listener_cnt; ++j) {
+ if (src_port->listener_slots[j] == port) {
+ pj_array_erase(src_port->listener_slots, sizeof(SLOT_TYPE),
+ src_port->listener_cnt, j);
+ pj_assert(conf->connect_cnt > 0);
+ --conf->connect_cnt;
+ --src_port->listener_cnt;
+ break;
+ }
+ }
+ }
+
+ /* Update transmitter_cnt of ports we're transmitting to */
+ while (conf_port->listener_cnt) {
+ unsigned dst_slot;
+ struct conf_port *dst_port;
+
+ dst_slot = conf_port->listener_slots[conf_port->listener_cnt-1];
+ dst_port = conf->ports[dst_slot];
+ --dst_port->transmitter_cnt;
+ --conf_port->listener_cnt;
+ pj_assert(conf->connect_cnt > 0);
+ --conf->connect_cnt;
+ }
+
+ /* Destroy pjmedia port if this conf port is passive port,
+ * i.e: has delay buf.
+ */
+ if (conf_port->delay_buf) {
+ pjmedia_port_destroy(conf_port->port);
+ conf_port->port = NULL;
+ }
+
+ /* Remove the port. */
+ conf->ports[port] = NULL;
+ --conf->port_cnt;
+
+ pj_mutex_unlock(conf->mutex);
+
+
+ /* Stop sound if there's no connection. */
+ if (conf->connect_cnt == 0) {
+ pause_sound(conf);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Enum ports.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_enum_ports( pjmedia_conf *conf,
+ unsigned ports[],
+ unsigned *p_count )
+{
+ unsigned i, count=0;
+
+ PJ_ASSERT_RETURN(conf && p_count && ports, PJ_EINVAL);
+
+ /* Lock mutex */
+ pj_mutex_lock(conf->mutex);
+
+ for (i=0; i<conf->max_ports && count<*p_count; ++i) {
+ if (!conf->ports[i])
+ continue;
+
+ ports[count++] = i;
+ }
+
+ /* Unlock mutex */
+ pj_mutex_unlock(conf->mutex);
+
+ *p_count = count;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get port info
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_get_port_info( pjmedia_conf *conf,
+ unsigned slot,
+ pjmedia_conf_port_info *info)
+{
+ struct conf_port *conf_port;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);
+
+ /* Lock mutex */
+ pj_mutex_lock(conf->mutex);
+
+ /* Port must be valid. */
+ conf_port = conf->ports[slot];
+ if (conf_port == NULL) {
+ pj_mutex_unlock(conf->mutex);
+ return PJ_EINVAL;
+ }
+
+ info->slot = slot;
+ info->name = conf_port->name;
+ info->tx_setting = conf_port->tx_setting;
+ info->rx_setting = conf_port->rx_setting;
+ info->listener_cnt = conf_port->listener_cnt;
+ info->listener_slots = conf_port->listener_slots;
+ info->transmitter_cnt = conf_port->transmitter_cnt;
+ info->clock_rate = conf_port->clock_rate;
+ info->channel_count = conf_port->channel_count;
+ info->samples_per_frame = conf_port->samples_per_frame;
+ info->bits_per_sample = conf->bits_per_sample;
+ info->tx_adj_level = conf_port->tx_adj_level - NORMAL_LEVEL;
+ info->rx_adj_level = conf_port->rx_adj_level - NORMAL_LEVEL;
+
+ /* Unlock mutex */
+ pj_mutex_unlock(conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_conf_get_ports_info(pjmedia_conf *conf,
+ unsigned *size,
+ pjmedia_conf_port_info info[])
+{
+ unsigned i, count=0;
+
+ PJ_ASSERT_RETURN(conf && size && info, PJ_EINVAL);
+
+ /* Lock mutex */
+ pj_mutex_lock(conf->mutex);
+
+ for (i=0; i<conf->max_ports && count<*size; ++i) {
+ if (!conf->ports[i])
+ continue;
+
+ pjmedia_conf_get_port_info(conf, i, &info[count]);
+ ++count;
+ }
+
+ /* Unlock mutex */
+ pj_mutex_unlock(conf->mutex);
+
+ *size = count;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get signal level.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_get_signal_level( pjmedia_conf *conf,
+ unsigned slot,
+ unsigned *tx_level,
+ unsigned *rx_level)
+{
+ struct conf_port *conf_port;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);
+
+ /* Lock mutex */
+ pj_mutex_lock(conf->mutex);
+
+ /* Port must be valid. */
+ conf_port = conf->ports[slot];
+ if (conf_port == NULL) {
+ pj_mutex_unlock(conf->mutex);
+ return PJ_EINVAL;
+ }
+
+ if (tx_level != NULL) {
+ *tx_level = conf_port->tx_level;
+ }
+
+ if (rx_level != NULL)
+ *rx_level = conf_port->rx_level;
+
+ /* Unlock mutex */
+ pj_mutex_unlock(conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Adjust RX level of individual port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_adjust_rx_level( pjmedia_conf *conf,
+ unsigned slot,
+ int adj_level )
+{
+ struct conf_port *conf_port;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);
+
+ /* Value must be from -128 to +127 */
+ /* Disabled, you can put more than +127, at your own risk:
+ PJ_ASSERT_RETURN(adj_level >= -128 && adj_level <= 127, PJ_EINVAL);
+ */
+ PJ_ASSERT_RETURN(adj_level >= -128, PJ_EINVAL);
+
+ /* Lock mutex */
+ pj_mutex_lock(conf->mutex);
+
+ /* Port must be valid. */
+ conf_port = conf->ports[slot];
+ if (conf_port == NULL) {
+ pj_mutex_unlock(conf->mutex);
+ return PJ_EINVAL;
+ }
+
+ /* Set normalized adjustment level. */
+ conf_port->rx_adj_level = adj_level + NORMAL_LEVEL;
+
+ /* Unlock mutex */
+ pj_mutex_unlock(conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Adjust TX level of individual port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_adjust_tx_level( pjmedia_conf *conf,
+ unsigned slot,
+ int adj_level )
+{
+ struct conf_port *conf_port;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);
+
+ /* Value must be from -128 to +127 */
+ /* Disabled, you can put more than +127,, at your own risk:
+ PJ_ASSERT_RETURN(adj_level >= -128 && adj_level <= 127, PJ_EINVAL);
+ */
+ PJ_ASSERT_RETURN(adj_level >= -128, PJ_EINVAL);
+
+ /* Lock mutex */
+ pj_mutex_lock(conf->mutex);
+
+ /* Port must be valid. */
+ conf_port = conf->ports[slot];
+ if (conf_port == NULL) {
+ pj_mutex_unlock(conf->mutex);
+ return PJ_EINVAL;
+ }
+
+ /* Set normalized adjustment level. */
+ conf_port->tx_adj_level = adj_level + NORMAL_LEVEL;
+
+ /* Unlock mutex */
+ pj_mutex_unlock(conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Read from port.
+ */
+static pj_status_t read_port( pjmedia_conf *conf,
+ struct conf_port *cport, pj_int16_t *frame,
+ pj_size_t count, pjmedia_frame_type *type )
+{
+
+ pj_assert(count == conf->samples_per_frame);
+
+ TRACE_((THIS_FILE, "read_port %.*s: count=%d",
+ (int)cport->name.slen, cport->name.ptr,
+ count));
+
+ /*
+ * If port's samples per frame and sampling rate and channel count
+ * matche conference bridge's settings, get the frame directly from
+ * the port.
+ */
+ if (cport->rx_buf_cap == 0) {
+ pjmedia_frame f;
+ pj_status_t status;
+
+ f.buf = frame;
+ f.size = count * BYTES_PER_SAMPLE;
+
+ TRACE_((THIS_FILE, " get_frame %.*s: count=%d",
+ (int)cport->name.slen, cport->name.ptr,
+ count));
+
+ status = pjmedia_port_get_frame(cport->port, &f);
+
+ *type = f.type;
+
+ return status;
+
+ } else {
+ unsigned samples_req;
+
+ /* Initialize frame type */
+ if (cport->rx_buf_count == 0) {
+ *type = PJMEDIA_FRAME_TYPE_NONE;
+ } else {
+ /* we got some samples in the buffer */
+ *type = PJMEDIA_FRAME_TYPE_AUDIO;
+ }
+
+ /*
+ * If we don't have enough samples in rx_buf, read from the port
+ * first. Remember that rx_buf may be in different clock rate and
+ * channel count!
+ */
+
+ samples_req = (unsigned) (count * 1.0 *
+ cport->clock_rate / conf->clock_rate + 0.5);
+
+ while (cport->rx_buf_count < samples_req) {
+
+ pjmedia_frame f;
+ pj_status_t status;
+
+ f.buf = cport->rx_buf + cport->rx_buf_count;
+ f.size = cport->samples_per_frame * BYTES_PER_SAMPLE;
+
+ TRACE_((THIS_FILE, " get_frame, count=%d",
+ cport->samples_per_frame));
+
+ status = pjmedia_port_get_frame(cport->port, &f);
+
+ if (status != PJ_SUCCESS) {
+ /* Fatal error! */
+ return status;
+ }
+
+ if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) {
+ TRACE_((THIS_FILE, " get_frame returned non-audio"));
+ pjmedia_zero_samples( cport->rx_buf + cport->rx_buf_count,
+ cport->samples_per_frame);
+ } else {
+ /* We've got at least one frame */
+ *type = PJMEDIA_FRAME_TYPE_AUDIO;
+ }
+
+ /* Adjust channels */
+ if (cport->channel_count != conf->channel_count) {
+ if (cport->channel_count == 1) {
+ pjmedia_convert_channel_1ton((pj_int16_t*)f.buf,
+ (const pj_int16_t*)f.buf,
+ conf->channel_count,
+ cport->samples_per_frame,
+ 0);
+ cport->rx_buf_count += (cport->samples_per_frame *
+ conf->channel_count);
+ } else { /* conf->channel_count == 1 */
+ pjmedia_convert_channel_nto1((pj_int16_t*)f.buf,
+ (const pj_int16_t*)f.buf,
+ cport->channel_count,
+ cport->samples_per_frame,
+ PJMEDIA_STEREO_MIX, 0);
+ cport->rx_buf_count += (cport->samples_per_frame /
+ cport->channel_count);
+ }
+ } else {
+ cport->rx_buf_count += cport->samples_per_frame;
+ }
+
+ TRACE_((THIS_FILE, " rx buffer size is now %d",
+ cport->rx_buf_count));
+
+ pj_assert(cport->rx_buf_count <= cport->rx_buf_cap);
+ }
+
+ /*
+ * If port's clock_rate is different, resample.
+ * Otherwise just copy.
+ */
+ if (cport->clock_rate != conf->clock_rate) {
+
+ unsigned src_count;
+
+ TRACE_((THIS_FILE, " resample, input count=%d",
+ pjmedia_resample_get_input_size(cport->rx_resample)));
+
+ pjmedia_resample_run( cport->rx_resample,cport->rx_buf, frame);
+
+ src_count = (unsigned)(count * 1.0 * cport->clock_rate /
+ conf->clock_rate + 0.5);
+ cport->rx_buf_count -= src_count;
+ if (cport->rx_buf_count) {
+ pjmedia_move_samples(cport->rx_buf, cport->rx_buf+src_count,
+ cport->rx_buf_count);
+ }
+
+ TRACE_((THIS_FILE, " rx buffer size is now %d",
+ cport->rx_buf_count));
+
+ } else {
+
+ pjmedia_copy_samples(frame, cport->rx_buf, count);
+ cport->rx_buf_count -= count;
+ if (cport->rx_buf_count) {
+ pjmedia_move_samples(cport->rx_buf, cport->rx_buf+count,
+ cport->rx_buf_count);
+ }
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Write the mixed signal to the port.
+ */
+static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport,
+ const pj_timestamp *timestamp,
+ pjmedia_frame_type *frm_type)
+{
+ pj_int16_t *buf;
+ unsigned j, ts;
+ pj_status_t status;
+ pj_int32_t adj_level;
+ pj_int32_t tx_level;
+ unsigned dst_count;
+
+ *frm_type = PJMEDIA_FRAME_TYPE_AUDIO;
+
+ /* If port is muted or nobody is transmitting to this port,
+ * transmit NULL frame.
+ */
+ if (cport->tx_setting == PJMEDIA_PORT_MUTE || cport->transmitter_cnt==0) {
+
+ pjmedia_frame frame;
+
+ /* Clear left-over samples in tx_buffer, if any, so that it won't
+ * be transmitted next time we have audio signal.
+ */
+ cport->tx_buf_count = 0;
+
+ /* Add sample counts to heart-beat samples */
+ cport->tx_heart_beat += conf->samples_per_frame * cport->clock_rate /
+ conf->clock_rate *
+ cport->channel_count / conf->channel_count;
+
+ /* Set frame timestamp */
+ frame.timestamp.u64 = timestamp->u64 * cport->clock_rate /
+ conf->clock_rate;
+ frame.type = PJMEDIA_FRAME_TYPE_NONE;
+ frame.buf = NULL;
+ frame.size = 0;
+
+ /* Transmit heart-beat frames (may transmit more than one NULL frame
+ * if port's ptime is less than bridge's ptime.
+ */
+ if (cport->port && cport->port->put_frame) {
+ while (cport->tx_heart_beat >= cport->samples_per_frame) {
+
+ pjmedia_port_put_frame(cport->port, &frame);
+
+ cport->tx_heart_beat -= cport->samples_per_frame;
+ frame.timestamp.u64 += cport->samples_per_frame;
+ }
+ }
+
+ cport->tx_level = 0;
+ *frm_type = PJMEDIA_FRAME_TYPE_NONE;
+ return PJ_SUCCESS;
+
+ } else if (cport->tx_setting != PJMEDIA_PORT_ENABLE) {
+ cport->tx_level = 0;
+ *frm_type = PJMEDIA_FRAME_TYPE_NONE;
+ return PJ_SUCCESS;
+ }
+
+ /* Reset heart-beat sample count */
+ cport->tx_heart_beat = 0;
+
+ buf = (pj_int16_t*) cport->mix_buf;
+
+ /* If there are sources in the mix buffer, convert the mixed samples
+ * from 32bit to 16bit in the mixed samples itself. This is possible
+ * because mixed sample is 32bit.
+ *
+ * In addition to this process, if we need to change the level of
+ * TX signal, we adjust is here too.
+ */
+
+ /* Calculate signal level and adjust the signal when needed.
+ * Two adjustments performed at once:
+ * 1. user setting adjustment (tx_adj_level).
+ * 2. automatic adjustment of overflowed mixed buffer (mix_adj).
+ */
+
+ /* Apply simple AGC to the mix_adj, the automatic adjust, to avoid
+ * dramatic change in the level thus causing noise because the signal
+ * is now not aligned with the signal from the previous frame.
+ */
+ SIMPLE_AGC(cport->last_mix_adj, cport->mix_adj);
+ cport->last_mix_adj = cport->mix_adj;
+
+ /* adj_level = cport->tx_adj_level * cport->mix_adj / NORMAL_LEVEL;*/
+ adj_level = cport->tx_adj_level * cport->mix_adj;
+ adj_level >>= 7;
+
+ tx_level = 0;
+
+ if (adj_level != NORMAL_LEVEL) {
+ for (j=0; j<conf->samples_per_frame; ++j) {
+ pj_int32_t itemp = cport->mix_buf[j];
+
+ /* Adjust the level */
+ /*itemp = itemp * adj_level / NORMAL_LEVEL;*/
+ itemp = (itemp * adj_level) >> 7;
+
+ /* Clip the signal if it's too loud */
+ if (itemp > MAX_LEVEL) itemp = MAX_LEVEL;
+ else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL;
+
+ /* Put back in the buffer. */
+ buf[j] = (pj_int16_t) itemp;
+
+ tx_level += (buf[j]>=0? buf[j] : -buf[j]);
+ }
+ } else {
+ for (j=0; j<conf->samples_per_frame; ++j) {
+ buf[j] = (pj_int16_t) cport->mix_buf[j];
+ tx_level += (buf[j]>=0? buf[j] : -buf[j]);
+ }
+ }
+
+ tx_level /= conf->samples_per_frame;
+
+ /* Convert level to 8bit complement ulaw */
+ tx_level = pjmedia_linear2ulaw(tx_level) ^ 0xff;
+
+ cport->tx_level = tx_level;
+
+ /* If port has the same clock_rate and samples_per_frame and
+ * number of channels as the conference bridge, transmit the
+ * frame as is.
+ */
+ if (cport->clock_rate == conf->clock_rate &&
+ cport->samples_per_frame == conf->samples_per_frame &&
+ cport->channel_count == conf->channel_count)
+ {
+ if (cport->port != NULL) {
+ pjmedia_frame frame;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = buf;
+ frame.size = conf->samples_per_frame * BYTES_PER_SAMPLE;
+ /* No need to adjust timestamp, port has the same
+ * clock rate as conference bridge
+ */
+ frame.timestamp = *timestamp;
+
+ TRACE_((THIS_FILE, "put_frame %.*s, count=%d",
+ (int)cport->name.slen, cport->name.ptr,
+ frame.size / BYTES_PER_SAMPLE));
+
+ return pjmedia_port_put_frame(cport->port, &frame);
+ } else
+ return PJ_SUCCESS;
+ }
+
+ /* If it has different clock_rate, must resample. */
+ if (cport->clock_rate != conf->clock_rate) {
+ pjmedia_resample_run( cport->tx_resample, buf,
+ cport->tx_buf + cport->tx_buf_count );
+ dst_count = (unsigned)(conf->samples_per_frame * 1.0 *
+ cport->clock_rate / conf->clock_rate + 0.5);
+ } else {
+ /* Same clock rate.
+ * Just copy the samples to tx_buffer.
+ */
+ pjmedia_copy_samples( cport->tx_buf + cport->tx_buf_count,
+ buf, conf->samples_per_frame );
+ dst_count = conf->samples_per_frame;
+ }
+
+ /* Adjust channels */
+ if (cport->channel_count != conf->channel_count) {
+ pj_int16_t *tx_buf = cport->tx_buf + cport->tx_buf_count;
+ if (conf->channel_count == 1) {
+ pjmedia_convert_channel_1ton(tx_buf, tx_buf,
+ cport->channel_count,
+ dst_count, 0);
+ dst_count *= cport->channel_count;
+ } else { /* cport->channel_count == 1 */
+ pjmedia_convert_channel_nto1(tx_buf, tx_buf,
+ conf->channel_count,
+ dst_count, PJMEDIA_STEREO_MIX, 0);
+ dst_count /= conf->channel_count;
+ }
+ }
+
+ cport->tx_buf_count += dst_count;
+
+ pj_assert(cport->tx_buf_count <= cport->tx_buf_cap);
+
+ /* Transmit while we have enough frame in the tx_buf. */
+ status = PJ_SUCCESS;
+ ts = 0;
+ while (cport->tx_buf_count >= cport->samples_per_frame &&
+ status == PJ_SUCCESS)
+ {
+
+ TRACE_((THIS_FILE, "write_port %.*s: count=%d",
+ (int)cport->name.slen, cport->name.ptr,
+ cport->samples_per_frame));
+
+ if (cport->port) {
+ pjmedia_frame frame;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = cport->tx_buf;
+ frame.size = cport->samples_per_frame * BYTES_PER_SAMPLE;
+ /* Adjust timestamp as port may have different clock rate
+ * than the bridge.
+ */
+ frame.timestamp.u64 = timestamp->u64 * cport->clock_rate /
+ conf->clock_rate;
+
+ /* Add timestamp for individual frame */
+ frame.timestamp.u64 += ts;
+ ts += cport->samples_per_frame;
+
+ TRACE_((THIS_FILE, "put_frame %.*s, count=%d",
+ (int)cport->name.slen, cport->name.ptr,
+ frame.size / BYTES_PER_SAMPLE));
+
+ status = pjmedia_port_put_frame(cport->port, &frame);
+
+ } else
+ status = PJ_SUCCESS;
+
+ cport->tx_buf_count -= cport->samples_per_frame;
+ if (cport->tx_buf_count) {
+ pjmedia_move_samples(cport->tx_buf,
+ cport->tx_buf + cport->samples_per_frame,
+ cport->tx_buf_count);
+ }
+
+ TRACE_((THIS_FILE, " tx_buf count now is %d",
+ cport->tx_buf_count));
+ }
+
+ return status;
+}
+
+
+/*
+ * Player callback.
+ */
+static pj_status_t get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata;
+ pjmedia_frame_type speaker_frame_type = PJMEDIA_FRAME_TYPE_NONE;
+ unsigned ci, cj, i, j;
+ pj_int16_t *p_in;
+
+ TRACE_((THIS_FILE, "- clock -"));
+
+ /* Check that correct size is specified. */
+ pj_assert(frame->size == conf->samples_per_frame *
+ conf->bits_per_sample / 8);
+
+ /* Must lock mutex */
+ pj_mutex_lock(conf->mutex);
+
+ /* Reset port source count. We will only reset port's mix
+ * buffer when we have someone transmitting to it.
+ */
+ for (i=0, ci=0; i<conf->max_ports && ci < conf->port_cnt; ++i) {
+ struct conf_port *conf_port = conf->ports[i];
+
+ /* Skip empty port. */
+ if (!conf_port)
+ continue;
+
+ /* Var "ci" is to count how many ports have been visited so far. */
+ ++ci;
+
+ /* Reset buffer (only necessary if more than one transmitter) and
+ * reset auto adjustment level for mixed signal.
+ */
+ conf_port->mix_adj = NORMAL_LEVEL;
+ if (conf_port->transmitter_cnt > 1) {
+ pj_bzero(conf_port->mix_buf,
+ conf->samples_per_frame*sizeof(conf_port->mix_buf[0]));
+ }
+ }
+
+ /* Get frames from all ports, and "mix" the signal
+ * to mix_buf of all listeners of the port.
+ */
+ for (i=0, ci=0; i < conf->max_ports && ci < conf->port_cnt; ++i) {
+ struct conf_port *conf_port = conf->ports[i];
+ pj_int32_t level = 0;
+
+ /* Skip empty port. */
+ if (!conf_port)
+ continue;
+
+ /* Var "ci" is to count how many ports have been visited so far. */
+ ++ci;
+
+ /* Skip if we're not allowed to receive from this port. */
+ if (conf_port->rx_setting == PJMEDIA_PORT_DISABLE) {
+ conf_port->rx_level = 0;
+ continue;
+ }
+
+ /* Also skip if this port doesn't have listeners. */
+ if (conf_port->listener_cnt == 0) {
+ conf_port->rx_level = 0;
+ continue;
+ }
+
+ /* Get frame from this port.
+ * For passive ports, get the frame from the delay_buf.
+ * For other ports, get the frame from the port.
+ */
+ if (conf_port->delay_buf != NULL) {
+ pj_status_t status;
+
+ status = pjmedia_delay_buf_get(conf_port->delay_buf,
+ (pj_int16_t*)frame->buf);
+ if (status != PJ_SUCCESS)
+ continue;
+
+ } else {
+
+ pj_status_t status;
+ pjmedia_frame_type frame_type;
+
+ status = read_port(conf, conf_port, (pj_int16_t*)frame->buf,
+ conf->samples_per_frame, &frame_type);
+
+ if (status != PJ_SUCCESS) {
+ /* bennylp: why do we need this????
+ * Also see comments on similar issue with write_port().
+ PJ_LOG(4,(THIS_FILE, "Port %.*s get_frame() returned %d. "
+ "Port is now disabled",
+ (int)conf_port->name.slen,
+ conf_port->name.ptr,
+ status));
+ conf_port->rx_setting = PJMEDIA_PORT_DISABLE;
+ */
+ continue;
+ }
+
+ /* Check that the port is not removed when we call get_frame() */
+ if (conf->ports[i] == NULL)
+ continue;
+
+ /* Ignore if we didn't get any frame */
+ if (frame_type != PJMEDIA_FRAME_TYPE_AUDIO)
+ continue;
+ }
+
+ p_in = (pj_int16_t*) frame->buf;
+
+ /* Adjust the RX level from this port
+ * and calculate the average level at the same time.
+ */
+ if (conf_port->rx_adj_level != NORMAL_LEVEL) {
+ for (j=0; j<conf->samples_per_frame; ++j) {
+ /* For the level adjustment, we need to store the sample to
+ * a temporary 32bit integer value to avoid overflowing the
+ * 16bit sample storage.
+ */
+ pj_int32_t itemp;
+
+ itemp = p_in[j];
+ /*itemp = itemp * adj / NORMAL_LEVEL;*/
+ /* bad code (signed/unsigned badness):
+ * itemp = (itemp * conf_port->rx_adj_level) >> 7;
+ */
+ itemp *= conf_port->rx_adj_level;
+ itemp >>= 7;
+
+ /* Clip the signal if it's too loud */
+ if (itemp > MAX_LEVEL) itemp = MAX_LEVEL;
+ else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL;
+
+ p_in[j] = (pj_int16_t) itemp;
+ level += (p_in[j]>=0? p_in[j] : -p_in[j]);
+ }
+ } else {
+ for (j=0; j<conf->samples_per_frame; ++j) {
+ level += (p_in[j]>=0? p_in[j] : -p_in[j]);
+ }
+ }
+
+ level /= conf->samples_per_frame;
+
+ /* Convert level to 8bit complement ulaw */
+ level = pjmedia_linear2ulaw(level) ^ 0xff;
+
+ /* Put this level to port's last RX level. */
+ conf_port->rx_level = level;
+
+ // Ticket #671: Skipping very low audio signal may cause noise
+ // to be generated in the remote end by some hardphones.
+ /* Skip processing frame if level is zero */
+ //if (level == 0)
+ // continue;
+
+ /* Add the signal to all listeners. */
+ for (cj=0; cj < conf_port->listener_cnt; ++cj)
+ {
+ struct conf_port *listener;
+ pj_int32_t *mix_buf;
+ unsigned k;
+
+ listener = conf->ports[conf_port->listener_slots[cj]];
+
+ /* Skip if this listener doesn't want to receive audio */
+ if (listener->tx_setting != PJMEDIA_PORT_ENABLE)
+ continue;
+
+ mix_buf = listener->mix_buf;
+
+ if (listener->transmitter_cnt > 1) {
+ /* Mixing signals,
+ * and calculate appropriate level adjustment if there is
+ * any overflowed level in the mixed signal.
+ */
+ for (k=0; k < conf->samples_per_frame; ++k) {
+ mix_buf[k] += p_in[k];
+ /* Check if normalization adjustment needed. */
+ if (IS_OVERFLOW(mix_buf[k])) {
+ /* NORMAL_LEVEL * MAX_LEVEL / mix_buf[k]; */
+ int tmp_adj = (MAX_LEVEL<<7) / mix_buf[k];
+ if (tmp_adj<0) tmp_adj = -tmp_adj;
+
+ if (tmp_adj<listener->mix_adj)
+ listener->mix_adj = tmp_adj;
+
+ } /* if any overflow in the mixed signals */
+ } /* loop mixing signals */
+ } else {
+ /* Only 1 transmitter:
+ * just copy the samples to the mix buffer
+ * no mixing and level adjustment needed
+ */
+ for (k=0; k<conf->samples_per_frame; ++k) {
+ mix_buf[k] = p_in[k];
+ }
+ }
+ } /* loop the listeners of conf port */
+ } /* loop of all conf ports */
+
+ /* Time for all ports to transmit whetever they have in their
+ * buffer.
+ */
+ for (i=0, ci=0; i<conf->max_ports && ci<conf->port_cnt; ++i) {
+ struct conf_port *conf_port = conf->ports[i];
+ pjmedia_frame_type frm_type;
+ pj_status_t status;
+
+ if (!conf_port)
+ continue;
+
+ /* Var "ci" is to count how many ports have been visited. */
+ ++ci;
+
+ status = write_port( conf, conf_port, &frame->timestamp,
+ &frm_type);
+ if (status != PJ_SUCCESS) {
+ /* bennylp: why do we need this????
+ One thing for sure, put_frame()/write_port() may return
+ non-successfull status on Win32 if there's temporary glitch
+ on network interface, so disabling the port here does not
+ sound like a good idea.
+
+ PJ_LOG(4,(THIS_FILE, "Port %.*s put_frame() returned %d. "
+ "Port is now disabled",
+ (int)conf_port->name.slen,
+ conf_port->name.ptr,
+ status));
+ conf_port->tx_setting = PJMEDIA_PORT_DISABLE;
+ */
+ continue;
+ }
+
+ /* Set the type of frame to be returned to sound playback
+ * device.
+ */
+ if (i == 0)
+ speaker_frame_type = frm_type;
+ }
+
+ /* Return sound playback frame. */
+ if (conf->ports[0]->tx_level) {
+ TRACE_((THIS_FILE, "write to audio, count=%d",
+ conf->samples_per_frame));
+ pjmedia_copy_samples( (pj_int16_t*)frame->buf,
+ (const pj_int16_t*)conf->ports[0]->mix_buf,
+ conf->samples_per_frame);
+ } else {
+ /* Force frame type NONE */
+ speaker_frame_type = PJMEDIA_FRAME_TYPE_NONE;
+ }
+
+ /* MUST set frame type */
+ frame->type = speaker_frame_type;
+
+ pj_mutex_unlock(conf->mutex);
+
+#ifdef REC_FILE
+ if (fhnd_rec == NULL)
+ fhnd_rec = fopen(REC_FILE, "wb");
+ if (fhnd_rec)
+ fwrite(frame->buf, frame->size, 1, fhnd_rec);
+#endif
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * get_frame() for passive port
+ */
+static pj_status_t get_frame_pasv(pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ pj_assert(0);
+ PJ_UNUSED_ARG(this_port);
+ PJ_UNUSED_ARG(frame);
+ return -1;
+}
+
+
+/*
+ * Recorder (or passive port) callback.
+ */
+static pj_status_t put_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata;
+ struct conf_port *port = conf->ports[this_port->port_data.ldata];
+ pj_status_t status;
+
+ /* Check for correct size. */
+ PJ_ASSERT_RETURN( frame->size == conf->samples_per_frame *
+ conf->bits_per_sample / 8,
+ PJMEDIA_ENCSAMPLESPFRAME);
+
+ /* Check existance of delay_buf instance */
+ PJ_ASSERT_RETURN( port->delay_buf, PJ_EBUG );
+
+ /* Skip if this port is muted/disabled. */
+ if (port->rx_setting != PJMEDIA_PORT_ENABLE) {
+ return PJ_SUCCESS;
+ }
+
+ /* Skip if no port is listening to the microphone */
+ if (port->listener_cnt == 0) {
+ return PJ_SUCCESS;
+ }
+
+ status = pjmedia_delay_buf_put(port->delay_buf, (pj_int16_t*)frame->buf);
+
+ return status;
+}
+
+#endif
diff --git a/pjmedia/src/pjmedia/converter.c b/pjmedia/src/pjmedia/converter.c
new file mode 100644
index 0000000..50fbcc8
--- /dev/null
+++ b/pjmedia/src/pjmedia/converter.c
@@ -0,0 +1,178 @@
+/* $Id: converter.c 4087 2012-04-26 03:39:24Z ming $ */
+/*
+ * Copyright (C) 2010-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia/converter.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+
+#define THIS_FILE "converter.c"
+
+struct pjmedia_converter_mgr
+{
+ pjmedia_converter_factory factory_list;
+};
+
+static pjmedia_converter_mgr *converter_manager_instance;
+
+#if PJMEDIA_HAS_LIBSWSCALE && PJMEDIA_HAS_LIBAVUTIL
+PJ_DECL(pj_status_t)
+pjmedia_libswscale_converter_init(pjmedia_converter_mgr *mgr);
+#endif
+
+
+PJ_DEF(pj_status_t) pjmedia_converter_mgr_create(pj_pool_t *pool,
+ pjmedia_converter_mgr **p_mgr)
+{
+ pjmedia_converter_mgr *mgr;
+ pj_status_t status = PJ_SUCCESS;
+
+ mgr = PJ_POOL_ALLOC_T(pool, pjmedia_converter_mgr);
+ pj_list_init(&mgr->factory_list);
+
+ if (!converter_manager_instance)
+ converter_manager_instance = mgr;
+
+#if PJMEDIA_HAS_LIBSWSCALE && PJMEDIA_HAS_LIBAVUTIL
+ status = pjmedia_libswscale_converter_init(mgr);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4,(THIS_FILE, status,
+ "Error initializing libswscale converter"));
+ }
+#endif
+
+ if (p_mgr)
+ *p_mgr = mgr;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pjmedia_converter_mgr*) pjmedia_converter_mgr_instance(void)
+{
+ pj_assert(converter_manager_instance != NULL);
+ return converter_manager_instance;
+}
+
+PJ_DEF(void) pjmedia_converter_mgr_set_instance(pjmedia_converter_mgr *mgr)
+{
+ converter_manager_instance = mgr;
+}
+
+PJ_DEF(void) pjmedia_converter_mgr_destroy(pjmedia_converter_mgr *mgr)
+{
+ pjmedia_converter_factory *f;
+
+ if (!mgr) mgr = pjmedia_converter_mgr_instance();
+
+ PJ_ASSERT_ON_FAIL(mgr != NULL, return);
+
+ f = mgr->factory_list.next;
+ while (f != &mgr->factory_list) {
+ pjmedia_converter_factory *next = f->next;
+ pj_list_erase(f);
+ (*f->op->destroy_factory)(f);
+ f = next;
+ }
+
+ if (converter_manager_instance == mgr)
+ converter_manager_instance = NULL;
+}
+
+PJ_DEF(pj_status_t)
+pjmedia_converter_mgr_register_factory(pjmedia_converter_mgr *mgr,
+ pjmedia_converter_factory *factory)
+{
+ pjmedia_converter_factory *pf;
+
+ if (!mgr) mgr = pjmedia_converter_mgr_instance();
+
+ PJ_ASSERT_RETURN(mgr != NULL, PJ_EINVAL);
+
+ PJ_ASSERT_RETURN(!pj_list_find_node(&mgr->factory_list, factory),
+ PJ_EEXISTS);
+
+ pf = mgr->factory_list.next;
+ while (pf != &mgr->factory_list) {
+ if (pf->priority > factory->priority)
+ break;
+ pf = pf->next;
+ }
+ pj_list_insert_before(pf, factory);
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t)
+pjmedia_converter_mgr_unregister_factory(pjmedia_converter_mgr *mgr,
+ pjmedia_converter_factory *f,
+ pj_bool_t destroy)
+{
+ if (!mgr) mgr = pjmedia_converter_mgr_instance();
+
+ PJ_ASSERT_RETURN(mgr != NULL, PJ_EINVAL);
+
+ PJ_ASSERT_RETURN(pj_list_find_node(&mgr->factory_list, f), PJ_ENOTFOUND);
+ pj_list_erase(f);
+ if (destroy)
+ (*f->op->destroy_factory)(f);
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_converter_create(pjmedia_converter_mgr *mgr,
+ pj_pool_t *pool,
+ pjmedia_conversion_param *param,
+ pjmedia_converter **p_cv)
+{
+ pjmedia_converter_factory *f;
+ pjmedia_converter *cv = NULL;
+ pj_status_t status = PJ_ENOTFOUND;
+
+ if (!mgr) mgr = pjmedia_converter_mgr_instance();
+
+ PJ_ASSERT_RETURN(mgr != NULL, PJ_EINVAL);
+
+ *p_cv = NULL;
+
+ f = mgr->factory_list.next;
+ while (f != &mgr->factory_list) {
+ status = (*f->op->create_converter)(f, pool, param, &cv);
+ if (status == PJ_SUCCESS)
+ break;
+ f = f->next;
+ }
+
+ if (status != PJ_SUCCESS)
+ return status;
+
+ *p_cv = cv;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_converter_convert(pjmedia_converter *cv,
+ pjmedia_frame *src_frame,
+ pjmedia_frame *dst_frame)
+{
+ return (*cv->op->convert)(cv, src_frame, dst_frame);
+}
+
+PJ_DEF(void) pjmedia_converter_destroy(pjmedia_converter *cv)
+{
+ (*cv->op->destroy)(cv);
+}
+
+
diff --git a/pjmedia/src/pjmedia/converter_libswscale.c b/pjmedia/src/pjmedia/converter_libswscale.c
new file mode 100644
index 0000000..3bb1c6a
--- /dev/null
+++ b/pjmedia/src/pjmedia/converter_libswscale.c
@@ -0,0 +1,208 @@
+/* $Id: converter_libswscale.c 4076 2012-04-24 09:40:35Z bennylp $ */
+/*
+ * Copyright (C) 2010-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia/converter.h>
+#include <pj/errno.h>
+
+#if PJMEDIA_HAS_LIBSWSCALE && PJMEDIA_HAS_LIBAVUTIL
+
+#include "ffmpeg_util.h"
+#include <libswscale/swscale.h>
+
+static pj_status_t factory_create_converter(pjmedia_converter_factory *cf,
+ pj_pool_t *pool,
+ const pjmedia_conversion_param*prm,
+ pjmedia_converter **p_cv);
+static void factory_destroy_factory(pjmedia_converter_factory *cf);
+static pj_status_t libswscale_conv_convert(pjmedia_converter *converter,
+ pjmedia_frame *src_frame,
+ pjmedia_frame *dst_frame);
+static void libswscale_conv_destroy(pjmedia_converter *converter);
+
+
+struct fmt_info
+{
+ const pjmedia_video_format_info *fmt_info;
+ pjmedia_video_apply_fmt_param apply_param;
+};
+
+struct ffmpeg_converter
+{
+ pjmedia_converter base;
+ struct SwsContext *sws_ctx;
+ struct fmt_info src,
+ dst;
+};
+
+static pjmedia_converter_factory_op libswscale_factory_op =
+{
+ &factory_create_converter,
+ &factory_destroy_factory
+};
+
+static pjmedia_converter_op liswscale_converter_op =
+{
+ &libswscale_conv_convert,
+ &libswscale_conv_destroy
+};
+
+static pj_status_t factory_create_converter(pjmedia_converter_factory *cf,
+ pj_pool_t *pool,
+ const pjmedia_conversion_param *prm,
+ pjmedia_converter **p_cv)
+{
+ enum PixelFormat srcFormat, dstFormat;
+ const pjmedia_video_format_detail *src_detail, *dst_detail;
+ const pjmedia_video_format_info *src_fmt_info, *dst_fmt_info;
+ struct SwsContext *sws_ctx;
+ struct ffmpeg_converter *fcv;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(cf);
+
+ /* Only supports video */
+ if (prm->src.type != PJMEDIA_TYPE_VIDEO ||
+ prm->dst.type != prm->src.type ||
+ prm->src.detail_type != PJMEDIA_FORMAT_DETAIL_VIDEO ||
+ prm->dst.detail_type != prm->src.detail_type)
+ {
+ return PJ_ENOTSUP;
+ }
+
+ /* lookup source format info */
+ src_fmt_info = pjmedia_get_video_format_info(
+ pjmedia_video_format_mgr_instance(),
+ prm->src.id);
+ if (!src_fmt_info)
+ return PJ_ENOTSUP;
+
+ /* lookup destination format info */
+ dst_fmt_info = pjmedia_get_video_format_info(
+ pjmedia_video_format_mgr_instance(),
+ prm->dst.id);
+ if (!dst_fmt_info)
+ return PJ_ENOTSUP;
+
+ src_detail = pjmedia_format_get_video_format_detail(&prm->src, PJ_TRUE);
+ dst_detail = pjmedia_format_get_video_format_detail(&prm->dst, PJ_TRUE);
+
+ status = pjmedia_format_id_to_PixelFormat(prm->src.id, &srcFormat);
+ if (status != PJ_SUCCESS)
+ return PJ_ENOTSUP;
+
+ status = pjmedia_format_id_to_PixelFormat(prm->dst.id, &dstFormat);
+ if (status != PJ_SUCCESS)
+ return PJ_ENOTSUP;
+
+ sws_ctx = sws_getContext(src_detail->size.w, src_detail->size.h, srcFormat,
+ dst_detail->size.w, dst_detail->size.h, dstFormat,
+ SWS_BICUBIC,
+ NULL, NULL, NULL);
+ if (sws_ctx == NULL)
+ return PJ_ENOTSUP;
+
+ fcv = PJ_POOL_ZALLOC_T(pool, struct ffmpeg_converter);
+ fcv->base.op = &liswscale_converter_op;
+ fcv->sws_ctx = sws_ctx;
+ fcv->src.apply_param.size = src_detail->size;
+ fcv->src.fmt_info = src_fmt_info;
+ fcv->dst.apply_param.size = dst_detail->size;
+ fcv->dst.fmt_info = dst_fmt_info;
+
+ *p_cv = &fcv->base;
+
+ return PJ_SUCCESS;
+}
+
+static void factory_destroy_factory(pjmedia_converter_factory *cf)
+{
+ PJ_UNUSED_ARG(cf);
+}
+
+static pj_status_t libswscale_conv_convert(pjmedia_converter *converter,
+ pjmedia_frame *src_frame,
+ pjmedia_frame *dst_frame)
+{
+ struct ffmpeg_converter *fcv = (struct ffmpeg_converter*)converter;
+ struct fmt_info *src = &fcv->src,
+ *dst = &fcv->dst;
+ int h;
+
+ src->apply_param.buffer = src_frame->buf;
+ (*src->fmt_info->apply_fmt)(src->fmt_info, &src->apply_param);
+
+ dst->apply_param.buffer = dst_frame->buf;
+ (*dst->fmt_info->apply_fmt)(dst->fmt_info, &dst->apply_param);
+
+ h = sws_scale(fcv->sws_ctx,
+ (const uint8_t* const *)src->apply_param.planes,
+ src->apply_param.strides,
+ 0, src->apply_param.size.h,
+ dst->apply_param.planes, dst->apply_param.strides);
+
+ //sws_scale() return value can't be trusted? There are cases when
+ //sws_scale() returns zero but conversion seems to work okay.
+ //return h==(int)dst->apply_param.size.h ? PJ_SUCCESS : PJ_EUNKNOWN;
+ PJ_UNUSED_ARG(h);
+
+ return PJ_SUCCESS;
+}
+
+static void libswscale_conv_destroy(pjmedia_converter *converter)
+{
+ struct ffmpeg_converter *fcv = (struct ffmpeg_converter*)converter;
+ if (fcv->sws_ctx) {
+ struct SwsContext *tmp = fcv->sws_ctx;
+ fcv->sws_ctx = NULL;
+ sws_freeContext(tmp);
+ }
+}
+
+static pjmedia_converter_factory libswscale_factory =
+{
+ NULL, NULL, /* list */
+ "libswscale", /* name */
+ PJMEDIA_CONVERTER_PRIORITY_NORMAL+1, /* priority */
+ NULL /* op will be init-ed later */
+};
+
+PJ_DEF(pj_status_t)
+pjmedia_libswscale_converter_init(pjmedia_converter_mgr *mgr)
+{
+ libswscale_factory.op = &libswscale_factory_op;
+ pjmedia_ffmpeg_add_ref();
+ return pjmedia_converter_mgr_register_factory(mgr, &libswscale_factory);
+}
+
+
+PJ_DEF(pj_status_t)
+pjmedia_libswscale_converter_shutdown(pjmedia_converter_mgr *mgr,
+ pj_pool_t *pool)
+{
+ PJ_UNUSED_ARG(pool);
+ pjmedia_ffmpeg_dec_ref();
+ return pjmedia_converter_mgr_unregister_factory(mgr, &libswscale_factory,
+ PJ_TRUE);
+}
+
+#ifdef _MSC_VER
+# pragma comment( lib, "avutil.lib")
+# pragma comment( lib, "swscale.lib")
+#endif
+
+#endif /* #if PJMEDIA_HAS_LIBSWSCALE && PJMEDIA_HAS_LIBAVUTIL */
diff --git a/pjmedia/src/pjmedia/delaybuf.c b/pjmedia/src/pjmedia/delaybuf.c
new file mode 100644
index 0000000..5acacaa
--- /dev/null
+++ b/pjmedia/src/pjmedia/delaybuf.c
@@ -0,0 +1,405 @@
+/* $Id: delaybuf.c 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/delaybuf.h>
+#include <pjmedia/circbuf.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/frame.h>
+#include <pjmedia/wsola.h>
+#include <pj/assert.h>
+#include <pj/lock.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/pool.h>
+
+
+#if 0
+# define TRACE__(x) PJ_LOG(3,x)
+#else
+# define TRACE__(x)
+#endif
+
+/* Operation types of delay buffer */
+enum OP
+{
+ OP_PUT,
+ OP_GET
+};
+
+/* Specify time for delaybuf to recalculate effective delay, in ms.
+ */
+#define RECALC_TIME 2000
+
+/* Default value of maximum delay, in ms, this value is used when
+ * maximum delay requested is less than ptime (one frame length).
+ */
+#define DEFAULT_MAX_DELAY 400
+
+/* Number of frames to add to learnt level for additional stability.
+ */
+#define SAFE_MARGIN 0
+
+/* This structure describes internal delaybuf settings and states.
+ */
+struct pjmedia_delay_buf
+{
+ /* Properties and configuration */
+ char obj_name[PJ_MAX_OBJ_NAME];
+ pj_lock_t *lock; /**< Lock object. */
+ unsigned samples_per_frame; /**< Number of samples in one frame */
+ unsigned ptime; /**< Frame time, in ms */
+ unsigned channel_count; /**< Channel count, in ms */
+ pjmedia_circ_buf *circ_buf; /**< Circular buffer to store audio
+ samples */
+ unsigned max_cnt; /**< Maximum samples to be buffered */
+ unsigned eff_cnt; /**< Effective count of buffered
+ samples to keep the optimum
+ balance between delay and
+ stability. This is calculated
+ based on burst level. */
+
+ /* Learning vars */
+ unsigned level; /**< Burst level counter */
+ enum OP last_op; /**< Last op (GET or PUT) of learning*/
+ int recalc_timer; /**< Timer for recalculating max_level*/
+ unsigned max_level; /**< Current max burst level */
+
+ /* Drift handler */
+ pjmedia_wsola *wsola; /**< Drift handler */
+};
+
+
+PJ_DEF(pj_status_t) pjmedia_delay_buf_create( pj_pool_t *pool,
+ const char *name,
+ unsigned clock_rate,
+ unsigned samples_per_frame,
+ unsigned channel_count,
+ unsigned max_delay,
+ unsigned options,
+ pjmedia_delay_buf **p_b)
+{
+ pjmedia_delay_buf *b;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pool && samples_per_frame && clock_rate && channel_count &&
+ p_b, PJ_EINVAL);
+
+ if (!name) {
+ name = "delaybuf";
+ }
+
+ b = PJ_POOL_ZALLOC_T(pool, pjmedia_delay_buf);
+
+ pj_ansi_strncpy(b->obj_name, name, PJ_MAX_OBJ_NAME-1);
+
+ b->samples_per_frame = samples_per_frame;
+ b->channel_count = channel_count;
+ b->ptime = samples_per_frame * 1000 / clock_rate / channel_count;
+ if (max_delay < b->ptime)
+ max_delay = PJ_MAX(DEFAULT_MAX_DELAY, b->ptime);
+
+ b->max_cnt = samples_per_frame * max_delay / b->ptime;
+ b->eff_cnt = b->max_cnt >> 1;
+ b->recalc_timer = RECALC_TIME;
+
+ /* Create circular buffer */
+ status = pjmedia_circ_buf_create(pool, b->max_cnt, &b->circ_buf);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (!(options & PJMEDIA_DELAY_BUF_SIMPLE_FIFO)) {
+ /* Create WSOLA */
+ status = pjmedia_wsola_create(pool, clock_rate, samples_per_frame, 1,
+ PJMEDIA_WSOLA_NO_FADING, &b->wsola);
+ if (status != PJ_SUCCESS)
+ return status;
+ PJ_LOG(5, (b->obj_name, "Using delay buffer with WSOLA."));
+ } else {
+ PJ_LOG(5, (b->obj_name, "Using simple FIFO delay buffer."));
+ }
+
+ /* Finally, create mutex */
+ status = pj_lock_create_recursive_mutex(pool, b->obj_name,
+ &b->lock);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ *p_b = b;
+
+ TRACE__((b->obj_name,"Delay buffer created"));
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_delay_buf_destroy(pjmedia_delay_buf *b)
+{
+ pj_status_t status = PJ_SUCCESS;
+
+ PJ_ASSERT_RETURN(b, PJ_EINVAL);
+
+ pj_lock_acquire(b->lock);
+
+ if (b->wsola) {
+ status = pjmedia_wsola_destroy(b->wsola);
+ if (status == PJ_SUCCESS)
+ b->wsola = NULL;
+ }
+
+ pj_lock_release(b->lock);
+
+ pj_lock_destroy(b->lock);
+ b->lock = NULL;
+
+ return status;
+}
+
+/* This function will erase samples from delay buffer.
+ * The number of erased samples is guaranteed to be >= erase_cnt.
+ */
+static void shrink_buffer(pjmedia_delay_buf *b, unsigned erase_cnt)
+{
+ pj_int16_t *buf1, *buf2;
+ unsigned buf1len;
+ unsigned buf2len;
+ pj_status_t status;
+
+ pj_assert(b && erase_cnt && pjmedia_circ_buf_get_len(b->circ_buf));
+
+ pjmedia_circ_buf_get_read_regions(b->circ_buf, &buf1, &buf1len,
+ &buf2, &buf2len);
+ status = pjmedia_wsola_discard(b->wsola, buf1, buf1len, buf2, buf2len,
+ &erase_cnt);
+
+ if ((status == PJ_SUCCESS) && (erase_cnt > 0)) {
+ /* WSOLA discard will manage the first buffer to be full, unless
+ * erase_cnt is greater than second buffer length. So it is safe
+ * to just set the circular buffer length.
+ */
+
+ pjmedia_circ_buf_set_len(b->circ_buf,
+ pjmedia_circ_buf_get_len(b->circ_buf) -
+ erase_cnt);
+
+ PJ_LOG(5,(b->obj_name,"%d samples reduced, buf_cnt=%d",
+ erase_cnt, pjmedia_circ_buf_get_len(b->circ_buf)));
+ }
+}
+
+/* Fast increase, slow decrease */
+#define AGC_UP(cur, target) cur = (cur + target*3) >> 2
+#define AGC_DOWN(cur, target) cur = (cur*3 + target) >> 2
+#define AGC(cur, target) \
+ if (cur < target) AGC_UP(cur, target); \
+ else AGC_DOWN(cur, target)
+
+static void update(pjmedia_delay_buf *b, enum OP op)
+{
+ /* Sequential operation */
+ if (op == b->last_op) {
+ ++b->level;
+ return;
+ }
+
+ /* Switching operation */
+ if (b->level > b->max_level)
+ b->max_level = b->level;
+
+ b->recalc_timer -= (b->level * b->ptime) >> 1;
+
+ b->last_op = op;
+ b->level = 1;
+
+ /* Recalculate effective count based on max_level */
+ if (b->recalc_timer <= 0) {
+ unsigned new_eff_cnt = (b->max_level+SAFE_MARGIN)*b->samples_per_frame;
+
+ /* Smoothening effective count transition */
+ AGC(b->eff_cnt, new_eff_cnt);
+
+ /* Make sure the new effective count is multiplication of
+ * channel_count, so let's round it up.
+ */
+ if (b->eff_cnt % b->channel_count)
+ b->eff_cnt += b->channel_count - (b->eff_cnt % b->channel_count);
+
+ TRACE__((b->obj_name,"Cur eff_cnt=%d", b->eff_cnt));
+
+ b->max_level = 0;
+ b->recalc_timer = RECALC_TIME;
+ }
+
+ /* See if we need to shrink the buffer to reduce delay */
+ if (op == OP_PUT && pjmedia_circ_buf_get_len(b->circ_buf) >
+ b->samples_per_frame + b->eff_cnt)
+ {
+ unsigned erase_cnt = b->samples_per_frame >> 1;
+ unsigned old_buf_cnt = pjmedia_circ_buf_get_len(b->circ_buf);
+
+ shrink_buffer(b, erase_cnt);
+ PJ_LOG(4,(b->obj_name,"Buffer size adjusted from %d to %d (eff_cnt=%d)",
+ old_buf_cnt,
+ pjmedia_circ_buf_get_len(b->circ_buf),
+ b->eff_cnt));
+ }
+}
+
+PJ_DEF(pj_status_t) pjmedia_delay_buf_put(pjmedia_delay_buf *b,
+ pj_int16_t frame[])
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(b && frame, PJ_EINVAL);
+
+ pj_lock_acquire(b->lock);
+
+ if (b->wsola) {
+ update(b, OP_PUT);
+
+ status = pjmedia_wsola_save(b->wsola, frame, PJ_FALSE);
+ if (status != PJ_SUCCESS) {
+ pj_lock_release(b->lock);
+ return status;
+ }
+ }
+
+ /* Overflow checking */
+ if (pjmedia_circ_buf_get_len(b->circ_buf) + b->samples_per_frame >
+ b->max_cnt)
+ {
+ unsigned erase_cnt;
+
+ if (b->wsola) {
+ /* shrink one frame or just the diff? */
+ //erase_cnt = b->samples_per_frame;
+ erase_cnt = pjmedia_circ_buf_get_len(b->circ_buf) +
+ b->samples_per_frame - b->max_cnt;
+
+ shrink_buffer(b, erase_cnt);
+ }
+
+ /* Check if shrinking failed or erased count is less than requested,
+ * delaybuf needs to drop eldest samples, this is bad since the voice
+ * samples get rough transition which may produce tick noise.
+ */
+ if (pjmedia_circ_buf_get_len(b->circ_buf) + b->samples_per_frame >
+ b->max_cnt)
+ {
+ erase_cnt = pjmedia_circ_buf_get_len(b->circ_buf) +
+ b->samples_per_frame - b->max_cnt;
+
+ pjmedia_circ_buf_adv_read_ptr(b->circ_buf, erase_cnt);
+
+ PJ_LOG(4,(b->obj_name,"%sDropping %d eldest samples, buf_cnt=%d",
+ (b->wsola? "Shrinking failed or insufficient. ": ""),
+ erase_cnt, pjmedia_circ_buf_get_len(b->circ_buf)));
+ }
+ }
+
+ pjmedia_circ_buf_write(b->circ_buf, frame, b->samples_per_frame);
+
+ pj_lock_release(b->lock);
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_delay_buf_get( pjmedia_delay_buf *b,
+ pj_int16_t frame[])
+{
+ pj_status_t status = PJ_SUCCESS;
+
+ PJ_ASSERT_RETURN(b && frame, PJ_EINVAL);
+
+ pj_lock_acquire(b->lock);
+
+ if (b->wsola)
+ update(b, OP_GET);
+
+ /* Starvation checking */
+ if (pjmedia_circ_buf_get_len(b->circ_buf) < b->samples_per_frame) {
+
+ PJ_LOG(4,(b->obj_name,"Underflow, buf_cnt=%d, will generate 1 frame",
+ pjmedia_circ_buf_get_len(b->circ_buf)));
+
+ if (b->wsola) {
+ status = pjmedia_wsola_generate(b->wsola, frame);
+
+ if (status == PJ_SUCCESS) {
+ TRACE__((b->obj_name,"Successfully generate 1 frame"));
+ if (pjmedia_circ_buf_get_len(b->circ_buf) == 0) {
+ pj_lock_release(b->lock);
+ return PJ_SUCCESS;
+ }
+
+ /* Put generated frame into buffer */
+ pjmedia_circ_buf_write(b->circ_buf, frame,
+ b->samples_per_frame);
+ }
+ }
+
+ if (!b->wsola || status != PJ_SUCCESS) {
+ unsigned buf_len = pjmedia_circ_buf_get_len(b->circ_buf);
+
+ /* Give all what delay buffer has, then pad with zeroes */
+ if (b->wsola)
+ PJ_LOG(4,(b->obj_name,"Error generating frame, status=%d",
+ status));
+
+ pjmedia_circ_buf_read(b->circ_buf, frame, buf_len);
+ pjmedia_zero_samples(&frame[buf_len],
+ b->samples_per_frame - buf_len);
+
+ /* The buffer is empty now, reset it */
+ pjmedia_circ_buf_reset(b->circ_buf);
+
+ pj_lock_release(b->lock);
+
+ return PJ_SUCCESS;
+ }
+ }
+
+ pjmedia_circ_buf_read(b->circ_buf, frame, b->samples_per_frame);
+
+ pj_lock_release(b->lock);
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_delay_buf_reset(pjmedia_delay_buf *b)
+{
+ PJ_ASSERT_RETURN(b, PJ_EINVAL);
+
+ pj_lock_acquire(b->lock);
+
+ b->recalc_timer = RECALC_TIME;
+
+ /* Reset buffer */
+ pjmedia_circ_buf_reset(b->circ_buf);
+
+ /* Reset WSOLA */
+ if (b->wsola)
+ pjmedia_wsola_reset(b->wsola, 0);
+
+ pj_lock_release(b->lock);
+
+ PJ_LOG(5,(b->obj_name,"Delay buffer is reset"));
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjmedia/src/pjmedia/dummy.c b/pjmedia/src/pjmedia/dummy.c
new file mode 100644
index 0000000..b98e5b3
--- /dev/null
+++ b/pjmedia/src/pjmedia/dummy.c
@@ -0,0 +1,24 @@
+/* $Id: dummy.c 3715 2011-08-19 09:35:25Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/frame.h>
+#include <pjmedia/port.h>
+#include <pjmedia/types.h>
+
diff --git a/pjmedia/src/pjmedia/echo_common.c b/pjmedia/src/pjmedia/echo_common.c
new file mode 100644
index 0000000..4e202c0
--- /dev/null
+++ b/pjmedia/src/pjmedia/echo_common.c
@@ -0,0 +1,375 @@
+/* $Id: echo_common.c 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/echo.h>
+#include <pjmedia/delaybuf.h>
+#include <pjmedia/frame.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/list.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/pool.h>
+#include "echo_internal.h"
+
+#define THIS_FILE "echo_common.c"
+
+typedef struct ec_operations ec_operations;
+
+struct frame
+{
+ PJ_DECL_LIST_MEMBER(struct frame);
+ short buf[1];
+};
+
+struct pjmedia_echo_state
+{
+ pj_pool_t *pool;
+ char *obj_name;
+ unsigned samples_per_frame;
+ void *state;
+ ec_operations *op;
+
+ pj_bool_t lat_ready; /* lat_buf has been filled in. */
+ struct frame lat_buf; /* Frame queue for delayed playback */
+ struct frame lat_free; /* Free frame list. */
+
+ pjmedia_delay_buf *delay_buf;
+ pj_int16_t *frm_buf;
+};
+
+
+struct ec_operations
+{
+ const char *name;
+
+ pj_status_t (*ec_create)(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned tail_ms,
+ unsigned options,
+ void **p_state );
+ pj_status_t (*ec_destroy)(void *state );
+ void (*ec_reset)(void *state );
+ pj_status_t (*ec_cancel)(void *state,
+ pj_int16_t *rec_frm,
+ const pj_int16_t *play_frm,
+ unsigned options,
+ void *reserved );
+};
+
+
+static struct ec_operations echo_supp_op =
+{
+ "Echo suppressor",
+ &echo_supp_create,
+ &echo_supp_destroy,
+ &echo_supp_reset,
+ &echo_supp_cancel_echo
+};
+
+
+
+/*
+ * Speex AEC prototypes
+ */
+#if defined(PJMEDIA_HAS_SPEEX_AEC) && PJMEDIA_HAS_SPEEX_AEC!=0
+static struct ec_operations speex_aec_op =
+{
+ "AEC",
+ &speex_aec_create,
+ &speex_aec_destroy,
+ &speex_aec_reset,
+ &speex_aec_cancel_echo
+};
+#endif
+
+
+/*
+ * IPP AEC prototypes
+ */
+#if defined(PJMEDIA_HAS_INTEL_IPP_AEC) && PJMEDIA_HAS_INTEL_IPP_AEC!=0
+static struct ec_operations ipp_aec_op =
+{
+ "IPP AEC",
+ &ipp_aec_create,
+ &ipp_aec_destroy,
+ &ipp_aec_reset,
+ &ipp_aec_cancel_echo
+};
+#endif
+
+/*
+ * Create the echo canceller.
+ */
+PJ_DEF(pj_status_t) pjmedia_echo_create( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned samples_per_frame,
+ unsigned tail_ms,
+ unsigned latency_ms,
+ unsigned options,
+ pjmedia_echo_state **p_echo )
+{
+ return pjmedia_echo_create2(pool, clock_rate, 1, samples_per_frame,
+ tail_ms, latency_ms, options, p_echo);
+}
+
+/*
+ * 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;
+ }
+
+ 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;
+ }
+
+ /* 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;
+}
+
+
+/*
+ * Destroy the Echo Canceller.
+ */
+PJ_DEF(pj_status_t) pjmedia_echo_destroy(pjmedia_echo_state *echo )
+{
+ (*echo->op->ec_destroy)(echo->state);
+
+ if (echo->delay_buf) {
+ pjmedia_delay_buf_destroy(echo->delay_buf);
+ echo->delay_buf = NULL;
+ }
+
+ pj_pool_release(echo->pool);
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Reset the echo canceller.
+ */
+PJ_DEF(pj_status_t) pjmedia_echo_reset(pjmedia_echo_state *echo )
+{
+ while (!pj_list_empty(&echo->lat_buf)) {
+ struct frame *frm;
+ frm = echo->lat_buf.next;
+ pj_list_erase(frm);
+ pj_list_push_back(&echo->lat_free, frm);
+ }
+ echo->lat_ready = PJ_FALSE;
+ pjmedia_delay_buf_reset(echo->delay_buf);
+ echo->op->ec_reset(echo->state);
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Let the Echo Canceller know that a frame has been played to the speaker.
+ */
+PJ_DEF(pj_status_t) pjmedia_echo_playback( pjmedia_echo_state *echo,
+ pj_int16_t *play_frm )
+{
+ /* Playing frame should be stored, as it will be used by echo_capture()
+ * as reference frame, delay buffer is used for storing the playing frames
+ * as in case there was clock drift between mic & speaker.
+ *
+ * Ticket #830:
+ * Note that pjmedia_delay_buf_put() may modify the input frame and those
+ * modified frames may not be smooth, i.e: if there were two or more
+ * consecutive pjmedia_delay_buf_get() before next pjmedia_delay_buf_put(),
+ * so we'll just feed the delay buffer with the copy of playing frame,
+ * instead of the original playing frame. However this will cause the EC
+ * uses slight 'different' frames (for reference) than actually played
+ * by the speaker.
+ */
+ pjmedia_copy_samples(echo->frm_buf, play_frm,
+ echo->samples_per_frame);
+ pjmedia_delay_buf_put(echo->delay_buf, echo->frm_buf);
+
+ if (!echo->lat_ready) {
+ /* We've not built enough latency in the buffer, so put this frame
+ * in the latency buffer list.
+ */
+ struct frame *frm;
+
+ if (pj_list_empty(&echo->lat_free)) {
+ echo->lat_ready = PJ_TRUE;
+ PJ_LOG(5,(echo->obj_name, "Latency bufferring complete"));
+ return PJ_SUCCESS;
+ }
+
+ frm = echo->lat_free.prev;
+ pj_list_erase(frm);
+
+ /* Move one frame from delay buffer to the latency buffer. */
+ pjmedia_delay_buf_get(echo->delay_buf, echo->frm_buf);
+ pjmedia_copy_samples(frm->buf, echo->frm_buf, echo->samples_per_frame);
+ pj_list_push_back(&echo->lat_buf, frm);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Let the Echo Canceller knows that a frame has been captured from
+ * the microphone.
+ */
+PJ_DEF(pj_status_t) pjmedia_echo_capture( pjmedia_echo_state *echo,
+ pj_int16_t *rec_frm,
+ unsigned options )
+{
+ struct frame *oldest_frm;
+ pj_status_t status, rc;
+
+ if (!echo->lat_ready) {
+ /* Prefetching to fill in the desired latency */
+ PJ_LOG(5,(echo->obj_name, "Prefetching.."));
+ return PJ_SUCCESS;
+ }
+
+ /* Retrieve oldest frame from the latency buffer */
+ oldest_frm = echo->lat_buf.next;
+ pj_list_erase(oldest_frm);
+
+ /* Cancel echo using this reference frame */
+ status = pjmedia_echo_cancel(echo, rec_frm, oldest_frm->buf,
+ options, NULL);
+
+ /* Move one frame from delay buffer to the latency buffer. */
+ rc = pjmedia_delay_buf_get(echo->delay_buf, oldest_frm->buf);
+ if (rc != PJ_SUCCESS) {
+ /* Ooops.. no frame! */
+ PJ_LOG(5,(echo->obj_name,
+ "No frame from delay buffer. This will upset EC later"));
+ pjmedia_zero_samples(oldest_frm->buf, echo->samples_per_frame);
+ }
+ pj_list_push_back(&echo->lat_buf, oldest_frm);
+
+ return status;
+}
+
+
+/*
+ * Perform echo cancellation.
+ */
+PJ_DEF(pj_status_t) pjmedia_echo_cancel( pjmedia_echo_state *echo,
+ pj_int16_t *rec_frm,
+ const pj_int16_t *play_frm,
+ unsigned options,
+ void *reserved )
+{
+ return (*echo->op->ec_cancel)( echo->state, rec_frm, play_frm, options,
+ reserved);
+}
+
diff --git a/pjmedia/src/pjmedia/echo_internal.h b/pjmedia/src/pjmedia/echo_internal.h
new file mode 100644
index 0000000..0e6599c
--- /dev/null
+++ b/pjmedia/src/pjmedia/echo_internal.h
@@ -0,0 +1,79 @@
+/* $Id: echo_internal.h 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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_ECHO_INTERNAL_H__
+#define __PJMEDIA_ECHO_INTERNAL_H__
+
+#include <pjmedia/types.h>
+
+PJ_BEGIN_DECL
+
+/*
+ * Simple echo suppressor
+ */
+PJ_DECL(pj_status_t) echo_supp_create(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned tail_ms,
+ unsigned options,
+ void **p_state );
+PJ_DECL(pj_status_t) echo_supp_destroy(void *state);
+PJ_DECL(void) echo_supp_reset(void *state);
+PJ_DECL(pj_status_t) echo_supp_cancel_echo(void *state,
+ pj_int16_t *rec_frm,
+ const pj_int16_t *play_frm,
+ unsigned options,
+ void *reserved );
+
+PJ_DECL(pj_status_t) speex_aec_create(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned tail_ms,
+ unsigned options,
+ void **p_state );
+PJ_DECL(pj_status_t) speex_aec_destroy(void *state );
+PJ_DECL(void) speex_aec_reset(void *state );
+PJ_DECL(pj_status_t) speex_aec_cancel_echo(void *state,
+ pj_int16_t *rec_frm,
+ const pj_int16_t *play_frm,
+ unsigned options,
+ void *reserved );
+
+PJ_DECL(pj_status_t) ipp_aec_create(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned tail_ms,
+ unsigned options,
+ void **p_echo );
+PJ_DECL(pj_status_t) ipp_aec_destroy(void *state );
+PJ_DECL(void) ipp_aec_reset(void *state );
+PJ_DECL(pj_status_t) ipp_aec_cancel_echo(void *state,
+ pj_int16_t *rec_frm,
+ const pj_int16_t *play_frm,
+ unsigned options,
+ void *reserved );
+
+
+PJ_END_DECL
+
+#endif
+
diff --git a/pjmedia/src/pjmedia/echo_port.c b/pjmedia/src/pjmedia/echo_port.c
new file mode 100644
index 0000000..7b1eb98
--- /dev/null
+++ b/pjmedia/src/pjmedia/echo_port.c
@@ -0,0 +1,146 @@
+/* $Id: echo_port.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/echo_port.h>
+#include <pjmedia/echo.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+
+
+#define THIS_FILE "ec_port.c"
+#define SIGNATURE PJMEDIA_SIG_PORT_ECHO
+#define BUF_COUNT 32
+
+struct ec
+{
+ pjmedia_port base;
+ pjmedia_port *dn_port;
+ pjmedia_echo_state *ec;
+};
+
+
+static pj_status_t ec_put_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t ec_get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t ec_on_destroy(pjmedia_port *this_port);
+
+
+PJ_DEF(pj_status_t) pjmedia_echo_port_create(pj_pool_t *pool,
+ pjmedia_port *dn_port,
+ unsigned tail_ms,
+ unsigned latency_ms,
+ unsigned options,
+ pjmedia_port **p_port )
+{
+ const pj_str_t AEC = { "EC", 2 };
+ pjmedia_audio_format_detail *afd;
+ struct ec *ec;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pool && dn_port && p_port, PJ_EINVAL);
+
+ afd = pjmedia_format_get_audio_format_detail(&dn_port->info.fmt, PJ_TRUE);
+
+ PJ_ASSERT_RETURN(afd->bits_per_sample==16 && tail_ms,
+ PJ_EINVAL);
+
+ /* Create the port and the AEC itself */
+ ec = PJ_POOL_ZALLOC_T(pool, struct ec);
+
+ pjmedia_port_info_init(&ec->base.info, &AEC, SIGNATURE,
+ afd->clock_rate,
+ afd->channel_count,
+ afd->bits_per_sample,
+ PJMEDIA_AFD_SPF(afd));
+
+ status = pjmedia_echo_create2(pool, afd->clock_rate,
+ afd->channel_count,
+ PJMEDIA_AFD_SPF(afd),
+ tail_ms, latency_ms, options, &ec->ec);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* More init */
+ ec->dn_port = dn_port;
+ ec->base.get_frame = &ec_get_frame;
+ ec->base.put_frame = &ec_put_frame;
+ ec->base.on_destroy = &ec_on_destroy;
+
+ /* Done */
+ *p_port = &ec->base;
+
+ 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);
+}
+
+
+static pj_status_t ec_get_frame( pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ struct ec *ec = (struct ec*)this_port;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVAL);
+
+ status = pjmedia_port_get_frame(ec->dn_port, frame);
+ if (status!=PJ_SUCCESS || frame->type!=PJMEDIA_FRAME_TYPE_AUDIO) {
+ pjmedia_zero_samples((pj_int16_t*)frame->buf,
+ PJMEDIA_PIA_SPF(&this_port->info));
+ }
+
+ pjmedia_echo_playback(ec->ec, (pj_int16_t*)frame->buf);
+
+ return status;
+}
+
+
+static pj_status_t ec_on_destroy(pjmedia_port *this_port)
+{
+ struct ec *ec = (struct ec*)this_port;
+
+ PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVAL);
+
+ pjmedia_echo_destroy(ec->ec);
+
+ return PJ_SUCCESS;
+}
+
+
diff --git a/pjmedia/src/pjmedia/echo_speex.c b/pjmedia/src/pjmedia/echo_speex.c
new file mode 100644
index 0000000..515cec9
--- /dev/null
+++ b/pjmedia/src/pjmedia/echo_speex.c
@@ -0,0 +1,192 @@
+/* $Id: echo_speex.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/echo.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/frame.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+
+#if defined(PJMEDIA_HAS_SPEEX_AEC) && PJMEDIA_HAS_SPEEX_AEC != 0
+
+#include <speex/speex_echo.h>
+#include <speex/speex_preprocess.h>
+
+#include "echo_internal.h"
+
+typedef struct speex_ec
+{
+ SpeexEchoState *state;
+ SpeexPreprocessState *preprocess;
+
+ unsigned samples_per_frame;
+ unsigned prefetch;
+ unsigned options;
+ pj_int16_t *tmp_frame;
+} speex_ec;
+
+
+
+/*
+ * Create the AEC.
+ */
+PJ_DEF(pj_status_t) speex_aec_create(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned tail_ms,
+ unsigned options,
+ void **p_echo )
+{
+ speex_ec *echo;
+ int sampling_rate;
+
+ *p_echo = NULL;
+
+ echo = PJ_POOL_ZALLOC_T(pool, speex_ec);
+ PJ_ASSERT_RETURN(echo != NULL, PJ_ENOMEM);
+
+ echo->samples_per_frame = samples_per_frame;
+ echo->options = options;
+
+#if 0
+ echo->state = speex_echo_state_init_mc(echo->samples_per_frame,
+ clock_rate * tail_ms / 1000,
+ channel_count, channel_count);
+#else
+ if (channel_count != 1) {
+ PJ_LOG(2,("echo_speex.c", "Multichannel EC is not supported by this "
+ "echo canceller. It may not work."));
+ }
+ echo->state = speex_echo_state_init(echo->samples_per_frame,
+ clock_rate * tail_ms / 1000);
+#endif
+ if (echo->state == NULL) {
+ return PJ_ENOMEM;
+ }
+
+ /* Set sampling rate */
+ sampling_rate = clock_rate;
+ speex_echo_ctl(echo->state, SPEEX_ECHO_SET_SAMPLING_RATE,
+ &sampling_rate);
+
+ echo->preprocess = speex_preprocess_state_init(echo->samples_per_frame,
+ clock_rate);
+ if (echo->preprocess == NULL) {
+ speex_echo_state_destroy(echo->state);
+ return PJ_ENOMEM;
+ }
+
+ /* Disable all preprocessing, we only want echo cancellation */
+#if 0
+ disabled = 0;
+ enabled = 1;
+ speex_preprocess_ctl(echo->preprocess, SPEEX_PREPROCESS_SET_DENOISE,
+ &enabled);
+ speex_preprocess_ctl(echo->preprocess, SPEEX_PREPROCESS_SET_AGC,
+ &disabled);
+ speex_preprocess_ctl(echo->preprocess, SPEEX_PREPROCESS_SET_VAD,
+ &disabled);
+ speex_preprocess_ctl(echo->preprocess, SPEEX_PREPROCESS_SET_DEREVERB,
+ &enabled);
+#endif
+
+ /* Control echo cancellation in the preprocessor */
+ speex_preprocess_ctl(echo->preprocess, SPEEX_PREPROCESS_SET_ECHO_STATE,
+ echo->state);
+
+
+ /* Create temporary frame for echo cancellation */
+ echo->tmp_frame = (pj_int16_t*) pj_pool_zalloc(pool, 2*samples_per_frame);
+ PJ_ASSERT_RETURN(echo->tmp_frame != NULL, PJ_ENOMEM);
+
+ /* Done */
+ *p_echo = echo;
+ return PJ_SUCCESS;
+
+}
+
+
+/*
+ * Destroy AEC
+ */
+PJ_DEF(pj_status_t) speex_aec_destroy(void *state )
+{
+ speex_ec *echo = (speex_ec*) state;
+
+ PJ_ASSERT_RETURN(echo && echo->state, PJ_EINVAL);
+
+ if (echo->state) {
+ speex_echo_state_destroy(echo->state);
+ echo->state = NULL;
+ }
+
+ if (echo->preprocess) {
+ speex_preprocess_state_destroy(echo->preprocess);
+ echo->preprocess = NULL;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Reset AEC
+ */
+PJ_DEF(void) speex_aec_reset(void *state )
+{
+ speex_ec *echo = (speex_ec*) state;
+ speex_echo_state_reset(echo->state);
+}
+
+
+/*
+ * Perform echo cancellation.
+ */
+PJ_DEF(pj_status_t) speex_aec_cancel_echo( void *state,
+ pj_int16_t *rec_frm,
+ const pj_int16_t *play_frm,
+ unsigned options,
+ void *reserved )
+{
+ speex_ec *echo = (speex_ec*) state;
+
+ /* Sanity checks */
+ PJ_ASSERT_RETURN(echo && rec_frm && play_frm && options==0 &&
+ reserved==NULL, PJ_EINVAL);
+
+ /* Cancel echo, put output in temporary buffer */
+ speex_echo_cancellation(echo->state, (const spx_int16_t*)rec_frm,
+ (const spx_int16_t*)play_frm,
+ (spx_int16_t*)echo->tmp_frame);
+
+
+ /* Preprocess output */
+ speex_preprocess_run(echo->preprocess, (spx_int16_t*)echo->tmp_frame);
+
+ /* Copy temporary buffer back to original rec_frm */
+ pjmedia_copy_samples(rec_frm, echo->tmp_frame, echo->samples_per_frame);
+
+ return PJ_SUCCESS;
+
+}
+
+#endif
diff --git a/pjmedia/src/pjmedia/echo_suppress.c b/pjmedia/src/pjmedia/echo_suppress.c
new file mode 100644
index 0000000..1563fb0
--- /dev/null
+++ b/pjmedia/src/pjmedia/echo_suppress.c
@@ -0,0 +1,805 @@
+/* $Id: echo_suppress.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/types.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/frame.h>
+#include <pjmedia/silencedet.h>
+#include <pj/array.h>
+#include <pj/assert.h>
+#include <pj/lock.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+
+#include "echo_internal.h"
+
+#define THIS_FILE "echo_suppress.c"
+
+/* Maximum float constant */
+#define MAX_FLOAT (float)1.701411e38
+
+/* The effective learn duration (in seconds) before we declare that learning
+ * is complete. The actual learning duration itself may be longer depending
+ * on the conversation pattern (e.g. we can't detect echo if speaker is only
+ * playing silence).
+ */
+#define MAX_CALC_DURATION_SEC 3
+
+/* The internal audio segment length, in milliseconds. 10ms shold be good
+ * and no need to change it.
+ */
+#define SEGMENT_PTIME 10
+
+/* The length of the template signal in milliseconds. The longer the template,
+ * the better correlation will be found, at the expense of more processing
+ * and longer learning time.
+ */
+#define TEMPLATE_PTIME 200
+
+/* How long to look back in the past to see if either mic or speaker is
+ * active.
+ */
+#define SIGNAL_LOOKUP_MSEC 200
+
+/* The minimum level value to be considered as talking, in uLaw complement
+ * (0-255).
+ */
+#define MIN_SIGNAL_ULAW 35
+
+/* The period (in seconds) on which the ES will analize it's effectiveness,
+ * and it may trigger soft-reset to force recalculation.
+ */
+#define CHECK_PERIOD 30
+
+/* Maximum signal level of average echo residue (in uLaw complement). When
+ * the residue value exceeds this value, we force the ES to re-learn.
+ */
+#define MAX_RESIDUE 2.5
+
+
+#if 0
+# define TRACE_(expr) PJ_LOG(5,expr)
+#else
+# define TRACE_(expr)
+#endif
+
+PJ_INLINE(float) FABS(float val)
+{
+ if (val < 0)
+ return -val;
+ else
+ return val;
+}
+
+
+#if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT!=0
+ typedef float pj_ufloat_t;
+# define pj_ufloat_from_float(f) (f)
+# define pj_ufloat_mul_u(val1, f) ((val1) * (f))
+# define pj_ufloat_mul_i(val1, f) ((val1) * (f))
+#else
+ typedef pj_uint32_t pj_ufloat_t;
+
+ pj_ufloat_t pj_ufloat_from_float(float f)
+ {
+ return (pj_ufloat_t)(f * 65536);
+ }
+
+ unsigned pj_ufloat_mul_u(unsigned val1, pj_ufloat_t val2)
+ {
+ return (val1 * val2) >> 16;
+ }
+
+ int pj_ufloat_mul_i(int val1, pj_ufloat_t val2)
+ {
+ return (val1 * (pj_int32_t)val2) >> 16;
+ }
+#endif
+
+
+/* Conversation state */
+typedef enum talk_state
+{
+ ST_NULL,
+ ST_LOCAL_TALK,
+ ST_REM_SILENT,
+ ST_DOUBLETALK,
+ ST_REM_TALK
+} talk_state_t;
+
+const char *state_names[] =
+{
+ "Null",
+ "local talking",
+ "remote silent",
+ "doubletalk",
+ "remote talking"
+};
+
+
+/* Description:
+
+ The echo suppressor tries to find the position of echoed signal by looking
+ at the correlation between signal played to the speaker (played signal)
+ and the signal captured from the microphone (recorded signal).
+
+ To do this, it first divides the frames (from mic and speaker) into
+ segments, calculate the audio level of the segment, and save the level
+ information in the playback and record history (play_hist and rec_hist
+ respectively).
+
+ In the history, the newest element (depicted as "t0" in the diagram belo)
+ is put in the last position of the array.
+
+ The record history size is as large as the template size (tmpl_cnt), since
+ we will use the record history as the template to find the best matching
+ position in the playback history.
+
+ Here is the record history buffer:
+
+ <--templ_cnt-->
+ +-------------+
+ | rec_hist |
+ +-------------+
+ t-templ_cnt......t0
+
+ As you can see, the newest frame ("t0") is put as the last element in the
+ array.
+
+ The playback history size is larger than record history, since we need to
+ find the matching pattern in the past. The playback history size is
+ "templ_cnt + tail_cnt", where "tail_cnt" is the number of segments equal
+ to the maximum tail length. The maximum tail length is set when the ES
+ is created.
+
+ Here is the playback history buffer:
+
+ <-----tail_cnt-----> <--templ_cnt-->
+ +-------------------+--------------+
+ | play_hist |
+ +-------------------+--------------+
+ t-play_hist_cnt...t-templ_cnt.......t0
+
+
+
+ Learning:
+
+ During the processing, the ES calculates the following values:
+ - the correlation value, that is how similar the playback signal compared
+ to the mic signal. The lower the correlation value the better (i.e. more
+ similar) the signal is. The correlation value is done over the template
+ duration.
+ - the gain scaling factor, that is the ratio between mic signal and
+ speaker signal. The ES calculates both the minimum and average ratios.
+
+ The ES calculates both the values above for every tail position in the
+ playback history. The values are saved in arrays below:
+
+ <-----tail_cnt----->
+ +-------------------+
+ | corr_sum |
+ +-------------------+
+ | min_factor |
+ +-------------------+
+ | avg_factor |
+ +-------------------+
+
+ At the end of processing, the ES iterates through the correlation array and
+ picks the tail index with the lowest corr_sum value. This is the position
+ where echo is most likely to be found.
+
+
+ Processing:
+
+ Once learning is done, the ES will change the level of the mic signal
+ depending on the state of the conversation and according to the ratio that
+ has been found in the learning phase above.
+
+ */
+
+/*
+ * The simple echo suppresor state
+ */
+typedef struct echo_supp
+{
+ unsigned clock_rate; /* Clock rate. */
+ pj_uint16_t samples_per_frame; /* Frame length in samples */
+ pj_uint16_t samples_per_segment;/* Segment length in samples */
+ pj_uint16_t tail_ms; /* Tail length in milliseconds */
+ pj_uint16_t tail_samples; /* Tail length in samples. */
+
+ pj_bool_t learning; /* Are we still learning yet? */
+ talk_state_t talk_state; /* Current talking state */
+ int tail_index; /* Echo location, -1 if not found */
+
+ unsigned max_calc; /* # of calc before learning complete.
+ (see MAX_CALC_DURATION_SEC) */
+ unsigned calc_cnt; /* Number of calculations so far */
+
+ unsigned update_cnt; /* # of updates */
+ unsigned templ_cnt; /* Template length, in # of segments */
+ unsigned tail_cnt; /* Tail length, in # of segments */
+ unsigned play_hist_cnt; /* # of segments in play_hist */
+ pj_uint16_t *play_hist; /* Array of playback levels */
+ pj_uint16_t *rec_hist; /* Array of rec levels */
+
+ float *corr_sum; /* Array of corr for each tail pos. */
+ float *tmp_corr; /* Temporary corr array calculation */
+ float best_corr; /* Best correlation so far. */
+
+ unsigned sum_rec_level; /* Running sum of level in rec_hist */
+ float rec_corr; /* Running corr in rec_hist. */
+
+ unsigned sum_play_level0; /* Running sum of level for first pos */
+ float play_corr0; /* Running corr for first pos . */
+
+ float *min_factor; /* Array of minimum scaling factor */
+ float *avg_factor; /* Array of average scaling factor */
+ float *tmp_factor; /* Array to store provisional result */
+
+ unsigned running_cnt; /* Running duration in # of frames */
+ float residue; /* Accummulated echo residue. */
+ float last_factor; /* Last factor applied to mic signal */
+} echo_supp;
+
+
+
+/*
+ * Create.
+ */
+PJ_DEF(pj_status_t) echo_supp_create( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned tail_ms,
+ unsigned options,
+ void **p_state )
+{
+ echo_supp *ec;
+
+ PJ_UNUSED_ARG(channel_count);
+ PJ_UNUSED_ARG(options);
+
+ PJ_ASSERT_RETURN(samples_per_frame >= SEGMENT_PTIME * clock_rate / 1000,
+ PJ_ENOTSUP);
+
+ ec = PJ_POOL_ZALLOC_T(pool, struct echo_supp);
+ ec->clock_rate = clock_rate;
+ ec->samples_per_frame = (pj_uint16_t)samples_per_frame;
+ ec->samples_per_segment = (pj_uint16_t)(SEGMENT_PTIME * clock_rate / 1000);
+ ec->tail_ms = (pj_uint16_t)tail_ms;
+ ec->tail_samples = (pj_uint16_t)(tail_ms * clock_rate / 1000);
+
+ ec->templ_cnt = TEMPLATE_PTIME / SEGMENT_PTIME;
+ ec->tail_cnt = (pj_uint16_t)(tail_ms / SEGMENT_PTIME);
+ ec->play_hist_cnt = (pj_uint16_t)(ec->tail_cnt+ec->templ_cnt);
+
+ ec->max_calc = (pj_uint16_t)(MAX_CALC_DURATION_SEC * clock_rate /
+ ec->samples_per_segment);
+
+ ec->rec_hist = (pj_uint16_t*)
+ pj_pool_alloc(pool, ec->templ_cnt *
+ sizeof(ec->rec_hist[0]));
+
+ /* Note: play history has twice number of elements */
+ ec->play_hist = (pj_uint16_t*)
+ pj_pool_alloc(pool, ec->play_hist_cnt *
+ sizeof(ec->play_hist[0]));
+
+ ec->corr_sum = (float*)
+ pj_pool_alloc(pool, ec->tail_cnt *
+ sizeof(ec->corr_sum[0]));
+ ec->tmp_corr = (float*)
+ pj_pool_alloc(pool, ec->tail_cnt *
+ sizeof(ec->tmp_corr[0]));
+ ec->min_factor = (float*)
+ pj_pool_alloc(pool, ec->tail_cnt *
+ sizeof(ec->min_factor[0]));
+ ec->avg_factor = (float*)
+ pj_pool_alloc(pool, ec->tail_cnt *
+ sizeof(ec->avg_factor[0]));
+ ec->tmp_factor = (float*)
+ pj_pool_alloc(pool, ec->tail_cnt *
+ sizeof(ec->tmp_factor[0]));
+ echo_supp_reset(ec);
+
+ *p_state = ec;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy.
+ */
+PJ_DEF(pj_status_t) echo_supp_destroy(void *state)
+{
+ PJ_UNUSED_ARG(state);
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Hard reset
+ */
+PJ_DEF(void) echo_supp_reset(void *state)
+{
+ unsigned i;
+ echo_supp *ec = (echo_supp*) state;
+
+ pj_bzero(ec->rec_hist, ec->templ_cnt * sizeof(ec->rec_hist[0]));
+ pj_bzero(ec->play_hist, ec->play_hist_cnt * sizeof(ec->play_hist[0]));
+
+ for (i=0; i<ec->tail_cnt; ++i) {
+ ec->corr_sum[i] = ec->avg_factor[i] = 0;
+ ec->min_factor[i] = MAX_FLOAT;
+ }
+
+ ec->update_cnt = 0;
+ ec->calc_cnt = 0;
+ ec->learning = PJ_TRUE;
+ ec->tail_index = -1;
+ ec->best_corr = MAX_FLOAT;
+ ec->talk_state = ST_NULL;
+ ec->last_factor = 1.0;
+ ec->residue = 0;
+ ec->running_cnt = 0;
+ ec->sum_rec_level = ec->sum_play_level0 = 0;
+ ec->rec_corr = ec->play_corr0 = 0;
+}
+
+/*
+ * Soft reset to force the EC to re-learn without having to discard all
+ * rec and playback history.
+ */
+PJ_DEF(void) echo_supp_soft_reset(void *state)
+{
+ unsigned i;
+
+ echo_supp *ec = (echo_supp*) state;
+
+ for (i=0; i<ec->tail_cnt; ++i) {
+ ec->corr_sum[i] = 0;
+ }
+
+ ec->update_cnt = 0;
+ ec->calc_cnt = 0;
+ ec->learning = PJ_TRUE;
+ ec->best_corr = MAX_FLOAT;
+ ec->residue = 0;
+ ec->running_cnt = 0;
+ ec->sum_rec_level = ec->sum_play_level0 = 0;
+ ec->rec_corr = ec->play_corr0 = 0;
+
+ PJ_LOG(4,(THIS_FILE, "Echo suppressor soft reset. Re-learning.."));
+}
+
+
+/* Set state */
+static void echo_supp_set_state(echo_supp *ec, talk_state_t state,
+ unsigned level)
+{
+ PJ_UNUSED_ARG(level);
+
+ if (state != ec->talk_state) {
+ TRACE_((THIS_FILE, "[%03d.%03d] %s --> %s, level=%u",
+ (ec->update_cnt * SEGMENT_PTIME / 1000),
+ ((ec->update_cnt * SEGMENT_PTIME) % 1000),
+ state_names[ec->talk_state],
+ state_names[state], level));
+ ec->talk_state = state;
+ }
+}
+
+/*
+ * Update EC state
+ */
+static void echo_supp_update(echo_supp *ec, pj_int16_t *rec_frm,
+ const pj_int16_t *play_frm)
+{
+ int prev_index;
+ unsigned i, j, frm_level, sum_play_level, ulaw;
+ pj_uint16_t old_rec_frm_level, old_play_frm_level;
+ float play_corr;
+
+ ++ec->update_cnt;
+ if (ec->update_cnt > 0x7FFFFFFF)
+ ec->update_cnt = 0x7FFFFFFF; /* Detect overflow */
+
+ /* Calculate current play frame level */
+ frm_level = pjmedia_calc_avg_signal(play_frm, ec->samples_per_segment);
+ ++frm_level; /* to avoid division by zero */
+
+ /* Save the oldest frame level for later */
+ old_play_frm_level = ec->play_hist[0];
+
+ /* Push current frame level to the back of the play history */
+ pj_array_erase(ec->play_hist, sizeof(pj_uint16_t), ec->play_hist_cnt, 0);
+ ec->play_hist[ec->play_hist_cnt-1] = (pj_uint16_t) frm_level;
+
+ /* Calculate level of current mic frame */
+ frm_level = pjmedia_calc_avg_signal(rec_frm, ec->samples_per_segment);
+ ++frm_level; /* to avoid division by zero */
+
+ /* Save the oldest frame level for later */
+ old_rec_frm_level = ec->rec_hist[0];
+
+ /* Push to the back of the rec history */
+ pj_array_erase(ec->rec_hist, sizeof(pj_uint16_t), ec->templ_cnt, 0);
+ ec->rec_hist[ec->templ_cnt-1] = (pj_uint16_t) frm_level;
+
+
+ /* Can't do the calc until the play history is full. */
+ if (ec->update_cnt < ec->play_hist_cnt)
+ return;
+
+ /* Skip if learning is done */
+ if (!ec->learning)
+ return;
+
+
+ /* Calculate rec signal pattern */
+ if (ec->sum_rec_level == 0) {
+ /* Buffer has just been filled up, do full calculation */
+ ec->rec_corr = 0;
+ ec->sum_rec_level = 0;
+ for (i=0; i < ec->templ_cnt-1; ++i) {
+ float corr;
+ corr = (float)ec->rec_hist[i+1] / ec->rec_hist[i];
+ ec->rec_corr += corr;
+ ec->sum_rec_level += ec->rec_hist[i];
+ }
+ ec->sum_rec_level += ec->rec_hist[i];
+ } else {
+ /* Update from previous calculation */
+ ec->sum_rec_level = ec->sum_rec_level - old_rec_frm_level +
+ ec->rec_hist[ec->templ_cnt-1];
+ ec->rec_corr = ec->rec_corr - ((float)ec->rec_hist[0] /
+ old_rec_frm_level) +
+ ((float)ec->rec_hist[ec->templ_cnt-1] /
+ ec->rec_hist[ec->templ_cnt-2]);
+ }
+
+ /* Iterate through the play history and calculate the signal correlation
+ * for every tail position in the play_hist. Save the result in temporary
+ * array since we may bail out early if the conversation state is not good
+ * to detect echo.
+ */
+ /*
+ * First phase: do full calculation for the first position
+ */
+ if (ec->sum_play_level0 == 0) {
+ /* Buffer has just been filled up, do full calculation */
+ sum_play_level = 0;
+ play_corr = 0;
+ for (j=0; j<ec->templ_cnt-1; ++j) {
+ float corr;
+ corr = (float)ec->play_hist[j+1] / ec->play_hist[j];
+ play_corr += corr;
+ sum_play_level += ec->play_hist[j];
+ }
+ sum_play_level += ec->play_hist[j];
+ ec->sum_play_level0 = sum_play_level;
+ ec->play_corr0 = play_corr;
+ } else {
+ /* Update from previous calculation */
+ ec->sum_play_level0 = ec->sum_play_level0 - old_play_frm_level +
+ ec->play_hist[ec->templ_cnt-1];
+ ec->play_corr0 = ec->play_corr0 - ((float)ec->play_hist[0] /
+ old_play_frm_level) +
+ ((float)ec->play_hist[ec->templ_cnt-1] /
+ ec->play_hist[ec->templ_cnt-2]);
+ sum_play_level = ec->sum_play_level0;
+ play_corr = ec->play_corr0;
+ }
+ ec->tmp_corr[0] = FABS(play_corr - ec->rec_corr);
+ ec->tmp_factor[0] = (float)ec->sum_rec_level / sum_play_level;
+
+ /* Bail out if remote isn't talking */
+ ulaw = pjmedia_linear2ulaw(sum_play_level/ec->templ_cnt) ^ 0xFF;
+ if (ulaw < MIN_SIGNAL_ULAW) {
+ echo_supp_set_state(ec, ST_REM_SILENT, ulaw);
+ return;
+ }
+ /* Bail out if local user is talking */
+ if (ec->sum_rec_level >= sum_play_level) {
+ echo_supp_set_state(ec, ST_LOCAL_TALK, ulaw);
+ return;
+ }
+
+ /*
+ * Second phase: do incremental calculation for the rest of positions
+ */
+ for (i=1; i < ec->tail_cnt; ++i) {
+ unsigned end;
+
+ end = i + ec->templ_cnt;
+
+ sum_play_level = sum_play_level - ec->play_hist[i-1] +
+ ec->play_hist[end-1];
+ play_corr = play_corr - ((float)ec->play_hist[i]/ec->play_hist[i-1]) +
+ ((float)ec->play_hist[end-1]/ec->play_hist[end-2]);
+
+ /* Bail out if remote isn't talking */
+ ulaw = pjmedia_linear2ulaw(sum_play_level/ec->templ_cnt) ^ 0xFF;
+ if (ulaw < MIN_SIGNAL_ULAW) {
+ echo_supp_set_state(ec, ST_REM_SILENT, ulaw);
+ return;
+ }
+
+ /* Bail out if local user is talking */
+ if (ec->sum_rec_level >= sum_play_level) {
+ echo_supp_set_state(ec, ST_LOCAL_TALK, ulaw);
+ return;
+ }
+
+#if 0
+ // disabled: not a good idea if mic throws out loud echo
+ /* Also bail out if we suspect there's a doubletalk */
+ ulaw = pjmedia_linear2ulaw(ec->sum_rec_level/ec->templ_cnt) ^ 0xFF;
+ if (ulaw > MIN_SIGNAL_ULAW) {
+ echo_supp_set_state(ec, ST_DOUBLETALK, ulaw);
+ return;
+ }
+#endif
+
+ /* Calculate correlation and save to temporary array */
+ ec->tmp_corr[i] = FABS(play_corr - ec->rec_corr);
+
+ /* Also calculate the gain factor between mic and speaker level */
+ ec->tmp_factor[i] = (float)ec->sum_rec_level / sum_play_level;
+ pj_assert(ec->tmp_factor[i] < 1);
+ }
+
+ /* We seem to have good signal, we can update the EC state */
+ echo_supp_set_state(ec, ST_REM_TALK, MIN_SIGNAL_ULAW);
+
+ /* Accummulate the correlation value to the history and at the same
+ * time find the tail index of the best correlation.
+ */
+ prev_index = ec->tail_index;
+ for (i=1; i<ec->tail_cnt-1; ++i) {
+ float *p = &ec->corr_sum[i], sum;
+
+ /* Accummulate correlation value for this tail position */
+ ec->corr_sum[i] += ec->tmp_corr[i];
+
+ /* Update the min and avg gain factor for this tail position */
+ if (ec->tmp_factor[i] < ec->min_factor[i])
+ ec->min_factor[i] = ec->tmp_factor[i];
+ ec->avg_factor[i] = ((ec->avg_factor[i] * ec->tail_cnt) +
+ ec->tmp_factor[i]) /
+ (ec->tail_cnt + 1);
+
+ /* To get the best correlation, also include the correlation
+ * value of the neighbouring tail locations.
+ */
+ sum = *(p-1) + (*p)*2 + *(p+1);
+ //sum = *p;
+
+ /* See if we have better correlation value */
+ if (sum < ec->best_corr) {
+ ec->tail_index = i;
+ ec->best_corr = sum;
+ }
+ }
+
+ if (ec->tail_index != prev_index) {
+ unsigned duration;
+ int imin, iavg;
+
+ duration = ec->update_cnt * SEGMENT_PTIME;
+ imin = (int)(ec->min_factor[ec->tail_index] * 1000);
+ iavg = (int)(ec->avg_factor[ec->tail_index] * 1000);
+
+ PJ_LOG(4,(THIS_FILE,
+ "Echo suppressor updated at t=%03d.%03ds, echo tail=%d msec"
+ ", factor min/avg=%d.%03d/%d.%03d",
+ (duration/1000), (duration%1000),
+ (ec->tail_cnt-ec->tail_index) * SEGMENT_PTIME,
+ imin/1000, imin%1000,
+ iavg/1000, iavg%1000));
+
+ }
+
+ ++ec->calc_cnt;
+
+ if (ec->calc_cnt > ec->max_calc) {
+ unsigned duration;
+ int imin, iavg;
+
+
+ ec->learning = PJ_FALSE;
+ ec->running_cnt = 0;
+
+ duration = ec->update_cnt * SEGMENT_PTIME;
+ imin = (int)(ec->min_factor[ec->tail_index] * 1000);
+ iavg = (int)(ec->avg_factor[ec->tail_index] * 1000);
+
+ PJ_LOG(4,(THIS_FILE,
+ "Echo suppressor learning done at t=%03d.%03ds, tail=%d ms"
+ ", factor min/avg=%d.%03d/%d.%03d",
+ (duration/1000), (duration%1000),
+ (ec->tail_cnt-ec->tail_index) * SEGMENT_PTIME,
+ imin/1000, imin%1000,
+ iavg/1000, iavg%1000));
+ }
+
+}
+
+
+/* Amplify frame */
+static void amplify_frame(pj_int16_t *frm, unsigned length,
+ pj_ufloat_t factor)
+{
+ unsigned i;
+
+ for (i=0; i<length; ++i) {
+ frm[i] = (pj_int16_t)pj_ufloat_mul_i(frm[i], factor);
+ }
+}
+
+/*
+ * Perform echo cancellation.
+ */
+PJ_DEF(pj_status_t) echo_supp_cancel_echo( void *state,
+ pj_int16_t *rec_frm,
+ const pj_int16_t *play_frm,
+ unsigned options,
+ void *reserved )
+{
+ unsigned i, N;
+ echo_supp *ec = (echo_supp*) state;
+
+ PJ_UNUSED_ARG(options);
+ PJ_UNUSED_ARG(reserved);
+
+ /* Calculate number of segments. This should be okay even if
+ * samples_per_frame is not a multiply of samples_per_segment, since
+ * we only calculate level.
+ */
+ N = ec->samples_per_frame / ec->samples_per_segment;
+ pj_assert(N>0);
+ for (i=0; i<N; ++i) {
+ unsigned pos = i * ec->samples_per_segment;
+ echo_supp_update(ec, rec_frm+pos, play_frm+pos);
+ }
+
+ if (ec->tail_index < 0) {
+ /* Not ready */
+ } else {
+ unsigned lookup_cnt, rec_level=0, play_level=0;
+ unsigned tail_cnt;
+ float factor;
+
+ /* How many previous segments to lookup */
+ lookup_cnt = SIGNAL_LOOKUP_MSEC / SEGMENT_PTIME;
+ if (lookup_cnt > ec->templ_cnt)
+ lookup_cnt = ec->templ_cnt;
+
+ /* Lookup in recording history to get maximum mic level, to see
+ * if local user is currently talking
+ */
+ for (i=ec->templ_cnt - lookup_cnt; i < ec->templ_cnt; ++i) {
+ if (ec->rec_hist[i] > rec_level)
+ rec_level = ec->rec_hist[i];
+ }
+ rec_level = pjmedia_linear2ulaw(rec_level) ^ 0xFF;
+
+ /* Calculate the detected tail length, in # of segments */
+ tail_cnt = (ec->tail_cnt - ec->tail_index);
+
+ /* Lookup in playback history to get max speaker level, to see
+ * if remote user is currently talking
+ */
+ for (i=ec->play_hist_cnt -lookup_cnt -tail_cnt;
+ i<ec->play_hist_cnt-tail_cnt; ++i)
+ {
+ if (ec->play_hist[i] > play_level)
+ play_level = ec->play_hist[i];
+ }
+ play_level = pjmedia_linear2ulaw(play_level) ^ 0xFF;
+
+ if (rec_level >= MIN_SIGNAL_ULAW) {
+ if (play_level < MIN_SIGNAL_ULAW) {
+ /* Mic is talking, speaker is idle. Let mic signal pass as is.
+ */
+ factor = 1.0;
+ echo_supp_set_state(ec, ST_LOCAL_TALK, rec_level);
+ } else if (rec_level > play_level) {
+ /* Seems that both are talking. Scale the mic signal
+ * down a little bit to reduce echo, while allowing both
+ * parties to talk at the same time.
+ */
+ factor = (float)(ec->avg_factor[ec->tail_index] * 2);
+ echo_supp_set_state(ec, ST_DOUBLETALK, rec_level);
+ } else {
+ /* Speaker is active, but we've picked up large signal in
+ * the microphone. Assume that this is an echo, so bring
+ * the level down to minimum too.
+ */
+ factor = ec->min_factor[ec->tail_index] / 2;
+ echo_supp_set_state(ec, ST_REM_TALK, play_level);
+ }
+ } else {
+ if (play_level < MIN_SIGNAL_ULAW) {
+ /* Both mic and speaker seems to be idle. Also scale the
+ * mic signal down with average factor to reduce low power
+ * echo.
+ */
+ factor = ec->avg_factor[ec->tail_index] * 3 / 2;
+ echo_supp_set_state(ec, ST_REM_SILENT, rec_level);
+ } else {
+ /* Mic is idle, but there's something playing in speaker.
+ * Scale the mic down to minimum
+ */
+ factor = ec->min_factor[ec->tail_index] / 2;
+ echo_supp_set_state(ec, ST_REM_TALK, play_level);
+ }
+ }
+
+ /* Smoothen the transition */
+ if (factor >= ec->last_factor)
+ factor = (factor + ec->last_factor) / 2;
+ else
+ factor = (factor + ec->last_factor*19) / 20;
+
+ /* Amplify frame */
+ amplify_frame(rec_frm, ec->samples_per_frame,
+ pj_ufloat_from_float(factor));
+ ec->last_factor = factor;
+
+ if (ec->talk_state == ST_REM_TALK) {
+ unsigned level, recalc_cnt;
+
+ /* Get the adjusted frame signal level */
+ level = pjmedia_calc_avg_signal(rec_frm, ec->samples_per_frame);
+ level = pjmedia_linear2ulaw(level) ^ 0xFF;
+
+ /* Accumulate average echo residue to see the ES effectiveness */
+ ec->residue = ((ec->residue * ec->running_cnt) + level) /
+ (ec->running_cnt + 1);
+
+ ++ec->running_cnt;
+
+ /* Check if we need to re-learn */
+ recalc_cnt = CHECK_PERIOD * ec->clock_rate / ec->samples_per_frame;
+ if (ec->running_cnt > recalc_cnt) {
+ int iresidue;
+
+ iresidue = (int)(ec->residue*1000);
+
+ PJ_LOG(5,(THIS_FILE, "Echo suppressor residue = %d.%03d",
+ iresidue/1000, iresidue%1000));
+
+ if (ec->residue > MAX_RESIDUE && !ec->learning) {
+ echo_supp_soft_reset(ec);
+ ec->residue = 0;
+ } else {
+ ec->running_cnt = 0;
+ ec->residue = 0;
+ }
+ }
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjmedia/src/pjmedia/endpoint.c b/pjmedia/src/pjmedia/endpoint.c
new file mode 100644
index 0000000..e20a5a4
--- /dev/null
+++ b/pjmedia/src/pjmedia/endpoint.c
@@ -0,0 +1,942 @@
+/* $Id: endpoint.c 3999 2012-03-30 07:10:13Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/endpoint.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/sdp.h>
+#include <pjmedia/vid_codec.h>
+#include <pjmedia-audiodev/audiodev.h>
+#include <pj/assert.h>
+#include <pj/ioqueue.h>
+#include <pj/lock.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/sock.h>
+#include <pj/string.h>
+
+
+#define THIS_FILE "endpoint.c"
+
+static const pj_str_t STR_AUDIO = { "audio", 5};
+static const pj_str_t STR_VIDEO = { "video", 5};
+static const pj_str_t STR_IN = { "IN", 2 };
+static const pj_str_t STR_IP4 = { "IP4", 3};
+static const pj_str_t STR_IP6 = { "IP6", 3};
+static const pj_str_t STR_RTP_AVP = { "RTP/AVP", 7 };
+static const pj_str_t STR_SDP_NAME = { "pjmedia", 7 };
+static const pj_str_t STR_SENDRECV = { "sendrecv", 8 };
+
+
+
+/* Config to control rtpmap inclusion for static payload types */
+pj_bool_t pjmedia_add_rtpmap_for_static_pt =
+ PJMEDIA_ADD_RTPMAP_FOR_STATIC_PT;
+
+
+
+/* Worker thread proc. */
+static int PJ_THREAD_FUNC worker_proc(void*);
+
+
+#define MAX_THREADS 16
+
+
+/* List of media endpoint exit callback. */
+typedef struct exit_cb
+{
+ PJ_DECL_LIST_MEMBER (struct exit_cb);
+ pjmedia_endpt_exit_callback func;
+} exit_cb;
+
+
+/** Concrete declaration of media endpoint. */
+struct pjmedia_endpt
+{
+ /** Pool. */
+ pj_pool_t *pool;
+
+ /** Pool factory. */
+ pj_pool_factory *pf;
+
+ /** Codec manager. */
+ pjmedia_codec_mgr codec_mgr;
+
+ /** IOqueue instance. */
+ pj_ioqueue_t *ioqueue;
+
+ /** Do we own the ioqueue? */
+ pj_bool_t own_ioqueue;
+
+ /** Number of threads. */
+ unsigned thread_cnt;
+
+ /** IOqueue polling thread, if any. */
+ pj_thread_t *thread[MAX_THREADS];
+
+ /** To signal polling thread to quit. */
+ pj_bool_t quit_flag;
+
+ /** Is telephone-event enable */
+ pj_bool_t has_telephone_event;
+
+ /** List of exit callback. */
+ exit_cb exit_cb_list;
+};
+
+/**
+ * Initialize and get the instance of media endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_endpt_create(pj_pool_factory *pf,
+ pj_ioqueue_t *ioqueue,
+ unsigned worker_cnt,
+ pjmedia_endpt **p_endpt)
+{
+ pj_pool_t *pool;
+ pjmedia_endpt *endpt;
+ unsigned i;
+ pj_status_t status;
+
+ status = pj_register_strerror(PJMEDIA_ERRNO_START, PJ_ERRNO_SPACE_SIZE,
+ &pjmedia_strerror);
+ pj_assert(status == PJ_SUCCESS);
+
+ PJ_ASSERT_RETURN(pf && p_endpt, PJ_EINVAL);
+ PJ_ASSERT_RETURN(worker_cnt <= MAX_THREADS, PJ_EINVAL);
+
+ pool = pj_pool_create(pf, "med-ept", 512, 512, NULL);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ endpt = PJ_POOL_ZALLOC_T(pool, struct pjmedia_endpt);
+ endpt->pool = pool;
+ endpt->pf = pf;
+ endpt->ioqueue = ioqueue;
+ endpt->thread_cnt = worker_cnt;
+ endpt->has_telephone_event = PJ_TRUE;
+
+ /* Sound */
+ status = pjmedia_aud_subsys_init(pf);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Init codec manager. */
+ status = pjmedia_codec_mgr_init(&endpt->codec_mgr, endpt->pf);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Initialize exit callback list. */
+ pj_list_init(&endpt->exit_cb_list);
+
+ /* Create ioqueue if none is specified. */
+ if (endpt->ioqueue == NULL) {
+
+ endpt->own_ioqueue = PJ_TRUE;
+
+ status = pj_ioqueue_create( endpt->pool, PJ_IOQUEUE_MAX_HANDLES,
+ &endpt->ioqueue);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ if (worker_cnt == 0) {
+ PJ_LOG(4,(THIS_FILE, "Warning: no worker thread is created in"
+ "media endpoint for internal ioqueue"));
+ }
+ }
+
+ /* Create worker threads if asked. */
+ for (i=0; i<worker_cnt; ++i) {
+ status = pj_thread_create( endpt->pool, "media", &worker_proc,
+ endpt, 0, 0, &endpt->thread[i]);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+
+ *p_endpt = endpt;
+ return PJ_SUCCESS;
+
+on_error:
+
+ /* Destroy threads */
+ for (i=0; i<endpt->thread_cnt; ++i) {
+ if (endpt->thread[i]) {
+ pj_thread_destroy(endpt->thread[i]);
+ }
+ }
+
+ /* Destroy internal ioqueue */
+ if (endpt->ioqueue && endpt->own_ioqueue)
+ pj_ioqueue_destroy(endpt->ioqueue);
+
+ pjmedia_codec_mgr_destroy(&endpt->codec_mgr);
+ pjmedia_aud_subsys_shutdown();
+ pj_pool_release(pool);
+ return status;
+}
+
+/**
+ * Get the codec manager instance.
+ */
+PJ_DEF(pjmedia_codec_mgr*) pjmedia_endpt_get_codec_mgr(pjmedia_endpt *endpt)
+{
+ return &endpt->codec_mgr;
+}
+
+/**
+ * Deinitialize media endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_endpt_destroy (pjmedia_endpt *endpt)
+{
+ exit_cb *ecb;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(endpt, PJ_EINVAL);
+
+ endpt->quit_flag = 1;
+
+ /* Destroy threads */
+ for (i=0; i<endpt->thread_cnt; ++i) {
+ if (endpt->thread[i]) {
+ pj_thread_join(endpt->thread[i]);
+ pj_thread_destroy(endpt->thread[i]);
+ endpt->thread[i] = NULL;
+ }
+ }
+
+ /* Destroy internal ioqueue */
+ if (endpt->ioqueue && endpt->own_ioqueue) {
+ pj_ioqueue_destroy(endpt->ioqueue);
+ endpt->ioqueue = NULL;
+ }
+
+ endpt->pf = NULL;
+
+ pjmedia_codec_mgr_destroy(&endpt->codec_mgr);
+ pjmedia_aud_subsys_shutdown();
+
+ /* Call all registered exit callbacks */
+ ecb = endpt->exit_cb_list.next;
+ while (ecb != &endpt->exit_cb_list) {
+ (*ecb->func)(endpt);
+ ecb = ecb->next;
+ }
+
+ pj_pool_release (endpt->pool);
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_endpt_set_flag( pjmedia_endpt *endpt,
+ pjmedia_endpt_flag flag,
+ const void *value)
+{
+ PJ_ASSERT_RETURN(endpt, PJ_EINVAL);
+
+ switch (flag) {
+ case PJMEDIA_ENDPT_HAS_TELEPHONE_EVENT_FLAG:
+ endpt->has_telephone_event = *(pj_bool_t*)value;
+ break;
+ default:
+ return PJ_EINVAL;
+ }
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_endpt_get_flag( pjmedia_endpt *endpt,
+ pjmedia_endpt_flag flag,
+ void *value)
+{
+ PJ_ASSERT_RETURN(endpt, PJ_EINVAL);
+
+ switch (flag) {
+ case PJMEDIA_ENDPT_HAS_TELEPHONE_EVENT_FLAG:
+ *(pj_bool_t*)value = endpt->has_telephone_event;
+ break;
+ default:
+ return PJ_EINVAL;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/**
+ * Get the ioqueue instance of the media endpoint.
+ */
+PJ_DEF(pj_ioqueue_t*) pjmedia_endpt_get_ioqueue(pjmedia_endpt *endpt)
+{
+ PJ_ASSERT_RETURN(endpt, NULL);
+ return endpt->ioqueue;
+}
+
+/**
+ * Get the number of worker threads in media endpoint.
+ */
+PJ_DEF(unsigned) pjmedia_endpt_get_thread_count(pjmedia_endpt *endpt)
+{
+ PJ_ASSERT_RETURN(endpt, 0);
+ return endpt->thread_cnt;
+}
+
+/**
+ * Get a reference to one of the worker threads of the media endpoint
+ */
+PJ_DEF(pj_thread_t*) pjmedia_endpt_get_thread(pjmedia_endpt *endpt,
+ unsigned index)
+{
+ PJ_ASSERT_RETURN(endpt, NULL);
+ PJ_ASSERT_RETURN(index < endpt->thread_cnt, NULL);
+
+ /* here should be an assert on index >= 0 < endpt->thread_cnt */
+
+ return endpt->thread[index];
+}
+
+/**
+ * Worker thread proc.
+ */
+static int PJ_THREAD_FUNC worker_proc(void *arg)
+{
+ pjmedia_endpt *endpt = (pjmedia_endpt*) arg;
+
+ while (!endpt->quit_flag) {
+ pj_time_val timeout = { 0, 500 };
+ pj_ioqueue_poll(endpt->ioqueue, &timeout);
+ }
+
+ return 0;
+}
+
+/**
+ * Create pool.
+ */
+PJ_DEF(pj_pool_t*) pjmedia_endpt_create_pool( pjmedia_endpt *endpt,
+ const char *name,
+ pj_size_t initial,
+ pj_size_t increment)
+{
+ pj_assert(endpt != NULL);
+
+ return pj_pool_create(endpt->pf, name, initial, increment, NULL);
+}
+
+/* Common initialization for both audio and video SDP media line */
+static pj_status_t init_sdp_media(pjmedia_sdp_media *m,
+ pj_pool_t *pool,
+ const pj_str_t *media_type,
+ const pjmedia_sock_info *sock_info)
+{
+ char tmp_addr[PJ_INET6_ADDRSTRLEN];
+ pjmedia_sdp_attr *attr;
+ const pj_sockaddr *addr;
+
+ pj_strdup(pool, &m->desc.media, media_type);
+
+ addr = &sock_info->rtp_addr_name;
+
+ /* Validate address family */
+ PJ_ASSERT_RETURN(addr->addr.sa_family == pj_AF_INET() ||
+ addr->addr.sa_family == pj_AF_INET6(),
+ PJ_EAFNOTSUP);
+
+ /* SDP connection line */
+ m->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
+ m->conn->net_type = STR_IN;
+ m->conn->addr_type = (addr->addr.sa_family==pj_AF_INET())? STR_IP4:STR_IP6;
+ pj_sockaddr_print(addr, tmp_addr, sizeof(tmp_addr), 0);
+ pj_strdup2(pool, &m->conn->addr, tmp_addr);
+
+ /* Port and transport in media description */
+ m->desc.port = pj_sockaddr_get_port(addr);
+ m->desc.port_count = 1;
+ pj_strdup (pool, &m->desc.transport, &STR_RTP_AVP);
+
+ /* Add "rtcp" attribute */
+#if defined(PJMEDIA_HAS_RTCP_IN_SDP) && PJMEDIA_HAS_RTCP_IN_SDP!=0
+ if (sock_info->rtcp_addr_name.addr.sa_family != 0) {
+ attr = pjmedia_sdp_attr_create_rtcp(pool, &sock_info->rtcp_addr_name);
+ if (attr)
+ pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
+ }
+#endif
+
+ /* Add sendrecv attribute. */
+ attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr);
+ attr->name = STR_SENDRECV;
+ m->attr[m->attr_count++] = attr;
+
+ return PJ_SUCCESS;
+}
+
+/* Create m=audio SDP media line */
+PJ_DEF(pj_status_t) pjmedia_endpt_create_audio_sdp(pjmedia_endpt *endpt,
+ pj_pool_t *pool,
+ const pjmedia_sock_info *si,
+ unsigned options,
+ pjmedia_sdp_media **p_m)
+{
+ const pj_str_t STR_AUDIO = { "audio", 5 };
+ pjmedia_sdp_media *m;
+ pjmedia_sdp_attr *attr;
+ unsigned i;
+ unsigned max_bitrate = 0;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(options);
+
+ /* Check that there are not too many codecs */
+ PJ_ASSERT_RETURN(endpt->codec_mgr.codec_cnt <= PJMEDIA_MAX_SDP_FMT,
+ PJ_ETOOMANY);
+
+ /* Create and init basic SDP media */
+ m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
+ status = init_sdp_media(m, pool, &STR_AUDIO, si);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Add format, rtpmap, and fmtp (when applicable) for each codec */
+ for (i=0; i<endpt->codec_mgr.codec_cnt; ++i) {
+
+ pjmedia_codec_info *codec_info;
+ pjmedia_sdp_rtpmap rtpmap;
+ char tmp_param[3];
+ pjmedia_codec_param codec_param;
+ pj_str_t *fmt;
+
+ if (endpt->codec_mgr.codec_desc[i].prio == PJMEDIA_CODEC_PRIO_DISABLED)
+ break;
+
+ codec_info = &endpt->codec_mgr.codec_desc[i].info;
+ pjmedia_codec_mgr_get_default_param(&endpt->codec_mgr, codec_info,
+ &codec_param);
+ fmt = &m->desc.fmt[m->desc.fmt_count++];
+
+ fmt->ptr = (char*) pj_pool_alloc(pool, 8);
+ fmt->slen = pj_utoa(codec_info->pt, fmt->ptr);
+
+ rtpmap.pt = *fmt;
+ rtpmap.enc_name = codec_info->encoding_name;
+
+#if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG != 0)
+ if (codec_info->pt == PJMEDIA_RTP_PT_G722)
+ rtpmap.clock_rate = 8000;
+ else
+ rtpmap.clock_rate = codec_info->clock_rate;
+#else
+ rtpmap.clock_rate = codec_info->clock_rate;
+#endif
+
+ /* For audio codecs, rtpmap parameters denotes the number
+ * of channels, which can be omited if the value is 1.
+ */
+ if (codec_info->type == PJMEDIA_TYPE_AUDIO &&
+ codec_info->channel_cnt > 1)
+ {
+ /* Can only support one digit channel count */
+ pj_assert(codec_info->channel_cnt < 10);
+
+ tmp_param[0] = (char)('0' + codec_info->channel_cnt);
+
+ rtpmap.param.ptr = tmp_param;
+ rtpmap.param.slen = 1;
+
+ } else {
+ rtpmap.param.ptr = "";
+ rtpmap.param.slen = 0;
+ }
+
+ if (codec_info->pt >= 96 || pjmedia_add_rtpmap_for_static_pt) {
+ pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr);
+ m->attr[m->attr_count++] = attr;
+ }
+
+ /* Add fmtp params */
+ if (codec_param.setting.dec_fmtp.cnt > 0) {
+ enum { MAX_FMTP_STR_LEN = 160 };
+ char buf[MAX_FMTP_STR_LEN];
+ unsigned buf_len = 0, i;
+ pjmedia_codec_fmtp *dec_fmtp = &codec_param.setting.dec_fmtp;
+
+ /* Print codec PT */
+ buf_len += pj_ansi_snprintf(buf,
+ MAX_FMTP_STR_LEN - buf_len,
+ "%d",
+ codec_info->pt);
+
+ for (i = 0; i < dec_fmtp->cnt; ++i) {
+ unsigned test_len = 2;
+
+ /* Check if buf still available */
+ test_len = dec_fmtp->param[i].val.slen +
+ dec_fmtp->param[i].name.slen;
+ if (test_len + buf_len >= MAX_FMTP_STR_LEN)
+ return PJ_ETOOBIG;
+
+ /* Print delimiter */
+ buf_len += pj_ansi_snprintf(&buf[buf_len],
+ MAX_FMTP_STR_LEN - buf_len,
+ (i == 0?" ":";"));
+
+ /* Print an fmtp param */
+ if (dec_fmtp->param[i].name.slen)
+ buf_len += pj_ansi_snprintf(
+ &buf[buf_len],
+ MAX_FMTP_STR_LEN - buf_len,
+ "%.*s=%.*s",
+ (int)dec_fmtp->param[i].name.slen,
+ dec_fmtp->param[i].name.ptr,
+ (int)dec_fmtp->param[i].val.slen,
+ dec_fmtp->param[i].val.ptr);
+ else
+ buf_len += pj_ansi_snprintf(&buf[buf_len],
+ MAX_FMTP_STR_LEN - buf_len,
+ "%.*s",
+ (int)dec_fmtp->param[i].val.slen,
+ dec_fmtp->param[i].val.ptr);
+ }
+
+ attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr);
+
+ attr->name = pj_str("fmtp");
+ attr->value = pj_strdup3(pool, buf);
+ m->attr[m->attr_count++] = attr;
+ }
+
+ /* Find maximum bitrate in this media */
+ if (max_bitrate < codec_param.info.max_bps)
+ max_bitrate = codec_param.info.max_bps;
+ }
+
+#if defined(PJMEDIA_RTP_PT_TELEPHONE_EVENTS) && \
+ PJMEDIA_RTP_PT_TELEPHONE_EVENTS != 0
+ /*
+ * Add support telephony event
+ */
+ if (endpt->has_telephone_event) {
+ m->desc.fmt[m->desc.fmt_count++] =
+ pj_str(PJMEDIA_RTP_PT_TELEPHONE_EVENTS_STR);
+
+ /* Add rtpmap. */
+ attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr);
+ attr->name = pj_str("rtpmap");
+ attr->value = pj_str(PJMEDIA_RTP_PT_TELEPHONE_EVENTS_STR
+ " telephone-event/8000");
+ m->attr[m->attr_count++] = attr;
+
+ /* Add fmtp */
+ attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr);
+ attr->name = pj_str("fmtp");
+ attr->value = pj_str(PJMEDIA_RTP_PT_TELEPHONE_EVENTS_STR " 0-15");
+ m->attr[m->attr_count++] = attr;
+ }
+#endif
+
+ /* Put bandwidth info in media level using bandwidth modifier "TIAS"
+ * (RFC3890).
+ */
+ if (max_bitrate) {
+ const pj_str_t STR_BANDW_MODIFIER = { "TIAS", 4 };
+ pjmedia_sdp_bandw *b;
+
+ b = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_bandw);
+ b->modifier = STR_BANDW_MODIFIER;
+ b->value = max_bitrate;
+ m->bandw[m->bandw_count++] = b;
+ }
+
+ *p_m = m;
+ return PJ_SUCCESS;
+}
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+/* Create m=video SDP media line */
+PJ_DEF(pj_status_t) pjmedia_endpt_create_video_sdp(pjmedia_endpt *endpt,
+ pj_pool_t *pool,
+ const pjmedia_sock_info *si,
+ unsigned options,
+ pjmedia_sdp_media **p_m)
+{
+
+
+ const pj_str_t STR_VIDEO = { "video", 5 };
+ pjmedia_sdp_media *m;
+ pjmedia_vid_codec_info codec_info[PJMEDIA_VID_CODEC_MGR_MAX_CODECS];
+ unsigned codec_prio[PJMEDIA_VID_CODEC_MGR_MAX_CODECS];
+ pjmedia_sdp_attr *attr;
+ unsigned cnt, i;
+ unsigned max_bitrate = 0;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(options);
+
+ /* Make sure video codec manager is instantiated */
+ if (!pjmedia_vid_codec_mgr_instance())
+ pjmedia_vid_codec_mgr_create(endpt->pool, NULL);
+
+ /* Create and init basic SDP media */
+ m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
+ status = init_sdp_media(m, pool, &STR_VIDEO, si);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ cnt = PJ_ARRAY_SIZE(codec_info);
+ status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &cnt,
+ codec_info, codec_prio);
+
+ /* Check that there are not too many codecs */
+ PJ_ASSERT_RETURN(0 <= PJMEDIA_MAX_SDP_FMT,
+ PJ_ETOOMANY);
+
+ /* Add format, rtpmap, and fmtp (when applicable) for each codec */
+ for (i=0; i<cnt; ++i) {
+ pjmedia_sdp_rtpmap rtpmap;
+ pjmedia_vid_codec_param codec_param;
+ pj_str_t *fmt;
+ pjmedia_video_format_detail *vfd;
+
+ pj_bzero(&rtpmap, sizeof(rtpmap));
+
+ if (codec_prio[i] == PJMEDIA_CODEC_PRIO_DISABLED)
+ break;
+
+ if (i > PJMEDIA_MAX_SDP_FMT) {
+ /* Too many codecs, perhaps it is better to tell application by
+ * returning appropriate status code.
+ */
+ PJ_PERROR(3,(THIS_FILE, PJ_ETOOMANY,
+ "Skipping some video codecs"));
+ break;
+ }
+
+ /* Must support RTP packetization and bidirectional */
+ if ((codec_info[i].packings & PJMEDIA_VID_PACKING_PACKETS) == 0 ||
+ codec_info[i].dir != PJMEDIA_DIR_ENCODING_DECODING)
+ {
+ continue;
+ }
+
+ pjmedia_vid_codec_mgr_get_default_param(NULL, &codec_info[i],
+ &codec_param);
+
+ fmt = &m->desc.fmt[m->desc.fmt_count++];
+ fmt->ptr = (char*) pj_pool_alloc(pool, 8);
+ fmt->slen = pj_utoa(codec_info[i].pt, fmt->ptr);
+ rtpmap.pt = *fmt;
+
+ /* Encoding name */
+ rtpmap.enc_name = codec_info[i].encoding_name;
+
+ /* Clock rate */
+ rtpmap.clock_rate = codec_info[i].clock_rate;
+
+ if (codec_info[i].pt >= 96 || pjmedia_add_rtpmap_for_static_pt) {
+ pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr);
+ m->attr[m->attr_count++] = attr;
+ }
+
+ /* Add fmtp params */
+ if (codec_param.dec_fmtp.cnt > 0) {
+ enum { MAX_FMTP_STR_LEN = 160 };
+ char buf[MAX_FMTP_STR_LEN];
+ unsigned buf_len = 0, j;
+ pjmedia_codec_fmtp *dec_fmtp = &codec_param.dec_fmtp;
+
+ /* Print codec PT */
+ buf_len += pj_ansi_snprintf(buf,
+ MAX_FMTP_STR_LEN - buf_len,
+ "%d",
+ codec_info[i].pt);
+
+ for (j = 0; j < dec_fmtp->cnt; ++j) {
+ unsigned test_len = 2;
+
+ /* Check if buf still available */
+ test_len = dec_fmtp->param[j].val.slen +
+ dec_fmtp->param[j].name.slen;
+ if (test_len + buf_len >= MAX_FMTP_STR_LEN)
+ return PJ_ETOOBIG;
+
+ /* Print delimiter */
+ buf_len += pj_ansi_snprintf(&buf[buf_len],
+ MAX_FMTP_STR_LEN - buf_len,
+ (j == 0?" ":";"));
+
+ /* Print an fmtp param */
+ if (dec_fmtp->param[j].name.slen)
+ buf_len += pj_ansi_snprintf(
+ &buf[buf_len],
+ MAX_FMTP_STR_LEN - buf_len,
+ "%.*s=%.*s",
+ (int)dec_fmtp->param[j].name.slen,
+ dec_fmtp->param[j].name.ptr,
+ (int)dec_fmtp->param[j].val.slen,
+ dec_fmtp->param[j].val.ptr);
+ else
+ buf_len += pj_ansi_snprintf(&buf[buf_len],
+ MAX_FMTP_STR_LEN - buf_len,
+ "%.*s",
+ (int)dec_fmtp->param[j].val.slen,
+ dec_fmtp->param[j].val.ptr);
+ }
+
+ attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr);
+
+ attr->name = pj_str("fmtp");
+ attr->value = pj_strdup3(pool, buf);
+ m->attr[m->attr_count++] = attr;
+ }
+
+ /* Find maximum bitrate in this media */
+ vfd = pjmedia_format_get_video_format_detail(&codec_param.enc_fmt,
+ PJ_TRUE);
+ if (vfd && max_bitrate < vfd->max_bps)
+ max_bitrate = vfd->max_bps;
+ }
+
+ /* Put bandwidth info in media level using bandwidth modifier "TIAS"
+ * (RFC3890).
+ */
+ if (max_bitrate) {
+ const pj_str_t STR_BANDW_MODIFIER = { "TIAS", 4 };
+ pjmedia_sdp_bandw *b;
+
+ b = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_bandw);
+ b->modifier = STR_BANDW_MODIFIER;
+ b->value = max_bitrate;
+ m->bandw[m->bandw_count++] = b;
+ }
+
+ *p_m = m;
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_HAS_VIDEO */
+
+
+/**
+ * Create a "blank" SDP session description. The SDP will contain basic SDP
+ * fields such as origin, time, and name, but without any media lines.
+ */
+PJ_DEF(pj_status_t) pjmedia_endpt_create_base_sdp( pjmedia_endpt *endpt,
+ pj_pool_t *pool,
+ const pj_str_t *sess_name,
+ const pj_sockaddr *origin,
+ pjmedia_sdp_session **p_sdp)
+{
+ pj_time_val tv;
+ pjmedia_sdp_session *sdp;
+
+ /* Sanity check arguments */
+ PJ_ASSERT_RETURN(endpt && pool && p_sdp, PJ_EINVAL);
+
+ sdp = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_session);
+
+ pj_gettimeofday(&tv);
+ sdp->origin.user = pj_str("-");
+ sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL;
+ sdp->origin.net_type = STR_IN;
+
+ if (origin->addr.sa_family == pj_AF_INET()) {
+ sdp->origin.addr_type = STR_IP4;
+ pj_strdup2(pool, &sdp->origin.addr,
+ pj_inet_ntoa(origin->ipv4.sin_addr));
+ } else if (origin->addr.sa_family == pj_AF_INET6()) {
+ char tmp_addr[PJ_INET6_ADDRSTRLEN];
+
+ sdp->origin.addr_type = STR_IP6;
+ pj_strdup2(pool, &sdp->origin.addr,
+ pj_sockaddr_print(origin, tmp_addr, sizeof(tmp_addr), 0));
+
+ } else {
+ pj_assert(!"Invalid address family");
+ return PJ_EAFNOTSUP;
+ }
+
+ if (sess_name)
+ pj_strdup(pool, &sdp->name, sess_name);
+ else
+ sdp->name = STR_SDP_NAME;
+
+ /* SDP time and attributes. */
+ sdp->time.start = sdp->time.stop = 0;
+ sdp->attr_count = 0;
+
+ /* Done */
+ *p_sdp = sdp;
+
+ return PJ_SUCCESS;
+}
+
+/**
+ * Create a SDP session description that describes the endpoint
+ * capability.
+ */
+PJ_DEF(pj_status_t) pjmedia_endpt_create_sdp( pjmedia_endpt *endpt,
+ pj_pool_t *pool,
+ unsigned stream_cnt,
+ const pjmedia_sock_info sock_info[],
+ pjmedia_sdp_session **p_sdp )
+{
+ const pj_sockaddr *addr0;
+ pjmedia_sdp_session *sdp;
+ pjmedia_sdp_media *m;
+ pj_status_t status;
+
+ /* Sanity check arguments */
+ PJ_ASSERT_RETURN(endpt && pool && p_sdp && stream_cnt, PJ_EINVAL);
+ PJ_ASSERT_RETURN(stream_cnt < PJMEDIA_MAX_SDP_MEDIA, PJ_ETOOMANY);
+
+ addr0 = &sock_info[0].rtp_addr_name;
+
+ /* Create and initialize basic SDP session */
+ status = pjmedia_endpt_create_base_sdp(endpt, pool, NULL, addr0, &sdp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Audio is first, by convention */
+ status = pjmedia_endpt_create_audio_sdp(endpt, pool,
+ &sock_info[0], 0, &m);
+ if (status != PJ_SUCCESS)
+ return status;
+ sdp->media[sdp->media_count++] = m;
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+ {
+ unsigned i;
+
+ /* The remaining stream, if any, are videos (by convention as well) */
+ for (i=1; i<stream_cnt; ++i) {
+ status = pjmedia_endpt_create_video_sdp(endpt, pool,
+ &sock_info[i], 0, &m);
+ if (status != PJ_SUCCESS)
+ return status;
+ sdp->media[sdp->media_count++] = m;
+ }
+ }
+#endif
+
+ /* Done */
+ *p_sdp = sdp;
+
+ return PJ_SUCCESS;
+}
+
+
+
+#if PJ_LOG_MAX_LEVEL >= 3
+static const char *good_number(char *buf, pj_int32_t val)
+{
+ if (val < 1000) {
+ pj_ansi_sprintf(buf, "%d", val);
+ } else if (val < 1000000) {
+ pj_ansi_sprintf(buf, "%d.%dK",
+ val / 1000,
+ (val % 1000) / 100);
+ } else {
+ pj_ansi_sprintf(buf, "%d.%02dM",
+ val / 1000000,
+ (val % 1000000) / 10000);
+ }
+
+ return buf;
+}
+#endif
+
+PJ_DEF(pj_status_t) pjmedia_endpt_dump(pjmedia_endpt *endpt)
+{
+
+#if PJ_LOG_MAX_LEVEL >= 3
+ unsigned i, count;
+ pjmedia_codec_info codec_info[32];
+ unsigned prio[32];
+
+ PJ_LOG(3,(THIS_FILE, "Dumping PJMEDIA capabilities:"));
+
+ count = PJ_ARRAY_SIZE(codec_info);
+ if (pjmedia_codec_mgr_enum_codecs(&endpt->codec_mgr,
+ &count, codec_info, prio) != PJ_SUCCESS)
+ {
+ PJ_LOG(3,(THIS_FILE, " -error: failed to enum codecs"));
+ return PJ_SUCCESS;
+ }
+
+ PJ_LOG(3,(THIS_FILE, " Total number of installed codecs: %d", count));
+ for (i=0; i<count; ++i) {
+ const char *type;
+ pjmedia_codec_param param;
+ char bps[32];
+
+ switch (codec_info[i].type) {
+ case PJMEDIA_TYPE_AUDIO:
+ type = "Audio"; break;
+ case PJMEDIA_TYPE_VIDEO:
+ type = "Video"; break;
+ default:
+ type = "Unknown type"; break;
+ }
+
+ if (pjmedia_codec_mgr_get_default_param(&endpt->codec_mgr,
+ &codec_info[i],
+ &param) != PJ_SUCCESS)
+ {
+ pj_bzero(&param, sizeof(pjmedia_codec_param));
+ }
+
+ PJ_LOG(3,(THIS_FILE,
+ " %s codec #%2d: pt=%d (%.*s @%dKHz/%d, %sbps, %dms%s%s%s%s%s)",
+ type, i, codec_info[i].pt,
+ (int)codec_info[i].encoding_name.slen,
+ codec_info[i].encoding_name.ptr,
+ codec_info[i].clock_rate/1000,
+ codec_info[i].channel_cnt,
+ good_number(bps, param.info.avg_bps),
+ param.info.frm_ptime * param.setting.frm_per_pkt,
+ (param.setting.vad ? " vad" : ""),
+ (param.setting.cng ? " cng" : ""),
+ (param.setting.plc ? " plc" : ""),
+ (param.setting.penh ? " penh" : ""),
+ (prio[i]==PJMEDIA_CODEC_PRIO_DISABLED?" disabled":"")));
+ }
+#endif
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_endpt_atexit( pjmedia_endpt *endpt,
+ pjmedia_endpt_exit_callback func)
+{
+ exit_cb *new_cb;
+
+ PJ_ASSERT_RETURN(endpt && func, PJ_EINVAL);
+
+ if (endpt->quit_flag)
+ return PJ_EINVALIDOP;
+
+ new_cb = PJ_POOL_ZALLOC_T(endpt->pool, exit_cb);
+ new_cb->func = func;
+
+ pj_enter_critical_section();
+ pj_list_push_back(&endpt->exit_cb_list, new_cb);
+ pj_leave_critical_section();
+
+ return PJ_SUCCESS;
+}
diff --git a/pjmedia/src/pjmedia/errno.c b/pjmedia/src/pjmedia/errno.c
new file mode 100644
index 0000000..6441463
--- /dev/null
+++ b/pjmedia/src/pjmedia/errno.c
@@ -0,0 +1,272 @@
+/* $Id: errno.c 3945 2012-01-27 09:12:59Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/errno.h>
+#include <pjmedia/types.h>
+#include <pj/string.h>
+#if defined(PJMEDIA_SOUND_IMPLEMENTATION) && \
+ PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_PORTAUDIO_SOUND
+# include <portaudio.h>
+#endif
+
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+PJ_BEGIN_DECL
+ const char* get_libsrtp_errstr(int err);
+PJ_END_DECL
+#endif
+
+
+/* PJMEDIA's own error codes/messages
+ * MUST KEEP THIS ARRAY SORTED!!
+ * Message must be limited to 64 chars!
+ */
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+static const struct
+{
+ int code;
+ const char *msg;
+} err_str[] =
+{
+ /* Generic PJMEDIA errors, shouldn't be used! */
+ PJ_BUILD_ERR( PJMEDIA_ERROR, "Unspecified PJMEDIA error" ),
+
+ /* SDP error. */
+ PJ_BUILD_ERR( PJMEDIA_SDP_EINSDP, "Invalid SDP descriptor" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EINVER, "Invalid SDP version line" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EINORIGIN, "Invalid SDP origin line" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EINTIME, "Invalid SDP time line"),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EINNAME, "SDP name/subject line is empty"),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EINCONN, "Invalid SDP connection line"),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EMISSINGCONN, "Missing SDP connection info line"),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EINATTR, "Invalid SDP attributes"),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EINRTPMAP, "Invalid SDP rtpmap attribute"),
+ PJ_BUILD_ERR( PJMEDIA_SDP_ERTPMAPTOOLONG,"SDP rtpmap attribute too long"),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EMISSINGRTPMAP,"Missing SDP rtpmap for dynamic payload type"),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EINMEDIA, "Invalid SDP media line" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_ENOFMT, "No SDP payload format in the media line" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EINPT, "Invalid SDP payload type in media line" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EINFMTP, "Invalid SDP fmtp attribute" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EINRTCP, "Invalid SDP rtcp attribyte" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EINPROTO, "Invalid SDP media transport protocol" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EINBANDW, "Invalid SDP bandwidth info line" ),
+
+ /* SDP negotiator errors. */
+ PJ_BUILD_ERR( PJMEDIA_SDPNEG_EINSTATE, "Invalid SDP negotiator state for operation" ),
+ PJ_BUILD_ERR( PJMEDIA_SDPNEG_ENOINITIAL, "No initial local SDP in SDP negotiator" ),
+ PJ_BUILD_ERR( PJMEDIA_SDPNEG_ENOACTIVE, "No active SDP in SDP negotiator" ),
+ PJ_BUILD_ERR( PJMEDIA_SDPNEG_ENONEG, "No current local/remote offer/answer" ),
+ PJ_BUILD_ERR( PJMEDIA_SDPNEG_EMISMEDIA, "SDP media count mismatch in offer/answer" ),
+ PJ_BUILD_ERR( PJMEDIA_SDPNEG_EINVANSMEDIA, "SDP media type mismatch in offer/answer" ),
+ PJ_BUILD_ERR( PJMEDIA_SDPNEG_EINVANSTP, "SDP media transport type mismatch in offer/answer" ),
+ PJ_BUILD_ERR( PJMEDIA_SDPNEG_EANSNOMEDIA, "No common SDP media payload in answer" ),
+ PJ_BUILD_ERR( PJMEDIA_SDPNEG_ENOMEDIA, "No active media stream after negotiation" ),
+ PJ_BUILD_ERR( PJMEDIA_SDPNEG_NOANSCODEC, "No suitable codec for remote offer"),
+ PJ_BUILD_ERR( PJMEDIA_SDPNEG_NOANSTELEVENT, "No suitable telephone-event for remote offer"),
+ PJ_BUILD_ERR( PJMEDIA_SDPNEG_NOANSUNKNOWN, "No suitable answer for unknown remote offer"),
+
+ /* SDP comparison results */
+ PJ_BUILD_ERR( PJMEDIA_SDP_EMEDIANOTEQUAL, "SDP media descriptor not equal" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EPORTNOTEQUAL, "Port in SDP media descriptor not equal" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_ETPORTNOTEQUAL, "Transport in SDP media descriptor not equal" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EFORMATNOTEQUAL, "Format in SDP media descriptor not equal" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_ECONNNOTEQUAL, "SDP connection line not equal" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EATTRNOTEQUAL, "SDP attributes not equal" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EDIRNOTEQUAL, "SDP media direction not equal" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EFMTPNOTEQUAL, "SDP fmtp attribute not equal" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_ERTPMAPNOTEQUAL, "SDP rtpmap attribute not equal" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_ESESSNOTEQUAL, "SDP session descriptor not equal" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_EORIGINNOTEQUAL, "SDP origin line not equal" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_ENAMENOTEQUAL, "SDP name/subject line not equal" ),
+ PJ_BUILD_ERR( PJMEDIA_SDP_ETIMENOTEQUAL, "SDP time line not equal" ),
+
+ /* Codec errors. */
+ PJ_BUILD_ERR( PJMEDIA_CODEC_EUNSUP, "Unsupported media codec" ),
+ PJ_BUILD_ERR( PJMEDIA_CODEC_EFAILED, "Codec internal creation error" ),
+ PJ_BUILD_ERR( PJMEDIA_CODEC_EFRMTOOSHORT, "Codec frame is too short" ),
+ PJ_BUILD_ERR( PJMEDIA_CODEC_EPCMTOOSHORT, "PCM frame is too short" ),
+ PJ_BUILD_ERR( PJMEDIA_CODEC_EFRMINLEN, "Invalid codec frame length" ),
+ PJ_BUILD_ERR( PJMEDIA_CODEC_EPCMFRMINLEN, "Invalid PCM frame length" ),
+ PJ_BUILD_ERR( PJMEDIA_CODEC_EINMODE, "Invalid codec mode (no fmtp?)" ),
+ PJ_BUILD_ERR( PJMEDIA_CODEC_EBADBITSTREAM, "Bad or corrupted bitstream" ),
+
+ /* Media errors. */
+ PJ_BUILD_ERR( PJMEDIA_EINVALIDIP, "Invalid remote media (IP) address" ),
+ PJ_BUILD_ERR( PJMEDIA_EASYMCODEC, "Asymetric media codec is not supported" ),
+ PJ_BUILD_ERR( PJMEDIA_EINVALIDPT, "Invalid media payload type" ),
+ PJ_BUILD_ERR( PJMEDIA_EMISSINGRTPMAP, "Missing rtpmap in media description" ),
+ PJ_BUILD_ERR( PJMEDIA_EINVALIMEDIATYPE, "Invalid media type" ),
+ PJ_BUILD_ERR( PJMEDIA_EREMOTENODTMF, "Remote does not support DTMF" ),
+ PJ_BUILD_ERR( PJMEDIA_RTP_EINDTMF, "Invalid DTMF digit" ),
+ PJ_BUILD_ERR( PJMEDIA_RTP_EREMNORFC2833,"Remote does not support RFC 2833" ),
+ PJ_BUILD_ERR( PJMEDIA_EBADFMT, "Bad format"),
+
+ /* RTP session errors. */
+ PJ_BUILD_ERR( PJMEDIA_RTP_EINPKT, "Invalid RTP packet" ),
+ PJ_BUILD_ERR( PJMEDIA_RTP_EINPACK, "Invalid RTP packing (internal error)" ),
+ PJ_BUILD_ERR( PJMEDIA_RTP_EINVER, "Invalid RTP version" ),
+ PJ_BUILD_ERR( PJMEDIA_RTP_EINSSRC, "RTP packet SSRC id mismatch" ),
+ PJ_BUILD_ERR( PJMEDIA_RTP_EINPT, "RTP packet payload type mismatch" ),
+ PJ_BUILD_ERR( PJMEDIA_RTP_EINLEN, "Invalid RTP packet length" ),
+ PJ_BUILD_ERR( PJMEDIA_RTP_ESESSRESTART, "RTP session restarted" ),
+ PJ_BUILD_ERR( PJMEDIA_RTP_ESESSPROBATION, "RTP session in probation" ),
+ PJ_BUILD_ERR( PJMEDIA_RTP_EBADSEQ, "Bad sequence number in RTP packet" ),
+ PJ_BUILD_ERR( PJMEDIA_RTP_EBADDEST, "RTP media port destination is not configured" ),
+ PJ_BUILD_ERR( PJMEDIA_RTP_ENOCONFIG, "RTP is not configured" ),
+
+ /* Media port errors: */
+ PJ_BUILD_ERR( PJMEDIA_ENOTCOMPATIBLE, "Media ports are not compatible" ),
+ PJ_BUILD_ERR( PJMEDIA_ENCCLOCKRATE, "Media ports have incompatible clock rate" ),
+ PJ_BUILD_ERR( PJMEDIA_ENCSAMPLESPFRAME, "Media ports have incompatible samples per frame" ),
+ PJ_BUILD_ERR( PJMEDIA_ENCTYPE, "Media ports have incompatible media type" ),
+ PJ_BUILD_ERR( PJMEDIA_ENCBITS, "Media ports have incompatible bits per sample" ),
+ PJ_BUILD_ERR( PJMEDIA_ENCBYTES, "Media ports have incompatible bytes per frame" ),
+ PJ_BUILD_ERR( PJMEDIA_ENCCHANNEL, "Media ports have incompatible number of channels" ),
+
+ /* Media file errors: */
+ PJ_BUILD_ERR( PJMEDIA_ENOTVALIDWAVE, "Not a valid WAVE file" ),
+ PJ_BUILD_ERR( PJMEDIA_EWAVEUNSUPP, "Unsupported WAVE file format" ),
+ PJ_BUILD_ERR( PJMEDIA_EWAVETOOSHORT, "WAVE file too short" ),
+ PJ_BUILD_ERR( PJMEDIA_EFRMFILETOOBIG, "Sound frame too large for file buffer"),
+ PJ_BUILD_ERR( PJMEDIA_EAVIUNSUPP, "Unsupported AVI file"),
+
+ /* Sound device errors: */
+ PJ_BUILD_ERR( PJMEDIA_ENOSNDREC, "No suitable sound capture device" ),
+ PJ_BUILD_ERR( PJMEDIA_ENOSNDPLAY, "No suitable sound playback device" ),
+ PJ_BUILD_ERR( PJMEDIA_ESNDINDEVID, "Invalid sound device ID" ),
+ PJ_BUILD_ERR( PJMEDIA_ESNDINSAMPLEFMT, "Invalid sample format for sound device" ),
+
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ /* SRTP transport errors: */
+ PJ_BUILD_ERR( PJMEDIA_SRTP_ECRYPTONOTMATCH, "SRTP crypto-suite name not match the offerer tag" ),
+ PJ_BUILD_ERR( PJMEDIA_SRTP_EINKEYLEN, "Invalid SRTP key length for specific crypto" ),
+ PJ_BUILD_ERR( PJMEDIA_SRTP_ENOTSUPCRYPTO, "Unsupported SRTP crypto-suite" ),
+ PJ_BUILD_ERR( PJMEDIA_SRTP_ESDPAMBIGUEANS, "SRTP SDP contains ambigue answer" ),
+ PJ_BUILD_ERR( PJMEDIA_SRTP_ESDPDUPCRYPTOTAG,"Duplicated SRTP crypto tag" ),
+ PJ_BUILD_ERR( PJMEDIA_SRTP_ESDPINCRYPTO, "Invalid SRTP crypto attribute" ),
+ PJ_BUILD_ERR( PJMEDIA_SRTP_ESDPINCRYPTOTAG, "Invalid SRTP crypto tag" ),
+ PJ_BUILD_ERR( PJMEDIA_SRTP_ESDPINTRANSPORT, "Invalid SDP media transport for SRTP" ),
+ PJ_BUILD_ERR( PJMEDIA_SRTP_ESDPREQCRYPTO, "SRTP crypto attribute required" ),
+ PJ_BUILD_ERR( PJMEDIA_SRTP_ESDPREQSECTP, "Secure transport required in SDP media descriptor" )
+#endif
+
+};
+
+#endif /* PJ_HAS_ERROR_STRING */
+
+
+
+/*
+ * pjmedia_strerror()
+ */
+PJ_DEF(pj_str_t) pjmedia_strerror( pj_status_t statcode,
+ char *buf, pj_size_t bufsize )
+{
+ pj_str_t errstr;
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+ /* See if the error comes from PortAudio. */
+#if defined(PJMEDIA_SOUND_IMPLEMENTATION) && \
+ PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_PORTAUDIO_SOUND
+ if (statcode >= PJMEDIA_PORTAUDIO_ERRNO_START &&
+ statcode <= PJMEDIA_PORTAUDIO_ERRNO_END)
+ {
+
+ //int pa_err = statcode - PJMEDIA_ERRNO_FROM_PORTAUDIO(0);
+ int pa_err = PJMEDIA_PORTAUDIO_ERRNO_START - statcode;
+ pj_str_t msg;
+
+ msg.ptr = (char*)Pa_GetErrorText(pa_err);
+ msg.slen = pj_ansi_strlen(msg.ptr);
+
+ errstr.ptr = buf;
+ pj_strncpy_with_null(&errstr, &msg, bufsize);
+ return errstr;
+
+ } else
+#endif /* PJMEDIA_SOUND_IMPLEMENTATION */
+
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ /* LIBSRTP error */
+ if (statcode >= PJMEDIA_LIBSRTP_ERRNO_START &&
+ statcode < PJMEDIA_LIBSRTP_ERRNO_END)
+ {
+ int err = statcode - PJMEDIA_LIBSRTP_ERRNO_START;
+ pj_str_t msg;
+
+ msg = pj_str((char*)get_libsrtp_errstr(err));
+
+ errstr.ptr = buf;
+ pj_strncpy_with_null(&errstr, &msg, bufsize);
+ return errstr;
+
+ } else
+#endif
+
+ /* PJMEDIA error */
+ if (statcode >= PJMEDIA_ERRNO_START &&
+ statcode < PJMEDIA_ERRNO_END)
+ {
+ /* Find the error in the table.
+ * Use binary search!
+ */
+ int first = 0;
+ int n = PJ_ARRAY_SIZE(err_str);
+
+ while (n > 0) {
+ int half = n/2;
+ int mid = first + half;
+
+ if (err_str[mid].code < statcode) {
+ first = mid+1;
+ n -= (half+1);
+ } else if (err_str[mid].code > statcode) {
+ n = half;
+ } else {
+ first = mid;
+ break;
+ }
+ }
+
+
+ if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) {
+ pj_str_t msg;
+
+ msg.ptr = (char*)err_str[first].msg;
+ msg.slen = pj_ansi_strlen(err_str[first].msg);
+
+ errstr.ptr = buf;
+ pj_strncpy_with_null(&errstr, &msg, bufsize);
+ return errstr;
+
+ }
+ }
+#endif /* PJ_HAS_ERROR_STRING */
+
+ /* Error not found. */
+ errstr.ptr = buf;
+ errstr.slen = pj_ansi_snprintf(buf, bufsize,
+ "Unknown pjmedia error %d",
+ statcode);
+
+ return errstr;
+}
+
diff --git a/pjmedia/src/pjmedia/event.c b/pjmedia/src/pjmedia/event.c
new file mode 100644
index 0000000..bc03ac2
--- /dev/null
+++ b/pjmedia/src/pjmedia/event.c
@@ -0,0 +1,377 @@
+/* $Id: event.c 3905 2011-12-09 05:15:39Z ming $ */
+/*
+ * Copyright (C) 2011-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia/event.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/list.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#define THIS_FILE "event.c"
+
+#define MAX_EVENTS 16
+
+typedef struct esub esub;
+
+struct esub
+{
+ PJ_DECL_LIST_MEMBER(esub);
+
+ pjmedia_event_cb *cb;
+ void *user_data;
+ void *epub;
+};
+
+typedef struct event_queue
+{
+ pjmedia_event events[MAX_EVENTS]; /**< array of events. */
+ int head, tail;
+ pj_bool_t is_full;
+} event_queue;
+
+struct pjmedia_event_mgr
+{
+ pj_pool_t *pool;
+ pj_thread_t *thread; /**< worker thread. */
+ pj_bool_t is_quitting;
+ pj_sem_t *sem;
+ pj_mutex_t *mutex;
+ event_queue ev_queue;
+ event_queue *pub_ev_queue; /**< publish() event queue. */
+ esub esub_list; /**< list of subscribers. */
+ esub free_esub_list; /**< list of subscribers. */
+ esub *th_next_sub, /**< worker thread's next sub. */
+ *pub_next_sub; /**< publish() next sub. */
+};
+
+static pjmedia_event_mgr *event_manager_instance;
+
+static pj_status_t event_queue_add_event(event_queue* ev_queue,
+ pjmedia_event *event)
+{
+ if (ev_queue->is_full) {
+ char ev_name[5];
+
+ /* This event will be ignored. */
+ PJ_LOG(4, (THIS_FILE, "Lost event %s from publisher [0x%p] "
+ "due to full queue.",
+ pjmedia_fourcc_name(event->type, ev_name),
+ event->epub));
+
+ return PJ_ETOOMANY;
+ }
+
+ pj_memcpy(&ev_queue->events[ev_queue->tail], event, sizeof(*event));
+ ev_queue->tail = (ev_queue->tail + 1) % MAX_EVENTS;
+ if (ev_queue->tail == ev_queue->head)
+ ev_queue->is_full = PJ_TRUE;
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t event_mgr_distribute_events(pjmedia_event_mgr *mgr,
+ event_queue *ev_queue,
+ esub **next_sub,
+ pj_bool_t rls_lock)
+{
+ pj_status_t err = PJ_SUCCESS;
+ esub * sub = mgr->esub_list.next;
+ pjmedia_event *ev = &ev_queue->events[ev_queue->head];
+
+ while (sub != &mgr->esub_list) {
+ *next_sub = sub->next;
+
+ /* Check if the subscriber is interested in
+ * receiving the event from the publisher.
+ */
+ if (sub->epub == ev->epub || !sub->epub) {
+ pjmedia_event_cb *cb = sub->cb;
+ void *user_data = sub->user_data;
+ pj_status_t status;
+
+ if (rls_lock)
+ pj_mutex_unlock(mgr->mutex);
+
+ status = (*cb)(ev, user_data);
+ if (status != PJ_SUCCESS && err == PJ_SUCCESS)
+ err = status;
+
+ if (rls_lock)
+ pj_mutex_lock(mgr->mutex);
+ }
+ sub = *next_sub;
+ }
+ *next_sub = NULL;
+
+ ev_queue->head = (ev_queue->head + 1) % MAX_EVENTS;
+ ev_queue->is_full = PJ_FALSE;
+
+ return err;
+}
+
+/* Event worker thread function. */
+static int event_worker_thread(void *arg)
+{
+ pjmedia_event_mgr *mgr = (pjmedia_event_mgr *)arg;
+
+ while (1) {
+ /* Wait until there is an event. */
+ pj_sem_wait(mgr->sem);
+
+ if (mgr->is_quitting)
+ break;
+
+ pj_mutex_lock(mgr->mutex);
+ event_mgr_distribute_events(mgr, &mgr->ev_queue,
+ &mgr->th_next_sub, PJ_TRUE);
+ pj_mutex_unlock(mgr->mutex);
+ }
+
+ return 0;
+}
+
+PJ_DEF(pj_status_t) pjmedia_event_mgr_create(pj_pool_t *pool,
+ unsigned options,
+ pjmedia_event_mgr **p_mgr)
+{
+ pjmedia_event_mgr *mgr;
+ pj_status_t status;
+
+ mgr = PJ_POOL_ZALLOC_T(pool, pjmedia_event_mgr);
+ mgr->pool = pj_pool_create(pool->factory, "evt mgr", 500, 500, NULL);
+ pj_list_init(&mgr->esub_list);
+ pj_list_init(&mgr->free_esub_list);
+
+ if (!(options & PJMEDIA_EVENT_MGR_NO_THREAD)) {
+ status = pj_sem_create(mgr->pool, "ev_sem", 0, MAX_EVENTS + 1,
+ &mgr->sem);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = pj_thread_create(mgr->pool, "ev_thread",
+ &event_worker_thread,
+ mgr, 0, 0, &mgr->thread);
+ if (status != PJ_SUCCESS) {
+ pjmedia_event_mgr_destroy(mgr);
+ return status;
+ }
+ }
+
+ status = pj_mutex_create_recursive(mgr->pool, "ev_mutex", &mgr->mutex);
+ if (status != PJ_SUCCESS) {
+ pjmedia_event_mgr_destroy(mgr);
+ return status;
+ }
+
+ if (!event_manager_instance)
+ event_manager_instance = mgr;
+
+ if (p_mgr)
+ *p_mgr = mgr;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pjmedia_event_mgr*) pjmedia_event_mgr_instance(void)
+{
+ return event_manager_instance;
+}
+
+PJ_DEF(void) pjmedia_event_mgr_set_instance(pjmedia_event_mgr *mgr)
+{
+ event_manager_instance = mgr;
+}
+
+PJ_DEF(void) pjmedia_event_mgr_destroy(pjmedia_event_mgr *mgr)
+{
+ if (!mgr) mgr = pjmedia_event_mgr_instance();
+ PJ_ASSERT_ON_FAIL(mgr != NULL, return);
+
+ if (mgr->thread) {
+ mgr->is_quitting = PJ_TRUE;
+ pj_sem_post(mgr->sem);
+ pj_thread_join(mgr->thread);
+ }
+
+ if (mgr->sem) {
+ pj_sem_destroy(mgr->sem);
+ mgr->sem = NULL;
+ }
+
+ if (mgr->mutex) {
+ pj_mutex_destroy(mgr->mutex);
+ mgr->mutex = NULL;
+ }
+
+ if (mgr->pool)
+ pj_pool_release(mgr->pool);
+
+ if (event_manager_instance == mgr)
+ event_manager_instance = NULL;
+}
+
+PJ_DEF(void) pjmedia_event_init( pjmedia_event *event,
+ pjmedia_event_type type,
+ const pj_timestamp *ts,
+ const void *src)
+{
+ pj_bzero(event, sizeof(*event));
+ event->type = type;
+ if (ts)
+ event->timestamp.u64 = ts->u64;
+ event->epub = event->src = src;
+}
+
+PJ_DEF(pj_status_t) pjmedia_event_subscribe( pjmedia_event_mgr *mgr,
+ pjmedia_event_cb *cb,
+ void *user_data,
+ void *epub)
+{
+ esub *sub;
+
+ PJ_ASSERT_RETURN(cb, PJ_EINVAL);
+
+ if (!mgr) mgr = pjmedia_event_mgr_instance();
+ PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+ pj_mutex_lock(mgr->mutex);
+ /* Check whether callback function with the same user data is already
+ * subscribed to the publisher. This is to prevent the callback function
+ * receiving the same event from the same publisher more than once.
+ */
+ sub = mgr->esub_list.next;
+ while (sub != &mgr->esub_list) {
+ esub *next = sub->next;
+ if (sub->cb == cb && sub->user_data == user_data &&
+ sub->epub == epub)
+ {
+ pj_mutex_unlock(mgr->mutex);
+ return PJ_SUCCESS;
+ }
+ sub = next;
+ }
+
+ if (mgr->free_esub_list.next != &mgr->free_esub_list) {
+ sub = mgr->free_esub_list.next;
+ pj_list_erase(sub);
+ } else
+ sub = PJ_POOL_ZALLOC_T(mgr->pool, esub);
+ sub->cb = cb;
+ sub->user_data = user_data;
+ sub->epub = epub;
+ pj_list_push_back(&mgr->esub_list, sub);
+ pj_mutex_unlock(mgr->mutex);
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t)
+pjmedia_event_unsubscribe(pjmedia_event_mgr *mgr,
+ pjmedia_event_cb *cb,
+ void *user_data,
+ void *epub)
+{
+ esub *sub;
+
+ PJ_ASSERT_RETURN(cb, PJ_EINVAL);
+
+ if (!mgr) mgr = pjmedia_event_mgr_instance();
+ PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+ pj_mutex_lock(mgr->mutex);
+ sub = mgr->esub_list.next;
+ while (sub != &mgr->esub_list) {
+ esub *next = sub->next;
+ if (sub->cb == cb && (sub->user_data == user_data || !user_data) &&
+ (sub->epub == epub || !epub))
+ {
+ /* If the worker thread or pjmedia_event_publish() API is
+ * in the process of distributing events, make sure that
+ * its pointer to the next subscriber stays valid.
+ */
+ if (mgr->th_next_sub == sub)
+ mgr->th_next_sub = sub->next;
+ if (mgr->pub_next_sub == sub)
+ mgr->pub_next_sub = sub->next;
+ pj_list_erase(sub);
+ pj_list_push_back(&mgr->free_esub_list, sub);
+ if (user_data && epub)
+ break;
+ }
+ sub = next;
+ }
+ pj_mutex_unlock(mgr->mutex);
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_event_publish( pjmedia_event_mgr *mgr,
+ void *epub,
+ pjmedia_event *event,
+ pjmedia_event_publish_flag flag)
+{
+ pj_status_t err = PJ_SUCCESS;
+
+ PJ_ASSERT_RETURN(epub && event, PJ_EINVAL);
+
+ if (!mgr) mgr = pjmedia_event_mgr_instance();
+ PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+ event->epub = epub;
+
+ pj_mutex_lock(mgr->mutex);
+ if (flag & PJMEDIA_EVENT_PUBLISH_POST_EVENT) {
+ if (event_queue_add_event(&mgr->ev_queue, event) == PJ_SUCCESS)
+ pj_sem_post(mgr->sem);
+ } else {
+ /* For nested pjmedia_event_publish() calls, i.e. calling publish()
+ * inside the subscriber's callback, the function will only add
+ * the event to the event queue of the first publish() call. It
+ * is the first publish() call that will be responsible to
+ * distribute the events.
+ */
+ if (mgr->pub_ev_queue) {
+ event_queue_add_event(mgr->pub_ev_queue, event);
+ } else {
+ static event_queue ev_queue;
+ pj_status_t status;
+
+ ev_queue.head = ev_queue.tail = 0;
+ ev_queue.is_full = PJ_FALSE;
+ mgr->pub_ev_queue = &ev_queue;
+
+ event_queue_add_event(mgr->pub_ev_queue, event);
+
+ do {
+ status = event_mgr_distribute_events(mgr, mgr->pub_ev_queue,
+ &mgr->pub_next_sub,
+ PJ_FALSE);
+ if (status != PJ_SUCCESS && err == PJ_SUCCESS)
+ err = status;
+ } while(ev_queue.head != ev_queue.tail || ev_queue.is_full);
+
+ mgr->pub_ev_queue = NULL;
+ }
+ }
+ pj_mutex_unlock(mgr->mutex);
+
+ return err;
+}
diff --git a/pjmedia/src/pjmedia/ffmpeg_util.c b/pjmedia/src/pjmedia/ffmpeg_util.c
new file mode 100644
index 0000000..97e0719
--- /dev/null
+++ b/pjmedia/src/pjmedia/ffmpeg_util.c
@@ -0,0 +1,204 @@
+/* $Id: ffmpeg_util.c 4158 2012-06-06 09:56:14Z nanang $ */
+/*
+ * Copyright (C) 2010-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia/types.h>
+#include <pj/errno.h>
+#include <pj/log.h>
+#include <pj/string.h>
+
+#if PJMEDIA_HAS_LIBAVFORMAT && PJMEDIA_HAS_LIBAVUTIL
+
+#include "ffmpeg_util.h"
+#include <libavformat/avformat.h>
+
+#define MAKE_VER(mj,mn,mi) ((mj << 16) | (mn << 8) | (mi << 0))
+#define VER_AT_LEAST(mj,mn,mi) (MAKE_VER(LIBAVUTIL_VERSION_MAJOR, \
+ LIBAVUTIL_VERSION_MINOR, \
+ LIBAVUTIL_VERSION_MICRO) >= \
+ MAKE_VER(mj,mn,mi))
+
+
+
+/* Conversion table between pjmedia_format_id and PixelFormat */
+static const struct ffmpeg_fmt_table_t
+{
+ pjmedia_format_id id;
+ enum PixelFormat pf;
+} ffmpeg_fmt_table[] =
+{
+ { PJMEDIA_FORMAT_RGBA, PIX_FMT_RGBA},
+ { PJMEDIA_FORMAT_RGB24,PIX_FMT_BGR24},
+ { PJMEDIA_FORMAT_BGRA, PIX_FMT_BGRA},
+#if VER_AT_LEAST(51,20,1)
+ { PJMEDIA_FORMAT_GBRP, PIX_FMT_GBR24P},
+#endif
+
+ { PJMEDIA_FORMAT_AYUV, PIX_FMT_NONE},
+ { PJMEDIA_FORMAT_YUY2, PIX_FMT_YUYV422},
+ { PJMEDIA_FORMAT_UYVY, PIX_FMT_UYVY422},
+ { PJMEDIA_FORMAT_I420, PIX_FMT_YUV420P},
+ //{ PJMEDIA_FORMAT_YV12, PIX_FMT_YUV420P},
+ { PJMEDIA_FORMAT_I422, PIX_FMT_YUV422P},
+ { PJMEDIA_FORMAT_I420JPEG, PIX_FMT_YUVJ420P},
+ { PJMEDIA_FORMAT_I422JPEG, PIX_FMT_YUVJ422P},
+};
+
+/* Conversion table between pjmedia_format_id and CodecID */
+static const struct ffmpeg_codec_table_t
+{
+ pjmedia_format_id id;
+ enum CodecID codec_id;
+} ffmpeg_codec_table[] =
+{
+ {PJMEDIA_FORMAT_H261, CODEC_ID_H261},
+ {PJMEDIA_FORMAT_H263, CODEC_ID_H263},
+ {PJMEDIA_FORMAT_H263P, CODEC_ID_H263P},
+ {PJMEDIA_FORMAT_H264, CODEC_ID_H264},
+ {PJMEDIA_FORMAT_MPEG1VIDEO, CODEC_ID_MPEG1VIDEO},
+ {PJMEDIA_FORMAT_MPEG2VIDEO, CODEC_ID_MPEG2VIDEO},
+ {PJMEDIA_FORMAT_MPEG4, CODEC_ID_MPEG4},
+ {PJMEDIA_FORMAT_MJPEG, CODEC_ID_MJPEG}
+};
+
+static int pjmedia_ffmpeg_ref_cnt;
+
+static void ffmpeg_log_cb(void* ptr, int level, const char* fmt, va_list vl);
+
+void pjmedia_ffmpeg_add_ref()
+{
+ if (pjmedia_ffmpeg_ref_cnt++ == 0) {
+ av_log_set_level(AV_LOG_ERROR);
+ av_log_set_callback(&ffmpeg_log_cb);
+ av_register_all();
+ }
+}
+
+void pjmedia_ffmpeg_dec_ref()
+{
+ if (pjmedia_ffmpeg_ref_cnt-- == 1) {
+ /* How to shutdown ffmpeg? */
+ }
+
+ if (pjmedia_ffmpeg_ref_cnt < 0) pjmedia_ffmpeg_ref_cnt = 0;
+}
+
+
+static void ffmpeg_log_cb(void* ptr, int level, const char* fmt, va_list vl)
+{
+ const char *LOG_SENDER = "ffmpeg";
+ enum { LOG_LEVEL = 5 };
+ char buf[100];
+ int bufsize = sizeof(buf), len;
+ pj_str_t fmt_st;
+
+ /* Custom callback needs to filter log level by itself */
+ if (level > av_log_get_level())
+ return;
+
+ /* Add original ffmpeg sender to log format */
+ if (ptr) {
+ AVClass* avc = *(AVClass**)ptr;
+ len = pj_ansi_snprintf(buf, bufsize, "%s: ", avc->item_name(ptr));
+ bufsize -= len;
+ }
+
+ /* Copy original log format */
+ len = pj_ansi_strlen(fmt);
+ if (len > bufsize-1)
+ len = bufsize-1;
+ pj_memcpy(buf+sizeof(buf)-bufsize, fmt, len);
+ bufsize -= len;
+
+ /* Trim log format */
+ pj_strset(&fmt_st, buf, sizeof(buf)-bufsize);
+ pj_strrtrim(&fmt_st);
+ buf[fmt_st.slen] = '\0';
+
+ pj_log(LOG_SENDER, LOG_LEVEL, buf, vl);
+}
+
+
+pj_status_t pjmedia_format_id_to_PixelFormat(pjmedia_format_id fmt_id,
+ enum PixelFormat *pixel_format)
+{
+ unsigned i;
+ for (i=0; i<PJ_ARRAY_SIZE(ffmpeg_fmt_table); ++i) {
+ const struct ffmpeg_fmt_table_t *t = &ffmpeg_fmt_table[i];
+ if (t->id==fmt_id && t->pf != PIX_FMT_NONE) {
+ *pixel_format = t->pf;
+ return PJ_SUCCESS;
+ }
+ }
+
+ *pixel_format = PIX_FMT_NONE;
+ return PJ_ENOTFOUND;
+}
+
+pj_status_t PixelFormat_to_pjmedia_format_id(enum PixelFormat pf,
+ pjmedia_format_id *fmt_id)
+{
+ unsigned i;
+ for (i=0; i<PJ_ARRAY_SIZE(ffmpeg_fmt_table); ++i) {
+ const struct ffmpeg_fmt_table_t *t = &ffmpeg_fmt_table[i];
+ if (t->pf == pf) {
+ if (fmt_id) *fmt_id = t->id;
+ return PJ_SUCCESS;
+ }
+ }
+
+ return PJ_ENOTFOUND;
+}
+
+pj_status_t pjmedia_format_id_to_CodecID(pjmedia_format_id fmt_id,
+ enum CodecID *codec_id)
+{
+ unsigned i;
+ for (i=0; i<PJ_ARRAY_SIZE(ffmpeg_codec_table); ++i) {
+ const struct ffmpeg_codec_table_t *t = &ffmpeg_codec_table[i];
+ if (t->id==fmt_id && t->codec_id != PIX_FMT_NONE) {
+ *codec_id = t->codec_id;
+ return PJ_SUCCESS;
+ }
+ }
+
+ *codec_id = PIX_FMT_NONE;
+ return PJ_ENOTFOUND;
+}
+
+pj_status_t CodecID_to_pjmedia_format_id(enum CodecID codec_id,
+ pjmedia_format_id *fmt_id)
+{
+ unsigned i;
+ for (i=0; i<PJ_ARRAY_SIZE(ffmpeg_codec_table); ++i) {
+ const struct ffmpeg_codec_table_t *t = &ffmpeg_codec_table[i];
+ if (t->codec_id == codec_id) {
+ if (fmt_id) *fmt_id = t->id;
+ return PJ_SUCCESS;
+ }
+ }
+
+ return PJ_ENOTFOUND;
+}
+
+
+#ifdef _MSC_VER
+# pragma comment( lib, "avformat.lib")
+# pragma comment( lib, "avutil.lib")
+#endif
+
+#endif /* #if PJMEDIA_HAS_LIBAVFORMAT && PJMEDIA_HAS_LIBAVUTIL */
diff --git a/pjmedia/src/pjmedia/ffmpeg_util.h b/pjmedia/src/pjmedia/ffmpeg_util.h
new file mode 100644
index 0000000..2804c9f
--- /dev/null
+++ b/pjmedia/src/pjmedia/ffmpeg_util.h
@@ -0,0 +1,55 @@
+/* $Id: ffmpeg_util.h 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2010-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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
+ */
+
+/*
+ * This file contains common utilities that are useful for pjmedia components
+ * that use ffmpeg. This is not a public API.
+ */
+
+#ifndef __PJMEDIA_FFMPEG_UTIL_H__
+#define __PJMEDIA_FFMPEG_UTIL_H__
+
+#include <pjmedia/format.h>
+
+#ifdef _MSC_VER
+# ifndef __cplusplus
+# define inline _inline
+# endif
+# pragma warning(disable:4244) /* possible loss of data */
+#endif
+
+#include <libavutil/avutil.h>
+#include <libavcodec/avcodec.h>
+
+void pjmedia_ffmpeg_add_ref();
+void pjmedia_ffmpeg_dec_ref();
+
+pj_status_t pjmedia_format_id_to_PixelFormat(pjmedia_format_id fmt_id,
+ enum PixelFormat *pixel_format);
+
+pj_status_t PixelFormat_to_pjmedia_format_id(enum PixelFormat pf,
+ pjmedia_format_id *fmt_id);
+
+pj_status_t pjmedia_format_id_to_CodecID(pjmedia_format_id fmt_id,
+ enum CodecID *codec_id);
+
+pj_status_t CodecID_to_pjmedia_format_id(enum CodecID codec_id,
+ pjmedia_format_id *fmt_id);
+
+#endif /* __PJMEDIA_FFMPEG_UTIL_H__ */
diff --git a/pjmedia/src/pjmedia/format.c b/pjmedia/src/pjmedia/format.c
new file mode 100644
index 0000000..5e1d253
--- /dev/null
+++ b/pjmedia/src/pjmedia/format.c
@@ -0,0 +1,416 @@
+/* $Id: format.c 4158 2012-06-06 09:56:14Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/format.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+PJ_DEF(void) pjmedia_format_init_audio( pjmedia_format *fmt,
+ pj_uint32_t fmt_id,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned bits_per_sample,
+ unsigned frame_time_usec,
+ pj_uint32_t avg_bps,
+ pj_uint32_t max_bps)
+{
+ fmt->id = fmt_id;
+ fmt->type = PJMEDIA_TYPE_AUDIO;
+ fmt->detail_type = PJMEDIA_FORMAT_DETAIL_AUDIO;
+
+ fmt->det.aud.clock_rate = clock_rate;
+ fmt->det.aud.channel_count = channel_count;
+ fmt->det.aud.bits_per_sample = bits_per_sample;
+ fmt->det.aud.frame_time_usec = frame_time_usec;
+ fmt->det.aud.avg_bps = avg_bps;
+ fmt->det.aud.max_bps = max_bps;
+}
+
+
+PJ_DEF(pjmedia_audio_format_detail*)
+pjmedia_format_get_audio_format_detail(const pjmedia_format *fmt,
+ pj_bool_t assert_valid)
+{
+ if (fmt->detail_type==PJMEDIA_FORMAT_DETAIL_AUDIO) {
+ return (pjmedia_audio_format_detail*) &fmt->det.aud;
+ } else {
+ /* Get rid of unused var compiler warning if pj_assert()
+ * macro does not do anything
+ */
+ PJ_UNUSED_ARG(assert_valid);
+ pj_assert(!assert_valid || !"Invalid audio format detail");
+ return NULL;
+ }
+}
+
+
+PJ_DEF(pjmedia_format*) pjmedia_format_copy(pjmedia_format *dst,
+ const pjmedia_format *src)
+{
+ return (pjmedia_format*)pj_memcpy(dst, src, sizeof(*src));
+}
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+
+static pj_status_t apply_packed_fmt(const pjmedia_video_format_info *fi,
+ pjmedia_video_apply_fmt_param *aparam);
+
+static pj_status_t apply_planar_420(const pjmedia_video_format_info *fi,
+ pjmedia_video_apply_fmt_param *aparam);
+
+static pj_status_t apply_planar_422(const pjmedia_video_format_info *fi,
+ pjmedia_video_apply_fmt_param *aparam);
+
+static pj_status_t apply_planar_444(const pjmedia_video_format_info *fi,
+ pjmedia_video_apply_fmt_param *aparam);
+
+struct pjmedia_video_format_mgr
+{
+ unsigned max_info;
+ unsigned info_cnt;
+ pjmedia_video_format_info **infos;
+};
+
+static pjmedia_video_format_mgr *video_format_mgr_instance;
+static pjmedia_video_format_info built_in_vid_fmt_info[] =
+{
+ {PJMEDIA_FORMAT_RGB24, "RGB24", PJMEDIA_COLOR_MODEL_RGB, 24, 1, &apply_packed_fmt},
+ {PJMEDIA_FORMAT_RGBA, "RGBA", PJMEDIA_COLOR_MODEL_RGB, 32, 1, &apply_packed_fmt},
+ {PJMEDIA_FORMAT_BGRA, "BGRA", PJMEDIA_COLOR_MODEL_RGB, 32, 1, &apply_packed_fmt},
+ {PJMEDIA_FORMAT_DIB , "DIB ", PJMEDIA_COLOR_MODEL_RGB, 24, 1, &apply_packed_fmt},
+ {PJMEDIA_FORMAT_GBRP, "GBRP", PJMEDIA_COLOR_MODEL_RGB, 24, 3, &apply_planar_444},
+ {PJMEDIA_FORMAT_AYUV, "AYUV", PJMEDIA_COLOR_MODEL_YUV, 32, 1, &apply_packed_fmt},
+ {PJMEDIA_FORMAT_YUY2, "YUY2", PJMEDIA_COLOR_MODEL_YUV, 16, 1, &apply_packed_fmt},
+ {PJMEDIA_FORMAT_UYVY, "UYVY", PJMEDIA_COLOR_MODEL_YUV, 16, 1, &apply_packed_fmt},
+ {PJMEDIA_FORMAT_YVYU, "YVYU", PJMEDIA_COLOR_MODEL_YUV, 16, 1, &apply_packed_fmt},
+ {PJMEDIA_FORMAT_I420, "I420", PJMEDIA_COLOR_MODEL_YUV, 12, 3, &apply_planar_420},
+ {PJMEDIA_FORMAT_YV12, "YV12", PJMEDIA_COLOR_MODEL_YUV, 12, 3, &apply_planar_420},
+ {PJMEDIA_FORMAT_I422, "I422", PJMEDIA_COLOR_MODEL_YUV, 16, 3, &apply_planar_422},
+ {PJMEDIA_FORMAT_I420JPEG, "I420JPG", PJMEDIA_COLOR_MODEL_YUV, 12, 3, &apply_planar_420},
+ {PJMEDIA_FORMAT_I422JPEG, "I422JPG", PJMEDIA_COLOR_MODEL_YUV, 16, 3, &apply_planar_422},
+};
+
+PJ_DEF(void) pjmedia_format_init_video( pjmedia_format *fmt,
+ pj_uint32_t fmt_id,
+ unsigned width,
+ unsigned height,
+ unsigned fps_num,
+ unsigned fps_denum)
+{
+ pj_assert(fps_denum);
+ fmt->id = fmt_id;
+ fmt->type = PJMEDIA_TYPE_VIDEO;
+ fmt->detail_type = PJMEDIA_FORMAT_DETAIL_VIDEO;
+
+ fmt->det.vid.size.w = width;
+ fmt->det.vid.size.h = height;
+ fmt->det.vid.fps.num = fps_num;
+ fmt->det.vid.fps.denum = fps_denum;
+ fmt->det.vid.avg_bps = fmt->det.vid.max_bps = 0;
+
+ if (pjmedia_video_format_mgr_instance()) {
+ const pjmedia_video_format_info *vfi;
+ pjmedia_video_apply_fmt_param vafp;
+ pj_uint32_t bps;
+
+ vfi = pjmedia_get_video_format_info(NULL, fmt->id);
+ if (vfi) {
+ pj_bzero(&vafp, sizeof(vafp));
+ vafp.size = fmt->det.vid.size;
+ vfi->apply_fmt(vfi, &vafp);
+
+ bps = vafp.framebytes * fps_num * (pj_size_t)8 / fps_denum;
+ fmt->det.vid.avg_bps = fmt->det.vid.max_bps = bps;
+ }
+ }
+}
+
+PJ_DEF(pjmedia_video_format_detail*)
+pjmedia_format_get_video_format_detail(const pjmedia_format *fmt,
+ pj_bool_t assert_valid)
+{
+ if (fmt->detail_type==PJMEDIA_FORMAT_DETAIL_VIDEO) {
+ return (pjmedia_video_format_detail*)&fmt->det.vid;
+ } else {
+ pj_assert(!assert_valid || !"Invalid video format detail");
+ return NULL;
+ }
+}
+
+
+static pj_status_t apply_packed_fmt(const pjmedia_video_format_info *fi,
+ pjmedia_video_apply_fmt_param *aparam)
+{
+ unsigned i;
+ pj_size_t stride;
+
+ stride = (pj_size_t)((aparam->size.w*fi->bpp) >> 3);
+
+ /* Calculate memsize */
+ aparam->framebytes = stride * aparam->size.h;
+
+ /* Packed formats only use 1 plane */
+ aparam->planes[0] = aparam->buffer;
+ aparam->strides[0] = stride;
+ aparam->plane_bytes[0] = aparam->framebytes;
+
+ /* Zero unused planes */
+ for (i=1; i<PJMEDIA_MAX_VIDEO_PLANES; ++i) {
+ aparam->strides[i] = 0;
+ aparam->planes[i] = NULL;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t apply_planar_420(const pjmedia_video_format_info *fi,
+ pjmedia_video_apply_fmt_param *aparam)
+{
+ unsigned i;
+ pj_size_t Y_bytes;
+
+ PJ_UNUSED_ARG(fi);
+
+ /* Calculate memsize */
+ Y_bytes = (pj_size_t)(aparam->size.w * aparam->size.h);
+ aparam->framebytes = Y_bytes + (Y_bytes>>1);
+
+ /* Planar formats use 3 plane */
+ aparam->strides[0] = aparam->size.w;
+ aparam->strides[1] = aparam->strides[2] = (aparam->size.w>>1);
+
+ aparam->planes[0] = aparam->buffer;
+ aparam->planes[1] = aparam->planes[0] + Y_bytes;
+ aparam->planes[2] = aparam->planes[1] + (Y_bytes>>2);
+
+ aparam->plane_bytes[0] = Y_bytes;
+ aparam->plane_bytes[1] = aparam->plane_bytes[2] = (Y_bytes>>2);
+
+ /* Zero unused planes */
+ for (i=3; i<PJMEDIA_MAX_VIDEO_PLANES; ++i) {
+ aparam->strides[i] = 0;
+ aparam->planes[i] = NULL;
+ aparam->plane_bytes[i] = 0;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t apply_planar_422(const pjmedia_video_format_info *fi,
+ pjmedia_video_apply_fmt_param *aparam)
+{
+ unsigned i;
+ pj_size_t Y_bytes;
+
+ PJ_UNUSED_ARG(fi);
+
+ /* Calculate memsize */
+ Y_bytes = (pj_size_t)(aparam->size.w * aparam->size.h);
+ aparam->framebytes = (Y_bytes << 1);
+
+ /* Planar formats use 3 plane */
+ aparam->strides[0] = aparam->size.w;
+ aparam->strides[1] = aparam->strides[2] = (aparam->size.w>>1);
+
+ aparam->planes[0] = aparam->buffer;
+ aparam->planes[1] = aparam->planes[0] + Y_bytes;
+ aparam->planes[2] = aparam->planes[1] + (Y_bytes>>1);
+
+ aparam->plane_bytes[0] = Y_bytes;
+ aparam->plane_bytes[1] = aparam->plane_bytes[2] = (Y_bytes>>1);
+
+ /* Zero unused planes */
+ for (i=3; i<PJMEDIA_MAX_VIDEO_PLANES; ++i) {
+ aparam->strides[i] = 0;
+ aparam->planes[i] = NULL;
+ aparam->plane_bytes[i] = 0;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t apply_planar_444(const pjmedia_video_format_info *fi,
+ pjmedia_video_apply_fmt_param *aparam)
+{
+ unsigned i;
+ pj_size_t Y_bytes;
+
+ PJ_UNUSED_ARG(fi);
+
+ /* Calculate memsize */
+ Y_bytes = (pj_size_t)(aparam->size.w * aparam->size.h);
+ aparam->framebytes = (Y_bytes * 3);
+
+ /* Planar formats use 3 plane */
+ aparam->strides[0] = aparam->strides[1] =
+ aparam->strides[2] = aparam->size.w;
+
+ aparam->planes[0] = aparam->buffer;
+ aparam->planes[1] = aparam->planes[0] + Y_bytes;
+ aparam->planes[2] = aparam->planes[1] + Y_bytes;
+
+ aparam->plane_bytes[0] = aparam->plane_bytes[1] =
+ aparam->plane_bytes[2] = Y_bytes;
+
+ /* Zero unused planes */
+ for (i=3; i<PJMEDIA_MAX_VIDEO_PLANES; ++i) {
+ aparam->strides[i] = 0;
+ aparam->planes[i] = NULL;
+ aparam->plane_bytes[i] = 0;
+ }
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t)
+pjmedia_video_format_mgr_create(pj_pool_t *pool,
+ unsigned max_fmt,
+ unsigned options,
+ pjmedia_video_format_mgr **p_mgr)
+{
+ pjmedia_video_format_mgr *mgr;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(pool && options==0, PJ_EINVAL);
+
+ PJ_UNUSED_ARG(options);
+
+ mgr = PJ_POOL_ALLOC_T(pool, pjmedia_video_format_mgr);
+ mgr->max_info = max_fmt;
+ mgr->info_cnt = 0;
+ mgr->infos = pj_pool_calloc(pool, max_fmt, sizeof(pjmedia_video_format_info *));
+
+ if (video_format_mgr_instance == NULL)
+ video_format_mgr_instance = mgr;
+
+ for (i=0; i<PJ_ARRAY_SIZE(built_in_vid_fmt_info); ++i) {
+ pjmedia_register_video_format_info(mgr,
+ &built_in_vid_fmt_info[i]);
+ }
+
+ if (p_mgr)
+ *p_mgr = mgr;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(const pjmedia_video_format_info*)
+pjmedia_get_video_format_info(pjmedia_video_format_mgr *mgr,
+ pj_uint32_t id)
+{
+ pjmedia_video_format_info **first;
+ int comp;
+ unsigned n;
+
+ if (!mgr)
+ mgr = pjmedia_video_format_mgr_instance();
+
+ PJ_ASSERT_RETURN(mgr != NULL, NULL);
+
+ /* Binary search for the appropriate format id */
+ comp = -1;
+ first = &mgr->infos[0];
+ n = mgr->info_cnt;
+ for (; n > 0; ) {
+ unsigned half = n / 2;
+ pjmedia_video_format_info **mid = first + half;
+
+ if ((*mid)->id < id) {
+ first = ++mid;
+ n -= half + 1;
+ } else if ((*mid)->id==id) {
+ return *mid;
+ } else {
+ n = half;
+ }
+ }
+
+ return NULL;
+}
+
+
+PJ_DEF(pj_status_t)
+pjmedia_register_video_format_info(pjmedia_video_format_mgr *mgr,
+ pjmedia_video_format_info *info)
+{
+ unsigned i;
+
+ if (!mgr)
+ mgr = pjmedia_video_format_mgr_instance();
+
+ PJ_ASSERT_RETURN(mgr != NULL, PJ_EINVALIDOP);
+
+ if (mgr->info_cnt >= mgr->max_info)
+ return PJ_ETOOMANY;
+
+ /* Insert to the array, sorted */
+ for (i=0; i<mgr->info_cnt; ++i) {
+ if (mgr->infos[i]->id >= info->id)
+ break;
+ }
+
+ if (i < mgr->info_cnt) {
+ if (mgr->infos[i]->id == info->id) {
+ /* just overwrite */
+ mgr->infos[i] = info;
+ return PJ_SUCCESS;
+ }
+
+ pj_memmove(&mgr->infos[i+1], &mgr->infos[i],
+ (mgr->info_cnt - i) * sizeof(pjmedia_video_format_info*));
+ }
+
+ mgr->infos[i] = info;
+ mgr->info_cnt++;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pjmedia_video_format_mgr*) pjmedia_video_format_mgr_instance(void)
+{
+ pj_assert(video_format_mgr_instance != NULL);
+ return video_format_mgr_instance;
+}
+
+PJ_DEF(void)
+pjmedia_video_format_mgr_set_instance(pjmedia_video_format_mgr *mgr)
+{
+ video_format_mgr_instance = mgr;
+}
+
+
+PJ_DEF(void) pjmedia_video_format_mgr_destroy(pjmedia_video_format_mgr *mgr)
+{
+ if (!mgr)
+ mgr = pjmedia_video_format_mgr_instance();
+
+ PJ_ASSERT_ON_FAIL(mgr != NULL, return);
+
+ mgr->info_cnt = 0;
+ if (video_format_mgr_instance == mgr)
+ video_format_mgr_instance = NULL;
+}
+
+#endif /* PJMEDIA_HAS_VIDEO */
diff --git a/pjmedia/src/pjmedia/g711.c b/pjmedia/src/pjmedia/g711.c
new file mode 100644
index 0000000..0a61a47
--- /dev/null
+++ b/pjmedia/src/pjmedia/g711.c
@@ -0,0 +1,621 @@
+/* $Id: g711.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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
+ */
+/* This file contains file from Sun Microsystems, Inc, with the complete
+ * notice in the second half of this file.
+ */
+#include <pjmedia/g711.h>
+#include <pjmedia/codec.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pjmedia/endpoint.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/port.h>
+#include <pjmedia/plc.h>
+#include <pjmedia/silencedet.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/assert.h>
+
+#if defined(PJMEDIA_HAS_G711_CODEC) && PJMEDIA_HAS_G711_CODEC!=0
+
+/* We removed PLC in 0.6 (and re-enabled it again in 0.9!) */
+#define PLC_DISABLED 0
+
+
+#define G711_BPS 64000
+#define G711_CODEC_CNT 0 /* number of codec to preallocate in memory */
+#define PTIME 10 /* basic frame size is 10 msec */
+#define FRAME_SIZE (8000 * PTIME / 1000) /* 80 bytes */
+#define SAMPLES_PER_FRAME (8000 * PTIME / 1000) /* 80 samples */
+
+/* Prototypes for G711 factory */
+static pj_status_t g711_test_alloc( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id );
+static pj_status_t g711_default_attr( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr );
+static pj_status_t g711_enum_codecs (pjmedia_codec_factory *factory,
+ unsigned *count,
+ pjmedia_codec_info codecs[]);
+static pj_status_t g711_alloc_codec( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec);
+static pj_status_t g711_dealloc_codec( pjmedia_codec_factory *factory,
+ pjmedia_codec *codec );
+
+/* Prototypes for G711 implementation. */
+static pj_status_t g711_init( pjmedia_codec *codec,
+ pj_pool_t *pool );
+static pj_status_t g711_open( pjmedia_codec *codec,
+ pjmedia_codec_param *attr );
+static pj_status_t g711_close( pjmedia_codec *codec );
+static pj_status_t g711_modify(pjmedia_codec *codec,
+ const pjmedia_codec_param *attr );
+static pj_status_t g711_parse(pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *timestamp,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[]);
+static pj_status_t g711_encode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+static pj_status_t g711_decode( pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+#if !PLC_DISABLED
+static pj_status_t g711_recover( pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output);
+#endif
+
+/* Definition for G711 codec operations. */
+static pjmedia_codec_op g711_op =
+{
+ &g711_init,
+ &g711_open,
+ &g711_close,
+ &g711_modify,
+ &g711_parse,
+ &g711_encode,
+ &g711_decode,
+#if !PLC_DISABLED
+ &g711_recover
+#else
+ NULL
+#endif
+};
+
+/* Definition for G711 codec factory operations. */
+static pjmedia_codec_factory_op g711_factory_op =
+{
+ &g711_test_alloc,
+ &g711_default_attr,
+ &g711_enum_codecs,
+ &g711_alloc_codec,
+ &g711_dealloc_codec,
+ &pjmedia_codec_g711_deinit
+};
+
+/* G711 factory private data */
+static struct g711_factory
+{
+ pjmedia_codec_factory base;
+ pjmedia_endpt *endpt;
+ pj_pool_t *pool;
+ pj_mutex_t *mutex;
+ pjmedia_codec codec_list;
+} g711_factory;
+
+/* G711 codec private data. */
+struct g711_private
+{
+ unsigned pt;
+#if !PLC_DISABLED
+ pj_bool_t plc_enabled;
+ pjmedia_plc *plc;
+#endif
+ pj_bool_t vad_enabled;
+ pjmedia_silence_det *vad;
+ pj_timestamp last_tx;
+};
+
+
+PJ_DEF(pj_status_t) pjmedia_codec_g711_init(pjmedia_endpt *endpt)
+{
+ pjmedia_codec_mgr *codec_mgr;
+ pj_status_t status;
+
+ if (g711_factory.endpt != NULL) {
+ /* Already initialized. */
+ return PJ_SUCCESS;
+ }
+
+ /* Init factory */
+ g711_factory.base.op = &g711_factory_op;
+ g711_factory.base.factory_data = NULL;
+ g711_factory.endpt = endpt;
+
+ pj_list_init(&g711_factory.codec_list);
+
+ /* Create pool */
+ g711_factory.pool = pjmedia_endpt_create_pool(endpt, "g711", 4000, 4000);
+ if (!g711_factory.pool)
+ return PJ_ENOMEM;
+
+ /* Create mutex. */
+ status = pj_mutex_create_simple(g711_factory.pool, "g611",
+ &g711_factory.mutex);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
+ if (!codec_mgr) {
+ return PJ_EINVALIDOP;
+ }
+
+ /* Register codec factory to endpoint. */
+ status = pjmedia_codec_mgr_register_factory(codec_mgr,
+ &g711_factory.base);
+ if (status != PJ_SUCCESS)
+ return status;
+
+
+ return PJ_SUCCESS;
+
+on_error:
+ if (g711_factory.mutex) {
+ pj_mutex_destroy(g711_factory.mutex);
+ g711_factory.mutex = NULL;
+ }
+ if (g711_factory.pool) {
+ pj_pool_release(g711_factory.pool);
+ g711_factory.pool = NULL;
+ }
+ return status;
+}
+
+PJ_DEF(pj_status_t) pjmedia_codec_g711_deinit(void)
+{
+ pjmedia_codec_mgr *codec_mgr;
+ pj_status_t status;
+
+ if (g711_factory.endpt == NULL) {
+ /* Not registered. */
+ return PJ_SUCCESS;
+ }
+
+ /* Lock mutex. */
+ pj_mutex_lock(g711_factory.mutex);
+
+ /* Get the codec manager. */
+ codec_mgr = pjmedia_endpt_get_codec_mgr(g711_factory.endpt);
+ if (!codec_mgr) {
+ g711_factory.endpt = NULL;
+ pj_mutex_unlock(g711_factory.mutex);
+ return PJ_EINVALIDOP;
+ }
+
+ /* Unregister G711 codec factory. */
+ status = pjmedia_codec_mgr_unregister_factory(codec_mgr,
+ &g711_factory.base);
+ g711_factory.endpt = NULL;
+
+ /* Destroy mutex. */
+ pj_mutex_destroy(g711_factory.mutex);
+ g711_factory.mutex = NULL;
+
+
+ /* Release pool. */
+ pj_pool_release(g711_factory.pool);
+ g711_factory.pool = NULL;
+
+
+ return status;
+}
+
+static pj_status_t g711_test_alloc(pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id )
+{
+ PJ_UNUSED_ARG(factory);
+
+ /* It's sufficient to check payload type only. */
+ return (id->pt==PJMEDIA_RTP_PT_PCMU || id->pt==PJMEDIA_RTP_PT_PCMA)? 0:-1;
+}
+
+static pj_status_t g711_default_attr (pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec_param *attr )
+{
+ PJ_UNUSED_ARG(factory);
+
+ pj_bzero(attr, sizeof(pjmedia_codec_param));
+ attr->info.clock_rate = 8000;
+ attr->info.channel_cnt = 1;
+ attr->info.avg_bps = G711_BPS;
+ attr->info.max_bps = G711_BPS;
+ attr->info.pcm_bits_per_sample = 16;
+ attr->info.frm_ptime = PTIME;
+ attr->info.pt = (pj_uint8_t)id->pt;
+
+ /* Set default frames per packet to 2 (or 20ms) */
+ attr->setting.frm_per_pkt = 2;
+
+#if !PLC_DISABLED
+ /* Enable plc by default. */
+ attr->setting.plc = 1;
+#endif
+
+ /* Enable VAD by default. */
+ attr->setting.vad = 1;
+
+ /* Default all other flag bits disabled. */
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t g711_enum_codecs(pjmedia_codec_factory *factory,
+ unsigned *max_count,
+ pjmedia_codec_info codecs[])
+{
+ unsigned count = 0;
+
+ PJ_UNUSED_ARG(factory);
+
+ if (count < *max_count) {
+ codecs[count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[count].pt = PJMEDIA_RTP_PT_PCMU;
+ codecs[count].encoding_name = pj_str("PCMU");
+ codecs[count].clock_rate = 8000;
+ codecs[count].channel_cnt = 1;
+ ++count;
+ }
+ if (count < *max_count) {
+ codecs[count].type = PJMEDIA_TYPE_AUDIO;
+ codecs[count].pt = PJMEDIA_RTP_PT_PCMA;
+ codecs[count].encoding_name = pj_str("PCMA");
+ codecs[count].clock_rate = 8000;
+ codecs[count].channel_cnt = 1;
+ ++count;
+ }
+
+ *max_count = count;
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t g711_alloc_codec( pjmedia_codec_factory *factory,
+ const pjmedia_codec_info *id,
+ pjmedia_codec **p_codec)
+{
+ pjmedia_codec *codec = NULL;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(factory==&g711_factory.base, PJ_EINVAL);
+
+ /* Lock mutex. */
+ pj_mutex_lock(g711_factory.mutex);
+
+ /* Allocate new codec if no more is available */
+ if (pj_list_empty(&g711_factory.codec_list)) {
+ struct g711_private *codec_priv;
+
+ codec = PJ_POOL_ALLOC_T(g711_factory.pool, pjmedia_codec);
+ codec_priv = PJ_POOL_ZALLOC_T(g711_factory.pool, struct g711_private);
+ if (!codec || !codec_priv) {
+ pj_mutex_unlock(g711_factory.mutex);
+ return PJ_ENOMEM;
+ }
+
+ /* Set the payload type */
+ codec_priv->pt = id->pt;
+
+#if !PLC_DISABLED
+ /* Create PLC, always with 10ms ptime */
+ status = pjmedia_plc_create(g711_factory.pool, 8000, 80,
+ 0, &codec_priv->plc);
+ if (status != PJ_SUCCESS) {
+ pj_mutex_unlock(g711_factory.mutex);
+ return status;
+ }
+#endif
+
+ /* Create VAD */
+ status = pjmedia_silence_det_create(g711_factory.pool,
+ 8000, 80,
+ &codec_priv->vad);
+ if (status != PJ_SUCCESS) {
+ pj_mutex_unlock(g711_factory.mutex);
+ return status;
+ }
+
+ codec->factory = factory;
+ codec->op = &g711_op;
+ codec->codec_data = codec_priv;
+ } else {
+ codec = g711_factory.codec_list.next;
+ pj_list_erase(codec);
+ }
+
+ /* Zero the list, for error detection in g711_dealloc_codec */
+ codec->next = codec->prev = NULL;
+
+ *p_codec = codec;
+
+ /* Unlock mutex. */
+ pj_mutex_unlock(g711_factory.mutex);
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t g711_dealloc_codec(pjmedia_codec_factory *factory,
+ pjmedia_codec *codec )
+{
+ struct g711_private *priv = (struct g711_private*) codec->codec_data;
+ int i = 0;
+
+ PJ_ASSERT_RETURN(factory==&g711_factory.base, PJ_EINVAL);
+
+ /* Check that this node has not been deallocated before */
+ pj_assert (codec->next==NULL && codec->prev==NULL);
+ if (codec->next!=NULL || codec->prev!=NULL) {
+ return PJ_EINVALIDOP;
+ }
+
+#if !PLC_DISABLED
+ /* Clear left samples in the PLC, since codec+plc will be reused
+ * next time.
+ */
+ for (i=0; i<2; ++i) {
+ pj_int16_t frame[SAMPLES_PER_FRAME];
+ pjmedia_zero_samples(frame, PJ_ARRAY_SIZE(frame));
+ pjmedia_plc_save(priv->plc, frame);
+ }
+#else
+ PJ_UNUSED_ARG(i);
+ PJ_UNUSED_ARG(priv);
+#endif
+
+ /* Lock mutex. */
+ pj_mutex_lock(g711_factory.mutex);
+
+ /* Insert at the back of the list */
+ pj_list_insert_before(&g711_factory.codec_list, codec);
+
+ /* Unlock mutex. */
+ pj_mutex_unlock(g711_factory.mutex);
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t g711_init( pjmedia_codec *codec, pj_pool_t *pool )
+{
+ /* There's nothing to do here really */
+ PJ_UNUSED_ARG(codec);
+ PJ_UNUSED_ARG(pool);
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t g711_open(pjmedia_codec *codec,
+ pjmedia_codec_param *attr )
+{
+ struct g711_private *priv = (struct g711_private*) codec->codec_data;
+ priv->pt = attr->info.pt;
+#if !PLC_DISABLED
+ priv->plc_enabled = (attr->setting.plc != 0);
+#endif
+ priv->vad_enabled = (attr->setting.vad != 0);
+ return PJ_SUCCESS;
+}
+
+static pj_status_t g711_close( pjmedia_codec *codec )
+{
+ PJ_UNUSED_ARG(codec);
+ /* Nothing to do */
+ return PJ_SUCCESS;
+}
+
+static pj_status_t g711_modify(pjmedia_codec *codec,
+ const pjmedia_codec_param *attr )
+{
+ struct g711_private *priv = (struct g711_private*) codec->codec_data;
+
+ if (attr->info.pt != priv->pt)
+ return PJMEDIA_EINVALIDPT;
+
+#if !PLC_DISABLED
+ priv->plc_enabled = (attr->setting.plc != 0);
+#endif
+ priv->vad_enabled = (attr->setting.vad != 0);
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t g711_parse( pjmedia_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[])
+{
+ unsigned count = 0;
+
+ PJ_UNUSED_ARG(codec);
+
+ PJ_ASSERT_RETURN(ts && frame_cnt && frames, PJ_EINVAL);
+
+ while (pkt_size >= FRAME_SIZE && count < *frame_cnt) {
+ frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frames[count].buf = pkt;
+ frames[count].size = FRAME_SIZE;
+ frames[count].timestamp.u64 = ts->u64 + SAMPLES_PER_FRAME * count;
+
+ pkt = ((char*)pkt) + FRAME_SIZE;
+ pkt_size -= FRAME_SIZE;
+
+ ++count;
+ }
+
+ *frame_cnt = count;
+ return PJ_SUCCESS;
+}
+
+static pj_status_t g711_encode(pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ pj_int16_t *samples = (pj_int16_t*) input->buf;
+ struct g711_private *priv = (struct g711_private*) codec->codec_data;
+
+ /* Check output buffer length */
+ if (output_buf_len < (input->size >> 1))
+ return PJMEDIA_CODEC_EFRMTOOSHORT;
+
+ /* Detect silence if VAD is enabled */
+ if (priv->vad_enabled) {
+ pj_bool_t is_silence;
+ pj_int32_t silence_period;
+
+ silence_period = pj_timestamp_diff32(&priv->last_tx,
+ &input->timestamp);
+
+ is_silence = pjmedia_silence_det_detect(priv->vad,
+ (const pj_int16_t*) input->buf,
+ (input->size >> 1), NULL);
+ if (is_silence &&
+ (PJMEDIA_CODEC_MAX_SILENCE_PERIOD == -1 ||
+ silence_period < PJMEDIA_CODEC_MAX_SILENCE_PERIOD*8000/1000))
+ {
+ output->type = PJMEDIA_FRAME_TYPE_NONE;
+ output->buf = NULL;
+ output->size = 0;
+ output->timestamp = input->timestamp;
+ return PJ_SUCCESS;
+ } else {
+ priv->last_tx = input->timestamp;
+ }
+ }
+
+ /* Encode */
+ if (priv->pt == PJMEDIA_RTP_PT_PCMA) {
+ unsigned i, n;
+ pj_uint8_t *dst = (pj_uint8_t*) output->buf;
+
+ n = (input->size >> 1);
+ for (i=0; i!=n; ++i, ++dst) {
+ *dst = pjmedia_linear2alaw(samples[i]);
+ }
+ } else if (priv->pt == PJMEDIA_RTP_PT_PCMU) {
+ unsigned i, n;
+ pj_uint8_t *dst = (pj_uint8_t*) output->buf;
+
+ n = (input->size >> 1);
+ for (i=0; i!=n; ++i, ++dst) {
+ *dst = pjmedia_linear2ulaw(samples[i]);
+ }
+
+ } else {
+ return PJMEDIA_EINVALIDPT;
+ }
+
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->size = (input->size >> 1);
+ output->timestamp = input->timestamp;
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t g711_decode(pjmedia_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ struct g711_private *priv = (struct g711_private*) codec->codec_data;
+
+ /* Check output buffer length */
+ PJ_ASSERT_RETURN(output_buf_len >= (input->size << 1),
+ PJMEDIA_CODEC_EPCMTOOSHORT);
+
+ /* Input buffer MUST have exactly 80 bytes long */
+ PJ_ASSERT_RETURN(input->size == FRAME_SIZE,
+ PJMEDIA_CODEC_EFRMINLEN);
+
+ /* Decode */
+ if (priv->pt == PJMEDIA_RTP_PT_PCMA) {
+ unsigned i;
+ pj_uint8_t *src = (pj_uint8_t*) input->buf;
+ pj_uint16_t *dst = (pj_uint16_t*) output->buf;
+
+ for (i=0; i!=input->size; ++i) {
+ *dst++ = (pj_uint16_t) pjmedia_alaw2linear(*src++);
+ }
+ } else if (priv->pt == PJMEDIA_RTP_PT_PCMU) {
+ unsigned i;
+ pj_uint8_t *src = (pj_uint8_t*) input->buf;
+ pj_uint16_t *dst = (pj_uint16_t*) output->buf;
+
+ for (i=0; i!=input->size; ++i) {
+ *dst++ = (pj_uint16_t) pjmedia_ulaw2linear(*src++);
+ }
+
+ } else {
+ return PJMEDIA_EINVALIDPT;
+ }
+
+ output->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ output->size = (input->size << 1);
+ output->timestamp = input->timestamp;
+
+#if !PLC_DISABLED
+ if (priv->plc_enabled)
+ pjmedia_plc_save( priv->plc, (pj_int16_t*)output->buf);
+#endif
+
+ return PJ_SUCCESS;
+}
+
+#if !PLC_DISABLED
+static pj_status_t g711_recover( pjmedia_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ struct g711_private *priv = (struct g711_private*) codec->codec_data;
+
+ if (!priv->plc_enabled)
+ return PJ_EINVALIDOP;
+
+ PJ_ASSERT_RETURN(output_buf_len >= SAMPLES_PER_FRAME * 2,
+ PJMEDIA_CODEC_EPCMTOOSHORT);
+
+ pjmedia_plc_generate(priv->plc, (pj_int16_t*)output->buf);
+ output->size = SAMPLES_PER_FRAME * 2;
+
+ return PJ_SUCCESS;
+}
+#endif
+
+#endif /* PJMEDIA_HAS_G711_CODEC */
+
+
+
diff --git a/pjmedia/src/pjmedia/jbuf.c b/pjmedia/src/pjmedia/jbuf.c
new file mode 100644
index 0000000..74f6469
--- /dev/null
+++ b/pjmedia/src/pjmedia/jbuf.c
@@ -0,0 +1,1190 @@
+/* $Id: jbuf.c 4100 2012-04-26 16:57:47Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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
+ */
+/*
+ * Based on implementation kindly contributed by Switchlab, Ltd.
+ */
+#include <pjmedia/jbuf.h>
+#include <pjmedia/errno.h>
+#include <pj/pool.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/string.h>
+
+
+#define THIS_FILE "jbuf.c"
+
+
+/* Invalid sequence number, used as the initial value. */
+#define INVALID_OFFSET -9999
+
+/* Maximum burst length, whenever an operation is bursting longer than
+ * this value, JB will assume that the opposite operation was idle.
+ */
+#define MAX_BURST_MSEC 1000
+
+/* Number of OP switches to be performed in JB_STATUS_INITIALIZING, before
+ * JB can switch its states to JB_STATUS_PROCESSING.
+ */
+#define INIT_CYCLE 10
+
+
+/* Minimal difference between JB size and 2*burst-level to perform
+ * JB shrinking in static discard algorithm.
+ */
+#define STA_DISC_SAFE_SHRINKING_DIFF 1
+
+
+/* Struct of JB internal buffer, represented in a circular buffer containing
+ * frame content, frame type, frame length, and frame bit info.
+ */
+typedef struct jb_framelist_t
+{
+ /* Settings */
+ unsigned frame_size; /**< maximum size of frame */
+ unsigned max_count; /**< maximum number of frames */
+
+ /* Buffers */
+ char *content; /**< frame content array */
+ int *frame_type; /**< frame type array */
+ pj_size_t *content_len; /**< frame length array */
+ pj_uint32_t *bit_info; /**< frame bit info array */
+ pj_uint32_t *ts; /**< timestamp array */
+
+ /* States */
+ unsigned head; /**< index of head, pointed frame
+ will be returned by next GET */
+ unsigned size; /**< current size of framelist,
+ including discarded frames. */
+ unsigned discarded_num; /**< current number of discarded
+ frames. */
+ int origin; /**< original index of flist_head */
+
+} jb_framelist_t;
+
+
+typedef void (*discard_algo)(pjmedia_jbuf *jb);
+static void jbuf_discard_static(pjmedia_jbuf *jb);
+static void jbuf_discard_progressive(pjmedia_jbuf *jb);
+
+
+struct pjmedia_jbuf
+{
+ /* Settings (consts) */
+ pj_str_t jb_name; /**< jitter buffer name */
+ pj_size_t jb_frame_size; /**< frame size */
+ unsigned jb_frame_ptime; /**< frame duration. */
+ pj_size_t jb_max_count; /**< capacity of jitter buffer,
+ in frames */
+ int jb_init_prefetch; /**< Initial prefetch */
+ int jb_min_prefetch; /**< Minimum allowable prefetch */
+ int jb_max_prefetch; /**< Maximum allowable prefetch */
+ int jb_max_burst; /**< maximum possible burst, whenever
+ burst exceeds this value, it
+ won't be included in level
+ calculation */
+ int jb_min_shrink_gap; /**< How often can we shrink */
+ discard_algo jb_discard_algo; /**< Discard algorithm */
+
+ /* Buffer */
+ jb_framelist_t jb_framelist; /**< the buffer */
+
+ /* States */
+ int jb_level; /**< delay between source &
+ destination (calculated according
+ of the number of burst get/put
+ operations) */
+ int jb_max_hist_level; /**< max level during the last level
+ calculations */
+ int jb_stable_hist; /**< num of times the delay has been
+ lower then the prefetch num */
+ int jb_last_op; /**< last operation executed
+ (put/get) */
+ int jb_eff_level; /**< effective burst level */
+ int jb_prefetch; /**< no. of frame to insert before
+ removing some (at the beginning
+ of the framelist->content
+ operation), the value may be
+ continuously updated based on
+ current frame burst level. */
+ pj_bool_t jb_prefetching; /**< flag if jbuf is prefetching. */
+ int jb_status; /**< status is 'init' until the first
+ 'put' operation */
+ int jb_init_cycle_cnt; /**< status is 'init' until the first
+ 'put' operation */
+
+ int jb_discard_ref; /**< Seq # of last frame deleted or
+ discarded */
+ unsigned jb_discard_dist; /**< Distance from jb_discard_ref
+ to perform discard (in frm) */
+
+ /* Statistics */
+ pj_math_stat jb_delay; /**< Delay statistics of jitter buffer
+ (in ms) */
+ pj_math_stat jb_burst; /**< Burst statistics (in frames) */
+ unsigned jb_lost; /**< Number of lost frames. */
+ unsigned jb_discard; /**< Number of discarded frames. */
+ unsigned jb_empty; /**< Number of empty/prefetching frame
+ returned by GET. */
+};
+
+
+#define JB_STATUS_INITIALIZING 0
+#define JB_STATUS_PROCESSING 1
+
+
+
+/* Progressive discard algorithm introduced to reduce JB latency
+ * by discarding incoming frames with adaptive aggressiveness based on
+ * actual burst level.
+ */
+#define PROGRESSIVE_DISCARD 1
+
+/* Internal JB frame flag, discarded frame will not be returned by JB to
+ * application, it's just simply discarded.
+ */
+#define PJMEDIA_JB_DISCARDED_FRAME 1024
+
+
+
+/* Enabling this would log the jitter buffer state about once per
+ * second.
+ */
+#if 1
+# define TRACE__(args) PJ_LOG(5,args)
+#else
+# define TRACE__(args)
+#endif
+
+static pj_status_t jb_framelist_reset(jb_framelist_t *framelist);
+static unsigned jb_framelist_remove_head(jb_framelist_t *framelist,
+ unsigned count);
+
+static pj_status_t jb_framelist_init( pj_pool_t *pool,
+ jb_framelist_t *framelist,
+ unsigned frame_size,
+ unsigned max_count)
+{
+ PJ_ASSERT_RETURN(pool && framelist, PJ_EINVAL);
+
+ pj_bzero(framelist, sizeof(jb_framelist_t));
+
+ framelist->frame_size = frame_size;
+ framelist->max_count = max_count;
+ framelist->content = (char*)
+ pj_pool_alloc(pool,
+ framelist->frame_size*
+ framelist->max_count);
+ framelist->frame_type = (int*)
+ pj_pool_alloc(pool,
+ sizeof(framelist->frame_type[0])*
+ framelist->max_count);
+ framelist->content_len = (pj_size_t*)
+ pj_pool_alloc(pool,
+ sizeof(framelist->content_len[0])*
+ framelist->max_count);
+ framelist->bit_info = (pj_uint32_t*)
+ pj_pool_alloc(pool,
+ sizeof(framelist->bit_info[0])*
+ framelist->max_count);
+ framelist->ts = (pj_uint32_t*)
+ pj_pool_alloc(pool,
+ sizeof(framelist->ts[0])*
+ framelist->max_count);
+
+ return jb_framelist_reset(framelist);
+
+}
+
+static pj_status_t jb_framelist_destroy(jb_framelist_t *framelist)
+{
+ PJ_UNUSED_ARG(framelist);
+ return PJ_SUCCESS;
+}
+
+static pj_status_t jb_framelist_reset(jb_framelist_t *framelist)
+{
+ framelist->head = 0;
+ framelist->origin = INVALID_OFFSET;
+ framelist->size = 0;
+ framelist->discarded_num = 0;
+
+
+ //pj_bzero(framelist->content,
+ // framelist->frame_size *
+ // framelist->max_count);
+
+ pj_memset(framelist->frame_type,
+ PJMEDIA_JB_MISSING_FRAME,
+ sizeof(framelist->frame_type[0]) *
+ framelist->max_count);
+
+ pj_bzero(framelist->content_len,
+ sizeof(framelist->content_len[0]) *
+ framelist->max_count);
+
+ //pj_bzero(framelist->bit_info,
+ // sizeof(framelist->bit_info[0]) *
+ // framelist->max_count);
+
+ return PJ_SUCCESS;
+}
+
+
+static unsigned jb_framelist_size(const jb_framelist_t *framelist)
+{
+ return framelist->size;
+}
+
+
+static unsigned jb_framelist_eff_size(const jb_framelist_t *framelist)
+{
+ return (framelist->size - framelist->discarded_num);
+}
+
+static int jb_framelist_origin(const jb_framelist_t *framelist)
+{
+ return framelist->origin;
+}
+
+
+static pj_bool_t jb_framelist_get(jb_framelist_t *framelist,
+ void *frame, pj_size_t *size,
+ pjmedia_jb_frame_type *p_type,
+ pj_uint32_t *bit_info,
+ pj_uint32_t *ts,
+ int *seq)
+{
+ if (framelist->size) {
+ pj_bool_t prev_discarded = PJ_FALSE;
+
+ /* Skip discarded frames */
+ while (framelist->frame_type[framelist->head] ==
+ PJMEDIA_JB_DISCARDED_FRAME)
+ {
+ jb_framelist_remove_head(framelist, 1);
+ prev_discarded = PJ_TRUE;
+ }
+
+ /* Return the head frame if any */
+ if (framelist->size) {
+ if (prev_discarded) {
+ /* Ticket #1188: when previous frame(s) was discarded, return
+ * 'missing' frame to trigger PLC to get smoother signal.
+ */
+ *p_type = PJMEDIA_JB_MISSING_FRAME;
+ if (size)
+ *size = 0;
+ if (bit_info)
+ *bit_info = 0;
+ } else {
+ pj_memcpy(frame,
+ framelist->content +
+ framelist->head * framelist->frame_size,
+ framelist->frame_size);
+ *p_type = (pjmedia_jb_frame_type)
+ framelist->frame_type[framelist->head];
+ if (size)
+ *size = framelist->content_len[framelist->head];
+ if (bit_info)
+ *bit_info = framelist->bit_info[framelist->head];
+ }
+ if (ts)
+ *ts = framelist->ts[framelist->head];
+ if (seq)
+ *seq = framelist->origin;
+
+ //pj_bzero(framelist->content +
+ // framelist->head * framelist->frame_size,
+ // framelist->frame_size);
+ framelist->frame_type[framelist->head] = PJMEDIA_JB_MISSING_FRAME;
+ framelist->content_len[framelist->head] = 0;
+ framelist->bit_info[framelist->head] = 0;
+ framelist->ts[framelist->head] = 0;
+
+ framelist->origin++;
+ framelist->head = (framelist->head + 1) % framelist->max_count;
+ framelist->size--;
+
+ return PJ_TRUE;
+ }
+ }
+
+ /* No frame available */
+ pj_bzero(frame, framelist->frame_size);
+
+ return PJ_FALSE;
+}
+
+
+static pj_bool_t jb_framelist_peek(jb_framelist_t *framelist,
+ unsigned offset,
+ const void **frame,
+ pj_size_t *size,
+ pjmedia_jb_frame_type *type,
+ pj_uint32_t *bit_info,
+ pj_uint32_t *ts,
+ int *seq)
+{
+ unsigned pos, idx;
+
+ if (offset >= jb_framelist_eff_size(framelist))
+ return PJ_FALSE;
+
+ pos = framelist->head;
+ idx = offset;
+
+ /* Find actual peek position, note there may be discarded frames */
+ while (1) {
+ if (framelist->frame_type[pos] != PJMEDIA_JB_DISCARDED_FRAME) {
+ if (idx == 0)
+ break;
+ else
+ --idx;
+ }
+ pos = (pos + 1) % framelist->max_count;
+ }
+
+ /* Return the frame pointer */
+ if (frame)
+ *frame = framelist->content + pos*framelist->frame_size;
+ if (type)
+ *type = (pjmedia_jb_frame_type)
+ framelist->frame_type[pos];
+ if (size)
+ *size = framelist->content_len[pos];
+ if (bit_info)
+ *bit_info = framelist->bit_info[pos];
+ if (ts)
+ *ts = framelist->ts[pos];
+ if (seq)
+ *seq = framelist->origin + offset;
+
+ return PJ_TRUE;
+}
+
+
+/* Remove oldest frames as many as param 'count' */
+static unsigned jb_framelist_remove_head(jb_framelist_t *framelist,
+ unsigned count)
+{
+ if (count > framelist->size)
+ count = framelist->size;
+
+ if (count) {
+ /* may be done in two steps if overlapping */
+ unsigned step1,step2;
+ unsigned tmp = framelist->head+count;
+ unsigned i;
+
+ if (tmp > framelist->max_count) {
+ step1 = framelist->max_count - framelist->head;
+ step2 = count-step1;
+ } else {
+ step1 = count;
+ step2 = 0;
+ }
+
+ for (i = framelist->head; i < (framelist->head + step1); ++i) {
+ if (framelist->frame_type[i] == PJMEDIA_JB_DISCARDED_FRAME) {
+ pj_assert(framelist->discarded_num > 0);
+ framelist->discarded_num--;
+ }
+ }
+
+ //pj_bzero(framelist->content +
+ // framelist->head * framelist->frame_size,
+ // step1*framelist->frame_size);
+ pj_memset(framelist->frame_type+framelist->head,
+ PJMEDIA_JB_MISSING_FRAME,
+ step1*sizeof(framelist->frame_type[0]));
+ pj_bzero(framelist->content_len+framelist->head,
+ step1*sizeof(framelist->content_len[0]));
+
+ if (step2) {
+ for (i = 0; i < step2; ++i) {
+ if (framelist->frame_type[i] == PJMEDIA_JB_DISCARDED_FRAME) {
+ pj_assert(framelist->discarded_num > 0);
+ framelist->discarded_num--;
+ }
+ }
+ //pj_bzero( framelist->content,
+ // step2*framelist->frame_size);
+ pj_memset(framelist->frame_type,
+ PJMEDIA_JB_MISSING_FRAME,
+ step2*sizeof(framelist->frame_type[0]));
+ pj_bzero (framelist->content_len,
+ step2*sizeof(framelist->content_len[0]));
+ }
+
+ /* update states */
+ framelist->origin += count;
+ framelist->head = (framelist->head + count) % framelist->max_count;
+ framelist->size -= count;
+ }
+
+ return count;
+}
+
+
+static pj_status_t jb_framelist_put_at(jb_framelist_t *framelist,
+ int index,
+ const void *frame,
+ unsigned frame_size,
+ pj_uint32_t bit_info,
+ pj_uint32_t ts,
+ unsigned frame_type)
+{
+ int distance;
+ unsigned pos;
+ enum { MAX_MISORDER = 100 };
+ enum { MAX_DROPOUT = 3000 };
+
+ PJ_ASSERT_RETURN(frame_size <= framelist->frame_size, PJ_EINVAL);
+
+ /* too late or sequence restart */
+ if (index < framelist->origin) {
+ if (framelist->origin - index < MAX_MISORDER) {
+ /* too late */
+ return PJ_ETOOSMALL;
+ } else {
+ /* sequence restart */
+ framelist->origin = index - framelist->size;
+ }
+ }
+
+ /* if jbuf is empty, just reset the origin */
+ if (framelist->size == 0) {
+ pj_assert(framelist->discarded_num == 0);
+ framelist->origin = index;
+ }
+
+ /* get distance of this frame to the first frame in the buffer */
+ distance = index - framelist->origin;
+
+ /* far jump, the distance is greater than buffer capacity */
+ if (distance >= (int)framelist->max_count) {
+ if (distance > MAX_DROPOUT) {
+ /* jump too far, reset the buffer */
+ jb_framelist_reset(framelist);
+ framelist->origin = index;
+ distance = 0;
+ } else {
+ /* otherwise, reject the frame */
+ return PJ_ETOOMANY;
+ }
+ }
+
+ /* get the slot position */
+ pos = (framelist->head + distance) % framelist->max_count;
+
+ /* if the slot is occupied, it must be duplicated frame, ignore it. */
+ if (framelist->frame_type[pos] != PJMEDIA_JB_MISSING_FRAME)
+ return PJ_EEXISTS;
+
+ /* put the frame into the slot */
+ framelist->frame_type[pos] = frame_type;
+ framelist->content_len[pos] = frame_size;
+ framelist->bit_info[pos] = bit_info;
+ framelist->ts[pos] = ts;
+
+ /* update framelist size */
+ if (framelist->origin + (int)framelist->size <= index)
+ framelist->size = distance + 1;
+
+ if(PJMEDIA_JB_NORMAL_FRAME == frame_type) {
+ /* copy frame content */
+ pj_memcpy(framelist->content + pos * framelist->frame_size,
+ frame, frame_size);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t jb_framelist_discard(jb_framelist_t *framelist,
+ int index)
+{
+ unsigned pos;
+
+ PJ_ASSERT_RETURN(index >= framelist->origin &&
+ index < framelist->origin + (int)framelist->size,
+ PJ_EINVAL);
+
+ /* Get the slot position */
+ pos = (framelist->head + (index - framelist->origin)) %
+ framelist->max_count;
+
+ /* Discard the frame */
+ framelist->frame_type[pos] = PJMEDIA_JB_DISCARDED_FRAME;
+ framelist->discarded_num++;
+
+ return PJ_SUCCESS;
+}
+
+
+enum pjmedia_jb_op
+{
+ JB_OP_INIT = -1,
+ JB_OP_PUT = 1,
+ JB_OP_GET = 2
+};
+
+
+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;
+}
+
+
+/*
+ * Set the jitter buffer to fixed delay mode. The default behavior
+ * is to adapt the delay with actual packet delay.
+ *
+ */
+PJ_DEF(pj_status_t) pjmedia_jbuf_set_fixed( pjmedia_jbuf *jb,
+ unsigned prefetch)
+{
+ PJ_ASSERT_RETURN(jb, PJ_EINVAL);
+ PJ_ASSERT_RETURN(prefetch <= jb->jb_max_count, PJ_EINVAL);
+
+ jb->jb_min_prefetch = jb->jb_max_prefetch =
+ jb->jb_prefetch = jb->jb_init_prefetch = prefetch;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Set the jitter buffer to adaptive mode.
+ */
+PJ_DEF(pj_status_t) pjmedia_jbuf_set_adaptive( pjmedia_jbuf *jb,
+ unsigned prefetch,
+ unsigned min_prefetch,
+ unsigned max_prefetch)
+{
+ PJ_ASSERT_RETURN(jb, PJ_EINVAL);
+ PJ_ASSERT_RETURN(min_prefetch <= max_prefetch &&
+ prefetch <= max_prefetch &&
+ max_prefetch <= jb->jb_max_count,
+ PJ_EINVAL);
+
+ jb->jb_prefetch = jb->jb_init_prefetch = prefetch;
+ jb->jb_min_prefetch = min_prefetch;
+ jb->jb_max_prefetch = max_prefetch;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_jbuf_set_discard( pjmedia_jbuf *jb,
+ pjmedia_jb_discard_algo algo)
+{
+ PJ_ASSERT_RETURN(jb, PJ_EINVAL);
+ PJ_ASSERT_RETURN(algo >= PJMEDIA_JB_DISCARD_NONE &&
+ algo <= PJMEDIA_JB_DISCARD_PROGRESSIVE,
+ PJ_EINVAL);
+
+ switch(algo) {
+ case PJMEDIA_JB_DISCARD_PROGRESSIVE:
+ jb->jb_discard_algo = &jbuf_discard_progressive;
+ break;
+ case PJMEDIA_JB_DISCARD_STATIC:
+ jb->jb_discard_algo = &jbuf_discard_static;
+ break;
+ default:
+ jb->jb_discard_algo = NULL;
+ break;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_jbuf_reset(pjmedia_jbuf *jb)
+{
+ jb->jb_level = 0;
+ jb->jb_last_op = JB_OP_INIT;
+ jb->jb_stable_hist = 0;
+ jb->jb_status = JB_STATUS_INITIALIZING;
+ jb->jb_init_cycle_cnt= 0;
+ jb->jb_max_hist_level= 0;
+ jb->jb_prefetching = (jb->jb_prefetch != 0);
+ jb->jb_discard_dist = 0;
+
+ jb_framelist_reset(&jb->jb_framelist);
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_jbuf_destroy(pjmedia_jbuf *jb)
+{
+ PJ_LOG(5, (jb->jb_name.ptr, ""
+ "JB summary:\n"
+ " size=%d/eff=%d prefetch=%d level=%d\n"
+ " delay (min/max/avg/dev)=%d/%d/%d/%d ms\n"
+ " burst (min/max/avg/dev)=%d/%d/%d/%d frames\n"
+ " lost=%d discard=%d empty=%d",
+ jb_framelist_size(&jb->jb_framelist),
+ jb_framelist_eff_size(&jb->jb_framelist),
+ jb->jb_prefetch, jb->jb_eff_level,
+ jb->jb_delay.min, jb->jb_delay.max, jb->jb_delay.mean,
+ pj_math_stat_get_stddev(&jb->jb_delay),
+ jb->jb_burst.min, jb->jb_burst.max, jb->jb_burst.mean,
+ pj_math_stat_get_stddev(&jb->jb_burst),
+ jb->jb_lost, jb->jb_discard, jb->jb_empty));
+
+ return jb_framelist_destroy(&jb->jb_framelist);
+}
+
+PJ_DEF(pj_bool_t) pjmedia_jbuf_is_full(const pjmedia_jbuf *jb)
+{
+ return jb->jb_framelist.size == jb->jb_framelist.max_count;
+}
+
+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;
+ }
+}
+
+
+static void jbuf_discard_static(pjmedia_jbuf *jb)
+{
+ /* These code is used for shortening the delay in the jitter buffer.
+ * It needs shrink only when there is possibility of drift. Drift
+ * detection is performed by inspecting the jitter buffer size, if
+ * its size is twice of current burst level, there can be drift.
+ *
+ * Moreover, normally drift level is quite low, so JB shouldn't need
+ * to shrink aggresively, it will shrink maximum one frame per
+ * PJMEDIA_JBUF_DISC_MIN_GAP ms. Theoritically, JB may handle drift level
+ * as much as = FRAME_PTIME/PJMEDIA_JBUF_DISC_MIN_GAP * 100%
+ *
+ * Whenever there is drift, where PUT > GET, this method will keep
+ * the latency (JB size) as much as twice of burst level.
+ */
+
+ /* Shrinking due of drift will be implicitly done by progressive discard,
+ * so just disable it when progressive discard is active.
+ */
+ int diff, burst_level;
+
+ burst_level = PJ_MAX(jb->jb_eff_level, jb->jb_level);
+ diff = jb_framelist_eff_size(&jb->jb_framelist) - burst_level*2;
+
+ if (diff >= STA_DISC_SAFE_SHRINKING_DIFF) {
+ int seq_origin;
+
+ /* Check and adjust jb_discard_ref, in case there was
+ * seq restart
+ */
+ seq_origin = jb_framelist_origin(&jb->jb_framelist);
+ if (seq_origin < jb->jb_discard_ref)
+ jb->jb_discard_ref = seq_origin;
+
+ if (seq_origin - jb->jb_discard_ref >= jb->jb_min_shrink_gap)
+ {
+ /* Shrink slowly, one frame per cycle */
+ diff = 1;
+
+ /* Drop frame(s)! */
+ diff = jb_framelist_remove_head(&jb->jb_framelist, diff);
+ jb->jb_discard_ref = jb_framelist_origin(&jb->jb_framelist);
+ jb->jb_discard += diff;
+
+ TRACE__((jb->jb_name.ptr,
+ "JB shrinking %d frame(s), cur size=%d", diff,
+ jb_framelist_eff_size(&jb->jb_framelist)));
+ }
+ }
+}
+
+
+static void jbuf_discard_progressive(pjmedia_jbuf *jb)
+{
+ unsigned cur_size, burst_level, overflow, T, discard_dist;
+ int last_seq;
+
+ /* Should be done in PUT operation */
+ if (jb->jb_last_op != JB_OP_PUT)
+ return;
+
+ /* Check if latency is longer than burst */
+ cur_size = jb_framelist_eff_size(&jb->jb_framelist);
+ burst_level = PJ_MAX(jb->jb_eff_level, jb->jb_level);
+ if (cur_size <= burst_level) {
+ /* Reset any scheduled discard */
+ jb->jb_discard_dist = 0;
+ return;
+ }
+
+ /* Estimate discard duration needed for adjusting latency */
+ if (burst_level <= PJMEDIA_JBUF_PRO_DISC_MIN_BURST)
+ T = PJMEDIA_JBUF_PRO_DISC_T1;
+ else if (burst_level >= PJMEDIA_JBUF_PRO_DISC_MAX_BURST)
+ T = PJMEDIA_JBUF_PRO_DISC_T2;
+ else
+ T = PJMEDIA_JBUF_PRO_DISC_T1 +
+ (PJMEDIA_JBUF_PRO_DISC_T2 - PJMEDIA_JBUF_PRO_DISC_T1) *
+ (burst_level - PJMEDIA_JBUF_PRO_DISC_MIN_BURST) /
+ (PJMEDIA_JBUF_PRO_DISC_MAX_BURST-PJMEDIA_JBUF_PRO_DISC_MIN_BURST);
+
+ /* Calculate current discard distance */
+ overflow = cur_size - burst_level;
+ discard_dist = T / overflow / jb->jb_frame_ptime;
+
+ /* Get last seq number in the JB */
+ last_seq = jb_framelist_origin(&jb->jb_framelist) +
+ jb_framelist_size(&jb->jb_framelist) - 1;
+
+ /* Setup new discard schedule if none, otherwise, update the existing
+ * discard schedule (can be delayed or accelerated).
+ */
+ if (jb->jb_discard_dist == 0) {
+ /* Setup new discard schedule */
+ jb->jb_discard_ref = last_seq;
+ } else if (last_seq < jb->jb_discard_ref) {
+ /* Seq restarted, update discard reference */
+ jb->jb_discard_ref = last_seq;
+ }
+ jb->jb_discard_dist = PJ_MAX(jb->jb_min_shrink_gap, (int)discard_dist);
+
+ /* Check if we need to discard now */
+ if (last_seq >= (jb->jb_discard_ref + (int)jb->jb_discard_dist)) {
+ int discard_seq;
+
+ discard_seq = jb->jb_discard_ref + jb->jb_discard_dist;
+ if (discard_seq < jb_framelist_origin(&jb->jb_framelist))
+ discard_seq = jb_framelist_origin(&jb->jb_framelist);
+
+ jb_framelist_discard(&jb->jb_framelist, discard_seq);
+
+ TRACE__((jb->jb_name.ptr,
+ "Discard #%d: ref=#%d dist=%d orig=%d size=%d/%d "
+ "burst=%d/%d",
+ discard_seq,
+ jb->jb_discard_ref,
+ jb->jb_discard_dist,
+ jb_framelist_origin(&jb->jb_framelist),
+ cur_size,
+ jb_framelist_size(&jb->jb_framelist),
+ jb->jb_eff_level,
+ burst_level));
+
+ /* Update discard reference */
+ jb->jb_discard_ref = discard_seq;
+ }
+}
+
+
+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);
+ }
+}
+
+PJ_DEF(void) pjmedia_jbuf_put_frame( pjmedia_jbuf *jb,
+ const void *frame,
+ pj_size_t frame_size,
+ int frame_seq)
+{
+ pjmedia_jbuf_put_frame3(jb, frame, frame_size, 0, frame_seq, 0, NULL);
+}
+
+PJ_DEF(void) pjmedia_jbuf_put_frame2(pjmedia_jbuf *jb,
+ const void *frame,
+ pj_size_t frame_size,
+ pj_uint32_t bit_info,
+ int frame_seq,
+ pj_bool_t *discarded)
+{
+ pjmedia_jbuf_put_frame3(jb, frame, frame_size, bit_info, frame_seq, 0,
+ discarded);
+}
+
+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++;
+}
+
+/*
+ * Get frame from jitter buffer.
+ */
+PJ_DEF(void) pjmedia_jbuf_get_frame( pjmedia_jbuf *jb,
+ void *frame,
+ char *p_frame_type)
+{
+ pjmedia_jbuf_get_frame3(jb, frame, NULL, p_frame_type, NULL,
+ NULL, NULL);
+}
+
+/*
+ * Get frame from jitter buffer.
+ */
+PJ_DEF(void) pjmedia_jbuf_get_frame2(pjmedia_jbuf *jb,
+ void *frame,
+ pj_size_t *size,
+ char *p_frame_type,
+ pj_uint32_t *bit_info)
+{
+ pjmedia_jbuf_get_frame3(jb, frame, size, p_frame_type, bit_info,
+ NULL, NULL);
+}
+
+/*
+ * Get frame from jitter buffer.
+ */
+PJ_DEF(void) pjmedia_jbuf_get_frame3(pjmedia_jbuf *jb,
+ void *frame,
+ pj_size_t *size,
+ char *p_frame_type,
+ pj_uint32_t *bit_info,
+ pj_uint32_t *ts,
+ int *seq)
+{
+ if (jb->jb_prefetching) {
+
+ /* Can't return frame because jitter buffer is filling up
+ * minimum prefetch.
+ */
+
+ //pj_bzero(frame, jb->jb_frame_size);
+ *p_frame_type = PJMEDIA_JB_ZERO_PREFETCH_FRAME;
+ if (size)
+ *size = 0;
+
+ TRACE__((jb->jb_name.ptr, "GET prefetch_cnt=%d/%d",
+ jb_framelist_eff_size(&jb->jb_framelist), jb->jb_prefetch));
+
+ jb->jb_empty++;
+
+ } else {
+
+ pjmedia_jb_frame_type ftype = PJMEDIA_JB_NORMAL_FRAME;
+ pj_bool_t res;
+
+ /* Try to retrieve a frame from frame list */
+ res = jb_framelist_get(&jb->jb_framelist, frame, size, &ftype,
+ bit_info, ts, seq);
+ if (res) {
+ /* We've successfully retrieved a frame from the frame list, but
+ * the frame could be a blank frame!
+ */
+ if (ftype == PJMEDIA_JB_NORMAL_FRAME) {
+ *p_frame_type = PJMEDIA_JB_NORMAL_FRAME;
+ } else {
+ *p_frame_type = PJMEDIA_JB_MISSING_FRAME;
+ jb->jb_lost++;
+ }
+
+ /* Store delay history at the first GET */
+ if (jb->jb_last_op == JB_OP_PUT) {
+ unsigned cur_size;
+
+ /* We've just retrieved one frame, so add one to cur_size */
+ cur_size = jb_framelist_eff_size(&jb->jb_framelist) + 1;
+ pj_math_stat_update(&jb->jb_delay,
+ cur_size*jb->jb_frame_ptime);
+ }
+ } else {
+ /* Jitter buffer is empty */
+ if (jb->jb_prefetch)
+ jb->jb_prefetching = PJ_TRUE;
+
+ //pj_bzero(frame, jb->jb_frame_size);
+ *p_frame_type = PJMEDIA_JB_ZERO_EMPTY_FRAME;
+ if (size)
+ *size = 0;
+
+ jb->jb_empty++;
+ }
+ }
+
+ jb->jb_level++;
+ jbuf_update(jb, JB_OP_GET);
+}
+
+/*
+ * Get jitter buffer state.
+ */
+PJ_DEF(pj_status_t) pjmedia_jbuf_get_state( const pjmedia_jbuf *jb,
+ pjmedia_jb_state *state )
+{
+ PJ_ASSERT_RETURN(jb && state, PJ_EINVAL);
+
+ state->frame_size = jb->jb_frame_size;
+ state->min_prefetch = jb->jb_min_prefetch;
+ state->max_prefetch = jb->jb_max_prefetch;
+
+ state->burst = jb->jb_eff_level;
+ state->prefetch = jb->jb_prefetch;
+ state->size = jb_framelist_eff_size(&jb->jb_framelist);
+
+ state->avg_delay = jb->jb_delay.mean;
+ state->min_delay = jb->jb_delay.min;
+ state->max_delay = jb->jb_delay.max;
+ state->dev_delay = pj_math_stat_get_stddev(&jb->jb_delay);
+
+ state->avg_burst = jb->jb_burst.mean;
+ state->empty = jb->jb_empty;
+ state->discard = jb->jb_discard;
+ state->lost = jb->jb_lost;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(void) pjmedia_jbuf_peek_frame( pjmedia_jbuf *jb,
+ unsigned offset,
+ const void **frame,
+ pj_size_t *size,
+ char *p_frm_type,
+ pj_uint32_t *bit_info,
+ pj_uint32_t *ts,
+ int *seq)
+{
+ pjmedia_jb_frame_type ftype;
+ pj_bool_t res;
+
+ res = jb_framelist_peek(&jb->jb_framelist, offset, frame, size, &ftype,
+ bit_info, ts, seq);
+ if (!res)
+ *p_frm_type = PJMEDIA_JB_ZERO_EMPTY_FRAME;
+ else if (ftype == PJMEDIA_JB_NORMAL_FRAME)
+ *p_frm_type = PJMEDIA_JB_NORMAL_FRAME;
+ else
+ *p_frm_type = PJMEDIA_JB_MISSING_FRAME;
+}
+
+
+PJ_DEF(unsigned) pjmedia_jbuf_remove_frame(pjmedia_jbuf *jb,
+ unsigned frame_cnt)
+{
+ unsigned count, last_discard_num;
+
+ last_discard_num = jb->jb_framelist.discarded_num;
+ count = jb_framelist_remove_head(&jb->jb_framelist, frame_cnt);
+
+ /* Remove some more when there were discarded frames included */
+ while (jb->jb_framelist.discarded_num < last_discard_num) {
+ /* Calculate frames count to be removed next */
+ frame_cnt = last_discard_num - jb->jb_framelist.discarded_num;
+
+ /* Normalize non-discarded frames count just been removed */
+ count -= frame_cnt;
+
+ /* Remove more frames */
+ last_discard_num = jb->jb_framelist.discarded_num;
+ count += jb_framelist_remove_head(&jb->jb_framelist, frame_cnt);
+ }
+
+ return count;
+}
diff --git a/pjmedia/src/pjmedia/master_port.c b/pjmedia/src/pjmedia/master_port.c
new file mode 100644
index 0000000..d423b31
--- /dev/null
+++ b/pjmedia/src/pjmedia/master_port.c
@@ -0,0 +1,321 @@
+/* $Id: master_port.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/master_port.h>
+#include <pjmedia/clock.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/lock.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+struct pjmedia_master_port
+{
+ unsigned options;
+ pjmedia_clock *clock;
+ pjmedia_port *u_port;
+ pjmedia_port *d_port;
+ unsigned buff_size;
+ void *buff;
+ pj_lock_t *lock;
+};
+
+
+static void clock_callback(const pj_timestamp *ts, void *user_data);
+
+
+/*
+ * Create a master port.
+ *
+ */
+PJ_DEF(pj_status_t) pjmedia_master_port_create( pj_pool_t *pool,
+ pjmedia_port *u_port,
+ pjmedia_port *d_port,
+ unsigned options,
+ pjmedia_master_port **p_m)
+{
+ pjmedia_master_port *m;
+ unsigned clock_rate;
+ unsigned channel_count;
+ unsigned samples_per_frame;
+ unsigned bytes_per_frame;
+ pjmedia_audio_format_detail *u_afd, *d_afd;
+ pj_status_t status;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(pool && u_port && d_port && p_m, PJ_EINVAL);
+
+ u_afd = pjmedia_format_get_audio_format_detail(&u_port->info.fmt, PJ_TRUE);
+ d_afd = pjmedia_format_get_audio_format_detail(&d_port->info.fmt, PJ_TRUE);
+
+ /* Both ports MUST have equal clock rate */
+ PJ_ASSERT_RETURN(u_afd->clock_rate == d_afd->clock_rate,
+ PJMEDIA_ENCCLOCKRATE);
+
+ /* Both ports MUST have equal samples per frame */
+ PJ_ASSERT_RETURN(PJMEDIA_PIA_SPF(&u_port->info)==
+ PJMEDIA_PIA_SPF(&d_port->info),
+ PJMEDIA_ENCSAMPLESPFRAME);
+
+ /* Both ports MUST have equal channel count */
+ PJ_ASSERT_RETURN(u_afd->channel_count == d_afd->channel_count,
+ PJMEDIA_ENCCHANNEL);
+
+
+ /* Get clock_rate and samples_per_frame from one of the port. */
+ clock_rate = u_afd->clock_rate;
+ samples_per_frame = PJMEDIA_PIA_SPF(&u_port->info);
+ channel_count = u_afd->channel_count;
+
+
+ /* Get the bytes_per_frame value, to determine the size of the
+ * buffer. We take the larger size of the two ports.
+ */
+ bytes_per_frame = PJMEDIA_AFD_AVG_FSZ(u_afd);
+ if (PJMEDIA_AFD_AVG_FSZ(d_afd) > bytes_per_frame)
+ bytes_per_frame = PJMEDIA_AFD_AVG_FSZ(d_afd);
+
+
+ /* Create the master port instance */
+ m = PJ_POOL_ZALLOC_T(pool, pjmedia_master_port);
+ m->options = options;
+ m->u_port = u_port;
+ m->d_port = d_port;
+
+
+ /* Create buffer */
+ m->buff_size = bytes_per_frame;
+ m->buff = pj_pool_alloc(pool, m->buff_size);
+ if (!m->buff)
+ return PJ_ENOMEM;
+
+ /* Create lock object */
+ status = pj_lock_create_simple_mutex(pool, "mport", &m->lock);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Create media clock */
+ status = pjmedia_clock_create(pool, clock_rate, channel_count,
+ samples_per_frame, options, &clock_callback,
+ m, &m->clock);
+ if (status != PJ_SUCCESS) {
+ pj_lock_destroy(m->lock);
+ return status;
+ }
+
+ /* Done */
+ *p_m = m;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Start the media flow.
+ */
+PJ_DEF(pj_status_t) pjmedia_master_port_start(pjmedia_master_port *m)
+{
+ PJ_ASSERT_RETURN(m && m->clock, PJ_EINVAL);
+ PJ_ASSERT_RETURN(m->u_port && m->d_port, PJ_EINVALIDOP);
+
+ return pjmedia_clock_start(m->clock);
+}
+
+
+/*
+ * Stop the media flow.
+ */
+PJ_DEF(pj_status_t) pjmedia_master_port_stop(pjmedia_master_port *m)
+{
+ PJ_ASSERT_RETURN(m && m->clock, PJ_EINVAL);
+
+ return pjmedia_clock_stop(m->clock);
+}
+
+
+/* Poll the master port clock */
+PJ_DEF(pj_bool_t) pjmedia_master_port_wait( pjmedia_master_port *m,
+ pj_bool_t wait,
+ pj_timestamp *ts)
+{
+ PJ_ASSERT_RETURN(m && m->clock, PJ_FALSE);
+
+ return pjmedia_clock_wait(m->clock, wait, ts);
+}
+
+/*
+ * Callback to be called for each clock ticks.
+ */
+static void clock_callback(const pj_timestamp *ts, void *user_data)
+{
+ pjmedia_master_port *m = (pjmedia_master_port*) user_data;
+ pjmedia_frame frame;
+ pj_status_t status;
+
+
+ /* Lock access to ports. */
+ pj_lock_acquire(m->lock);
+
+ /* Get frame from upstream port and pass it to downstream port */
+ pj_bzero(&frame, sizeof(frame));
+ frame.buf = m->buff;
+ frame.size = m->buff_size;
+ frame.timestamp.u64 = ts->u64;
+
+ status = pjmedia_port_get_frame(m->u_port, &frame);
+ if (status != PJ_SUCCESS)
+ frame.type = PJMEDIA_FRAME_TYPE_NONE;
+
+ status = pjmedia_port_put_frame(m->d_port, &frame);
+
+ /* Get frame from downstream port and pass it to upstream port */
+ pj_bzero(&frame, sizeof(frame));
+ frame.buf = m->buff;
+ frame.size = m->buff_size;
+ frame.timestamp.u64 = ts->u64;
+
+ status = pjmedia_port_get_frame(m->d_port, &frame);
+ if (status != PJ_SUCCESS)
+ frame.type = PJMEDIA_FRAME_TYPE_NONE;
+
+ status = pjmedia_port_put_frame(m->u_port, &frame);
+
+ /* Release lock */
+ pj_lock_release(m->lock);
+}
+
+
+/*
+ * Change the upstream port.
+ */
+PJ_DEF(pj_status_t) pjmedia_master_port_set_uport(pjmedia_master_port *m,
+ pjmedia_port *port)
+{
+ PJ_ASSERT_RETURN(m && port, PJ_EINVAL);
+
+ /* Only supports audio for now */
+ PJ_ASSERT_RETURN(port->info.fmt.type==PJMEDIA_TYPE_AUDIO, PJ_ENOTSUP);
+
+ /* If we have downstream port, make sure they have matching samples per
+ * frame.
+ */
+ if (m->d_port) {
+ PJ_ASSERT_RETURN(
+ PJMEDIA_PIA_PTIME(&port->info) ==
+ PJMEDIA_PIA_PTIME(&m->d_port->info),
+ PJMEDIA_ENCSAMPLESPFRAME
+ );
+ }
+
+ pj_lock_acquire(m->lock);
+
+ m->u_port = port;
+
+ pj_lock_release(m->lock);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the upstream port.
+ */
+PJ_DEF(pjmedia_port*) pjmedia_master_port_get_uport(pjmedia_master_port*m)
+{
+ PJ_ASSERT_RETURN(m, NULL);
+ return m->u_port;
+}
+
+
+/*
+ * Change the downstream port.
+ */
+PJ_DEF(pj_status_t) pjmedia_master_port_set_dport(pjmedia_master_port *m,
+ pjmedia_port *port)
+{
+ PJ_ASSERT_RETURN(m && port, PJ_EINVAL);
+
+ /* Only supports audio for now */
+ PJ_ASSERT_RETURN(port->info.fmt.type==PJMEDIA_TYPE_AUDIO, PJ_ENOTSUP);
+
+ /* If we have upstream port, make sure they have matching samples per
+ * frame.
+ */
+ if (m->u_port) {
+ PJ_ASSERT_RETURN(
+ PJMEDIA_PIA_PTIME(&port->info) ==
+ PJMEDIA_PIA_PTIME(&m->u_port->info),
+ PJMEDIA_ENCSAMPLESPFRAME
+ );
+ }
+
+ pj_lock_acquire(m->lock);
+
+ m->d_port = port;
+
+ pj_lock_release(m->lock);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the downstream port.
+ */
+PJ_DEF(pjmedia_port*) pjmedia_master_port_get_dport(pjmedia_master_port*m)
+{
+ PJ_ASSERT_RETURN(m, NULL);
+ return m->d_port;
+}
+
+
+/*
+ * Destroy the master port, and optionally destroy the u_port and
+ * d_port ports.
+ */
+PJ_DEF(pj_status_t) pjmedia_master_port_destroy(pjmedia_master_port *m,
+ pj_bool_t destroy_ports)
+{
+ PJ_ASSERT_RETURN(m, PJ_EINVAL);
+
+ if (m->clock) {
+ pjmedia_clock_destroy(m->clock);
+ m->clock = NULL;
+ }
+
+ if (m->u_port && destroy_ports) {
+ pjmedia_port_destroy(m->u_port);
+ m->u_port = NULL;
+ }
+
+ if (m->d_port && destroy_ports) {
+ pjmedia_port_destroy(m->d_port);
+ m->d_port = NULL;
+ }
+
+ if (m->lock) {
+ pj_lock_destroy(m->lock);
+ m->lock = NULL;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
diff --git a/pjmedia/src/pjmedia/mem_capture.c b/pjmedia/src/pjmedia/mem_capture.c
new file mode 100644
index 0000000..96dd703
--- /dev/null
+++ b/pjmedia/src/pjmedia/mem_capture.c
@@ -0,0 +1,235 @@
+/* $Id: mem_capture.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/mem_port.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/pool.h>
+
+
+#define THIS_FILE "mem_capture.c"
+
+#define SIGNATURE PJMEDIA_SIG_PORT_MEM_CAPTURE
+#define BYTES_PER_SAMPLE 2
+
+struct mem_rec
+{
+ pjmedia_port base;
+
+ unsigned options;
+
+ char *buffer;
+ pj_size_t buf_size;
+ char *write_pos;
+
+ pj_bool_t eof;
+ void *user_data;
+ pj_status_t (*cb)(pjmedia_port *port,
+ void *user_data);
+};
+
+
+static pj_status_t rec_put_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t rec_get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t rec_on_destroy(pjmedia_port *this_port);
+
+
+PJ_DEF(pj_status_t) pjmedia_mem_capture_create( pj_pool_t *pool,
+ void *buffer,
+ pj_size_t size,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ unsigned options,
+ pjmedia_port **p_port)
+{
+ struct mem_rec *rec;
+ const pj_str_t name = { "memrec", 6 };
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(pool && buffer && size && clock_rate && channel_count &&
+ samples_per_frame && bits_per_sample && p_port,
+ PJ_EINVAL);
+
+ /* Can only support 16bit PCM */
+ PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL);
+
+
+ rec = PJ_POOL_ZALLOC_T(pool, struct mem_rec);
+ PJ_ASSERT_RETURN(rec != NULL, PJ_ENOMEM);
+
+ /* Create the rec */
+ pjmedia_port_info_init(&rec->base.info, &name, SIGNATURE,
+ clock_rate, channel_count, bits_per_sample,
+ samples_per_frame);
+
+
+ rec->base.put_frame = &rec_put_frame;
+ rec->base.get_frame = &rec_get_frame;
+ rec->base.on_destroy = &rec_on_destroy;
+
+
+ /* Save the buffer */
+ rec->buffer = rec->write_pos = (char*)buffer;
+ rec->buf_size = size;
+
+ /* Options */
+ rec->options = options;
+
+ *p_port = &rec->base;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Register a callback to be called when the file reading has reached the
+ * end of buffer.
+ */
+PJ_DEF(pj_status_t) pjmedia_mem_capture_set_eof_cb( pjmedia_port *port,
+ void *user_data,
+ pj_status_t (*cb)(pjmedia_port *port,
+ void *usr_data))
+{
+ struct mem_rec *rec;
+
+ PJ_ASSERT_RETURN(port->info.signature == SIGNATURE,
+ PJ_EINVALIDOP);
+
+ rec = (struct mem_rec*) port;
+ rec->user_data = user_data;
+ rec->cb = cb;
+
+ return PJ_SUCCESS;
+}
+
+
+/* Get current buffer size */
+PJ_DEF(pj_size_t) pjmedia_mem_capture_get_size(pjmedia_port *port)
+{
+ struct mem_rec *rec;
+
+ PJ_ASSERT_RETURN(port->info.signature == SIGNATURE,
+ 0);
+
+ rec = (struct mem_rec*) port;
+ if (rec->eof){
+ return rec->buf_size;
+ }
+ return rec->write_pos - rec->buffer;
+}
+
+
+static pj_status_t rec_put_frame( pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ struct mem_rec *rec;
+ char *endpos;
+ pj_size_t size_written;
+
+ PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE,
+ PJ_EINVALIDOP);
+
+ rec = (struct mem_rec*) this_port;
+
+ if (rec->eof) {
+ return PJ_EEOF;
+ }
+
+ size_written = 0;
+ endpos = rec->buffer + rec->buf_size;
+
+ while (size_written < frame->size) {
+ pj_size_t max;
+
+ max = frame->size - size_written;
+ if ((endpos - rec->write_pos) < (int)max)
+ max = endpos - rec->write_pos;
+
+ pj_memcpy(rec->write_pos, ((char*)frame->buf)+size_written, max);
+ size_written += max;
+ rec->write_pos += max;
+
+ pj_assert(rec->write_pos <= endpos);
+
+ if (rec->write_pos == endpos) {
+
+ /* Rewind */
+ rec->write_pos = rec->buffer;
+
+ /* Call callback, if any */
+ if (rec->cb) {
+ pj_status_t status;
+
+ rec->eof = PJ_TRUE;
+ status = (*rec->cb)(this_port, rec->user_data);
+ if (status != PJ_SUCCESS) {
+ /* Must not access recorder from here on. It may be
+ * destroyed by application.
+ */
+ return status;
+ }
+ rec->eof = PJ_FALSE;
+ }
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t rec_get_frame( pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE,
+ PJ_EINVALIDOP);
+
+ PJ_UNUSED_ARG(this_port);
+
+ frame->size = 0;
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t rec_on_destroy(pjmedia_port *this_port)
+{
+ /* Call callback if data was captured
+ * and we're not in the callback already.
+ */
+ struct mem_rec *rec;
+
+ PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE,
+ PJ_EINVALIDOP);
+
+ rec = (struct mem_rec*) this_port;
+
+ if(rec->cb && PJ_FALSE == rec->eof) {
+ rec->eof = PJ_TRUE;
+ (*rec->cb)(this_port, rec->user_data);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
diff --git a/pjmedia/src/pjmedia/mem_player.c b/pjmedia/src/pjmedia/mem_player.c
new file mode 100644
index 0000000..be2ae24
--- /dev/null
+++ b/pjmedia/src/pjmedia/mem_player.c
@@ -0,0 +1,224 @@
+/* $Id: mem_player.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/mem_port.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/pool.h>
+
+
+#define THIS_FILE "mem_player.c"
+
+#define SIGNATURE PJMEDIA_SIG_PORT_MEM_PLAYER
+#define BYTES_PER_SAMPLE 2
+
+struct mem_player
+{
+ pjmedia_port base;
+
+ unsigned options;
+ pj_timestamp timestamp;
+
+ char *buffer;
+ pj_size_t buf_size;
+ char *read_pos;
+
+ pj_bool_t eof;
+ void *user_data;
+ pj_status_t (*cb)(pjmedia_port *port,
+ void *user_data);
+
+};
+
+
+static pj_status_t mem_put_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t mem_get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t mem_on_destroy(pjmedia_port *this_port);
+
+
+PJ_DEF(pj_status_t) pjmedia_mem_player_create( pj_pool_t *pool,
+ const void *buffer,
+ pj_size_t size,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ unsigned options,
+ pjmedia_port **p_port )
+{
+ struct mem_player *port;
+ pj_str_t name = pj_str("memplayer");
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(pool && buffer && size && clock_rate && channel_count &&
+ samples_per_frame && bits_per_sample && p_port,
+ PJ_EINVAL);
+
+ /* Can only support 16bit PCM */
+ PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL);
+
+
+ port = PJ_POOL_ZALLOC_T(pool, struct mem_player);
+ PJ_ASSERT_RETURN(port != NULL, PJ_ENOMEM);
+
+ /* Create the port */
+ pjmedia_port_info_init(&port->base.info, &name, SIGNATURE, clock_rate,
+ channel_count, bits_per_sample, samples_per_frame);
+
+ port->base.put_frame = &mem_put_frame;
+ port->base.get_frame = &mem_get_frame;
+ port->base.on_destroy = &mem_on_destroy;
+
+
+ /* Save the buffer */
+ port->buffer = port->read_pos = (char*)buffer;
+ port->buf_size = size;
+
+ /* Options */
+ port->options = options;
+
+ *p_port = &port->base;
+
+ return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Register a callback to be called when the file reading has reached the
+ * end of buffer.
+ */
+PJ_DEF(pj_status_t) pjmedia_mem_player_set_eof_cb( pjmedia_port *port,
+ void *user_data,
+ pj_status_t (*cb)(pjmedia_port *port,
+ void *usr_data))
+{
+ struct mem_player *player;
+
+ PJ_ASSERT_RETURN(port->info.signature == SIGNATURE,
+ PJ_EINVALIDOP);
+
+ player = (struct mem_player*) port;
+ player->user_data = user_data;
+ player->cb = cb;
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t mem_put_frame( pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ PJ_UNUSED_ARG(this_port);
+ PJ_UNUSED_ARG(frame);
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t mem_get_frame( pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ struct mem_player *player;
+ char *endpos;
+ pj_size_t size_needed, size_written;
+
+ PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE,
+ PJ_EINVALIDOP);
+
+ player = (struct mem_player*) this_port;
+
+ if (player->eof) {
+ pj_status_t status = PJ_SUCCESS;
+
+ /* Call callback, if any */
+ if (player->cb)
+ status = (*player->cb)(this_port, player->user_data);
+
+ /* 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) || (player->options & PJMEDIA_MEM_NO_LOOP)) {
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+ return PJ_EEOF;
+ }
+
+ player->eof = PJ_FALSE;
+ }
+
+ size_needed = PJMEDIA_PIA_AVG_FSZ(&this_port->info);
+ size_written = 0;
+ endpos = player->buffer + player->buf_size;
+
+ while (size_written < size_needed) {
+ char *dst = ((char*)frame->buf) + size_written;
+ pj_size_t max;
+
+ max = size_needed - size_written;
+ if (endpos - player->read_pos < (int)max)
+ max = endpos - player->read_pos;
+
+ pj_memcpy(dst, player->read_pos, max);
+ size_written += max;
+ player->read_pos += max;
+
+ pj_assert(player->read_pos <= endpos);
+
+ if (player->read_pos == endpos) {
+ /* Set EOF flag */
+ player->eof = PJ_TRUE;
+ /* Reset read pointer */
+ player->read_pos = player->buffer;
+
+ /* Pad with zeroes then return for no looped play */
+ if (player->options & PJMEDIA_MEM_NO_LOOP) {
+ pj_size_t null_len;
+
+ null_len = size_needed - size_written;
+ pj_bzero(dst + max, null_len);
+ break;
+ }
+ }
+ }
+
+ frame->size = PJMEDIA_PIA_AVG_FSZ(&this_port->info);
+ frame->timestamp.u64 = player->timestamp.u64;
+ frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+
+ player->timestamp.u64 += PJMEDIA_PIA_SPF(&this_port->info);
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t mem_on_destroy(pjmedia_port *this_port)
+{
+ PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE,
+ PJ_EINVALIDOP);
+
+ /* Destroy signature */
+ this_port->info.signature = 0;
+
+ return PJ_SUCCESS;
+}
+
+
diff --git a/pjmedia/src/pjmedia/null_port.c b/pjmedia/src/pjmedia/null_port.c
new file mode 100644
index 0000000..ac150c9
--- /dev/null
+++ b/pjmedia/src/pjmedia/null_port.c
@@ -0,0 +1,101 @@
+/* $Id: null_port.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/null_port.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#define SIGNATURE PJMEDIA_SIG_PORT_NULL
+
+static pj_status_t null_get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t null_put_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t null_on_destroy(pjmedia_port *this_port);
+
+
+PJ_DEF(pj_status_t) pjmedia_null_port_create( pj_pool_t *pool,
+ unsigned sampling_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ pjmedia_port **p_port )
+{
+ pjmedia_port *port;
+ const pj_str_t name = pj_str("null-port");
+
+ PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL);
+
+ port = PJ_POOL_ZALLOC_T(pool, pjmedia_port);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ pjmedia_port_info_init(&port->info, &name, SIGNATURE, sampling_rate,
+ channel_count, bits_per_sample, samples_per_frame);
+
+ port->get_frame = &null_get_frame;
+ port->put_frame = &null_put_frame;
+ port->on_destroy = &null_on_destroy;
+
+
+ *p_port = port;
+
+ return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Put frame to file.
+ */
+static pj_status_t null_put_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ PJ_UNUSED_ARG(this_port);
+ PJ_UNUSED_ARG(frame);
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get frame from file.
+ */
+static pj_status_t null_get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame->size = PJMEDIA_PIA_AVG_FSZ(&this_port->info);
+ frame->timestamp.u32.lo += PJMEDIA_PIA_SPF(&this_port->info);
+ pjmedia_zero_samples((pj_int16_t*)frame->buf,
+ PJMEDIA_PIA_SPF(&this_port->info));
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy port.
+ */
+static pj_status_t null_on_destroy(pjmedia_port *this_port)
+{
+ PJ_UNUSED_ARG(this_port);
+ return PJ_SUCCESS;
+}
diff --git a/pjmedia/src/pjmedia/plc_common.c b/pjmedia/src/pjmedia/plc_common.c
new file mode 100644
index 0000000..6849836
--- /dev/null
+++ b/pjmedia/src/pjmedia/plc_common.c
@@ -0,0 +1,164 @@
+/* $Id: plc_common.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/plc.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/wsola.h>
+#include <pj/assert.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+static void* plc_wsola_create(pj_pool_t*, unsigned c, unsigned f);
+static void plc_wsola_save(void*, pj_int16_t*);
+static void plc_wsola_generate(void*, pj_int16_t*);
+
+/**
+ * This struct is used internally to represent a PLC backend.
+ */
+struct plc_alg
+{
+ void* (*plc_create)(pj_pool_t*, unsigned c, unsigned f);
+ void (*plc_save)(void*, pj_int16_t*);
+ void (*plc_generate)(void*, pj_int16_t*);
+};
+
+
+static struct plc_alg plc_wsola =
+{
+ &plc_wsola_create,
+ &plc_wsola_save,
+ &plc_wsola_generate
+};
+
+
+struct pjmedia_plc
+{
+ void *obj;
+ struct plc_alg *op;
+};
+
+
+/*
+ * Create PLC session. This function will select the PLC algorithm to
+ * use based on the arguments.
+ */
+PJ_DEF(pj_status_t) pjmedia_plc_create( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned samples_per_frame,
+ unsigned options,
+ pjmedia_plc **p_plc)
+{
+ pjmedia_plc *plc;
+
+ PJ_ASSERT_RETURN(pool && clock_rate && samples_per_frame && p_plc,
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(options == 0, PJ_EINVAL);
+
+ PJ_UNUSED_ARG(options);
+
+ plc = PJ_POOL_ZALLOC_T(pool, pjmedia_plc);
+
+ plc->op = &plc_wsola;
+ plc->obj = plc->op->plc_create(pool, clock_rate, samples_per_frame);
+
+ *p_plc = plc;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Save a good frame to PLC.
+ */
+PJ_DEF(pj_status_t) pjmedia_plc_save( pjmedia_plc *plc,
+ pj_int16_t *frame )
+{
+ PJ_ASSERT_RETURN(plc && frame, PJ_EINVAL);
+
+ plc->op->plc_save(plc->obj, frame);
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Generate a replacement for lost frame.
+ */
+PJ_DEF(pj_status_t) pjmedia_plc_generate( pjmedia_plc *plc,
+ pj_int16_t *frame )
+{
+ PJ_ASSERT_RETURN(plc && frame, PJ_EINVAL);
+
+ plc->op->plc_generate(plc->obj, frame);
+ return PJ_SUCCESS;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+/*
+ * Packet loss concealment based on WSOLA
+ */
+struct wsola_plc
+{
+ pjmedia_wsola *wsola;
+ pj_bool_t prev_lost;
+};
+
+
+static void* plc_wsola_create(pj_pool_t *pool, unsigned clock_rate,
+ unsigned samples_per_frame)
+{
+ struct wsola_plc *o;
+ unsigned flag;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(clock_rate);
+
+ o = PJ_POOL_ZALLOC_T(pool, struct wsola_plc);
+ o->prev_lost = PJ_FALSE;
+
+ flag = PJMEDIA_WSOLA_NO_DISCARD;
+ if (PJMEDIA_WSOLA_PLC_NO_FADING)
+ flag |= PJMEDIA_WSOLA_NO_FADING;
+
+ status = pjmedia_wsola_create(pool, clock_rate, samples_per_frame, 1,
+ flag, &o->wsola);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ return o;
+}
+
+static void plc_wsola_save(void *plc, pj_int16_t *frame)
+{
+ struct wsola_plc *o = (struct wsola_plc*) plc;
+
+ pjmedia_wsola_save(o->wsola, frame, o->prev_lost);
+ o->prev_lost = PJ_FALSE;
+}
+
+static void plc_wsola_generate(void *plc, pj_int16_t *frame)
+{
+ struct wsola_plc *o = (struct wsola_plc*) plc;
+
+ pjmedia_wsola_generate(o->wsola, frame);
+ o->prev_lost = PJ_TRUE;
+}
+
+
diff --git a/pjmedia/src/pjmedia/port.c b/pjmedia/src/pjmedia/port.c
new file mode 100644
index 0000000..b38b718
--- /dev/null
+++ b/pjmedia/src/pjmedia/port.c
@@ -0,0 +1,138 @@
+/* $Id: port.c 3893 2011-12-01 10:49:07Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/port.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+
+#define THIS_FILE "port.c"
+
+
+/**
+ * This is an auxiliary function to initialize port info for
+ * ports which deal with PCM audio.
+ */
+PJ_DEF(pj_status_t) pjmedia_port_info_init( pjmedia_port_info *info,
+ const pj_str_t *name,
+ unsigned signature,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned bits_per_sample,
+ unsigned samples_per_frame)
+{
+#define USEC_IN_SEC (pj_uint64_t)1000000
+ unsigned frame_time_usec, avg_bps;
+
+ pj_bzero(info, sizeof(*info));
+
+ info->signature = signature;
+ info->dir = PJMEDIA_DIR_ENCODING_DECODING;
+ info->name = *name;
+
+ frame_time_usec = (unsigned)(samples_per_frame * USEC_IN_SEC /
+ channel_count / clock_rate);
+ avg_bps = clock_rate * channel_count * bits_per_sample;
+
+ pjmedia_format_init_audio(&info->fmt, PJMEDIA_FORMAT_L16, clock_rate,
+ channel_count, bits_per_sample, frame_time_usec,
+ avg_bps, avg_bps);
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_port_info_init2( pjmedia_port_info *info,
+ const pj_str_t *name,
+ unsigned signature,
+ pjmedia_dir dir,
+ const pjmedia_format *fmt)
+{
+ pj_bzero(info, sizeof(*info));
+ info->signature = signature;
+ info->dir = dir;
+ info->name = *name;
+
+ pjmedia_format_copy(&info->fmt, fmt);
+
+ return PJ_SUCCESS;
+}
+
+/**
+ * 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).
+ */
+PJ_DEF(pj_status_t) pjmedia_port_get_frame( pjmedia_port *port,
+ pjmedia_frame *frame )
+{
+ PJ_ASSERT_RETURN(port && frame, PJ_EINVAL);
+
+ if (port->get_frame)
+ return port->get_frame(port, frame);
+ else {
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+ return PJ_EINVALIDOP;
+ }
+}
+
+
+/**
+ * Put a frame to the port (and subsequent downstream ports).
+ */
+PJ_DEF(pj_status_t) pjmedia_port_put_frame( pjmedia_port *port,
+ pjmedia_frame *frame )
+{
+ PJ_ASSERT_RETURN(port && frame, PJ_EINVAL);
+
+ if (port->put_frame)
+ return port->put_frame(port, frame);
+ else
+ return PJ_EINVALIDOP;
+}
+
+/**
+ * Destroy port (and subsequent downstream ports)
+ */
+PJ_DEF(pj_status_t) pjmedia_port_destroy( pjmedia_port *port )
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(port, PJ_EINVAL);
+
+ if (port->on_destroy)
+ status = port->on_destroy(port);
+ else
+ status = PJ_SUCCESS;
+
+ return status;
+}
+
+
+
diff --git a/pjmedia/src/pjmedia/resample_libsamplerate.c b/pjmedia/src/pjmedia/resample_libsamplerate.c
new file mode 100644
index 0000000..1f7cd57
--- /dev/null
+++ b/pjmedia/src/pjmedia/resample_libsamplerate.c
@@ -0,0 +1,207 @@
+/* $Id: resample_libsamplerate.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/resample.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+
+/*
+ * HOW TO ACTIVATE LIBSAMPLERATE (a.k.a SRC/Secret Rabbit Code) AS
+ * PJMEDIA'S SAMPLE RATE CONVERSION BACKEND
+ *
+ * See README.txt in third_party/samplerate directory.
+ */
+
+
+#if PJMEDIA_RESAMPLE_IMP==PJMEDIA_RESAMPLE_LIBSAMPLERATE
+
+#include "../../third_party/libsamplerate/src/samplerate.h"
+
+#define THIS_FILE "resample_libsamplerate.c"
+
+#if defined(_MSC_VER)
+# ifdef _DEBUG
+# pragma comment( lib, "../../third_party/lib/libsamplerate-i386-win32-vc-debug.lib")
+# else
+# pragma comment( lib, "../../third_party/lib/libsamplerate-i386-win32-vc-release.lib")
+# endif
+#endif
+
+
+struct pjmedia_resample
+{
+ SRC_STATE *state;
+ unsigned in_samples;
+ unsigned out_samples;
+ float *frame_in, *frame_out;
+ unsigned in_extra, out_extra;
+ double ratio;
+};
+
+
+PJ_DEF(pj_status_t) pjmedia_resample_create( pj_pool_t *pool,
+ pj_bool_t high_quality,
+ pj_bool_t large_filter,
+ unsigned channel_count,
+ unsigned rate_in,
+ unsigned rate_out,
+ unsigned samples_per_frame,
+ pjmedia_resample **p_resample)
+{
+ pjmedia_resample *resample;
+ int type, err;
+
+ PJ_ASSERT_RETURN(pool && p_resample && rate_in &&
+ rate_out && samples_per_frame, PJ_EINVAL);
+
+ resample = PJ_POOL_ZALLOC_T(pool, pjmedia_resample);
+ PJ_ASSERT_RETURN(resample, PJ_ENOMEM);
+
+ /* Select conversion type */
+ if (high_quality) {
+ type = large_filter ? SRC_SINC_BEST_QUALITY : SRC_SINC_MEDIUM_QUALITY;
+ } else {
+ type = large_filter ? SRC_SINC_FASTEST : SRC_LINEAR;
+ }
+
+ /* Create converter */
+ resample->state = src_new(type, channel_count, &err);
+ if (resample->state == NULL) {
+ PJ_LOG(4,(THIS_FILE, "Error creating resample: %s",
+ src_strerror(err)));
+ return PJMEDIA_ERROR;
+ }
+
+ /* Calculate ratio */
+ resample->ratio = rate_out * 1.0 / rate_in;
+
+ /* Calculate number of samples for input and output */
+ resample->in_samples = samples_per_frame;
+ resample->out_samples = rate_out / (rate_in / samples_per_frame);
+
+ resample->frame_in = (float*)
+ pj_pool_calloc(pool,
+ resample->in_samples + 8,
+ sizeof(float));
+
+ resample->frame_out = (float*)
+ pj_pool_calloc(pool,
+ resample->out_samples + 8,
+ sizeof(float));
+
+ /* Set the converter ratio */
+ err = src_set_ratio(resample->state, resample->ratio);
+ if (err != 0) {
+ PJ_LOG(4,(THIS_FILE, "Error creating resample: %s",
+ src_strerror(err)));
+ return PJMEDIA_ERROR;
+ }
+
+ /* Done */
+
+ PJ_LOG(5,(THIS_FILE,
+ "Resample using libsamplerate %s, type=%s (%s), "
+ "ch=%d, in/out rate=%d/%d",
+ src_get_version(),
+ src_get_name(type), src_get_description(type),
+ channel_count, rate_in, rate_out));
+
+ *p_resample = resample;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(void) pjmedia_resample_run( pjmedia_resample *resample,
+ const pj_int16_t *input,
+ pj_int16_t *output )
+{
+ SRC_DATA src_data;
+
+ /* Convert samples to float */
+ src_short_to_float_array(input, resample->frame_in,
+ resample->in_samples);
+
+ if (resample->in_extra) {
+ unsigned i;
+
+ for (i=0; i<resample->in_extra; ++i)
+ resample->frame_in[resample->in_samples+i] =
+ resample->frame_in[resample->in_samples-1];
+ }
+
+ /* Prepare SRC_DATA */
+ pj_bzero(&src_data, sizeof(src_data));
+ src_data.data_in = resample->frame_in;
+ src_data.data_out = resample->frame_out;
+ src_data.input_frames = resample->in_samples + resample->in_extra;
+ src_data.output_frames = resample->out_samples + resample->out_extra;
+ src_data.src_ratio = resample->ratio;
+
+ /* Process! */
+ src_process(resample->state, &src_data);
+
+ /* Convert output back to short */
+ src_float_to_short_array(resample->frame_out, output,
+ src_data.output_frames_gen);
+
+ /* Replay last sample if conversion couldn't fill up the whole
+ * frame. This could happen for example with 22050 to 16000 conversion.
+ */
+ if (src_data.output_frames_gen < (int)resample->out_samples) {
+ unsigned i;
+
+ if (resample->in_extra < 4)
+ resample->in_extra++;
+
+ for (i=src_data.output_frames_gen;
+ i<resample->out_samples; ++i)
+ {
+ output[i] = output[src_data.output_frames_gen-1];
+ }
+ }
+}
+
+
+PJ_DEF(unsigned) pjmedia_resample_get_input_size(pjmedia_resample *resample)
+{
+ PJ_ASSERT_RETURN(resample != NULL, 0);
+ return resample->in_samples;
+}
+
+
+PJ_DEF(void) pjmedia_resample_destroy(pjmedia_resample *resample)
+{
+ PJ_ASSERT_ON_FAIL(resample, return);
+ if (resample->state) {
+ src_delete(resample->state);
+ resample->state = NULL;
+
+ PJ_LOG(5,(THIS_FILE, "Resample destroyed"));
+ }
+}
+
+#else /* PJMEDIA_RESAMPLE_IMP==PJMEDIA_RESAMPLE_LIBSAMPLERATE */
+
+int pjmedia_libsamplerate_excluded;
+
+#endif /* PJMEDIA_RESAMPLE_IMP==PJMEDIA_RESAMPLE_LIBSAMPLERATE */
+
diff --git a/pjmedia/src/pjmedia/resample_port.c b/pjmedia/src/pjmedia/resample_port.c
new file mode 100644
index 0000000..06309ac
--- /dev/null
+++ b/pjmedia/src/pjmedia/resample_port.c
@@ -0,0 +1,232 @@
+/* $Id: resample_port.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/resample.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#define BYTES_PER_SAMPLE 2
+#define SIGNATURE PJMEDIA_SIG_PORT_RESAMPLE
+
+
+struct resample_port
+{
+ pjmedia_port base;
+ pjmedia_port *dn_port;
+ unsigned options;
+ pjmedia_resample *resample_get;
+ pjmedia_resample *resample_put;
+ pj_int16_t *get_buf;
+ pj_int16_t *put_buf;
+};
+
+
+
+static pj_status_t resample_put_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t resample_get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t resample_destroy(pjmedia_port *this_port);
+
+
+
+PJ_DEF(pj_status_t) pjmedia_resample_port_create( pj_pool_t *pool,
+ pjmedia_port *dn_port,
+ unsigned clock_rate,
+ unsigned opt,
+ pjmedia_port **p_port )
+{
+ const pj_str_t name = pj_str("resample");
+ struct resample_port *rport;
+ pjmedia_audio_format_detail *d_afd, *r_afd;
+ pj_status_t status;
+
+ /* Validate arguments. */
+ PJ_ASSERT_RETURN(pool && dn_port && clock_rate && p_port, PJ_EINVAL);
+
+ /* Only supports 16bit samples per frame */
+ PJ_ASSERT_RETURN(PJMEDIA_PIA_BITS(&dn_port->info) == 16, PJMEDIA_ENCBITS);
+
+ d_afd = pjmedia_format_get_audio_format_detail(&dn_port->info.fmt, 1);
+
+ /* Create and initialize port. */
+ rport = PJ_POOL_ZALLOC_T(pool, struct resample_port);
+ PJ_ASSERT_RETURN(rport != NULL, PJ_ENOMEM);
+
+ pjmedia_port_info_init(&rport->base.info, &name, SIGNATURE, clock_rate,
+ d_afd->channel_count, BYTES_PER_SAMPLE * 8,
+ clock_rate * d_afd->frame_time_usec / 1000000);
+
+ rport->dn_port = dn_port;
+ rport->options = opt;
+
+ r_afd = pjmedia_format_get_audio_format_detail(&rport->base.info.fmt, 1);
+
+ /* Create buffers.
+ * We need separate buffer for get_frame() and put_frame() since
+ * both functions may run simultaneously.
+ */
+ rport->get_buf = (pj_int16_t*)
+ pj_pool_alloc(pool, PJMEDIA_PIA_AVG_FSZ(&dn_port->info));
+ PJ_ASSERT_RETURN(rport->get_buf != NULL, PJ_ENOMEM);
+
+ rport->put_buf = (pj_int16_t*)
+ pj_pool_alloc(pool, PJMEDIA_PIA_AVG_FSZ(&dn_port->info));
+ PJ_ASSERT_RETURN(rport->put_buf != NULL, PJ_ENOMEM);
+
+
+ /* Create "get_frame" resample */
+ status = pjmedia_resample_create(pool,
+ (opt&PJMEDIA_RESAMPLE_USE_LINEAR)==0,
+ (opt&PJMEDIA_RESAMPLE_USE_SMALL_FILTER)==0,
+ d_afd->channel_count,
+ d_afd->clock_rate,
+ r_afd->clock_rate,
+ PJMEDIA_PIA_SPF(&dn_port->info),
+ &rport->resample_get);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Create "put_frame" resample */
+ status = pjmedia_resample_create(pool,
+ (opt&PJMEDIA_RESAMPLE_USE_LINEAR)==0,
+ (opt&PJMEDIA_RESAMPLE_USE_SMALL_FILTER)==0,
+ d_afd->channel_count,
+ r_afd->clock_rate,
+ d_afd->clock_rate,
+ PJMEDIA_PIA_SPF(&rport->base.info),
+ &rport->resample_put);
+
+ /* Media port interface */
+ rport->base.get_frame = &resample_get_frame;
+ rport->base.put_frame = &resample_put_frame;
+ rport->base.on_destroy = &resample_destroy;
+
+
+ /* Done */
+ *p_port = &rport->base;
+
+ return PJ_SUCCESS;
+}
+
+
+
+static pj_status_t resample_put_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ struct resample_port *rport = (struct resample_port*) this_port;
+ pjmedia_frame downstream_frame;
+
+ /* Return if we don't have downstream port. */
+ if (rport->dn_port == NULL) {
+ return PJ_SUCCESS;
+ }
+
+ if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+ pjmedia_resample_run( rport->resample_put,
+ (const pj_int16_t*) frame->buf,
+ rport->put_buf);
+
+ downstream_frame.buf = rport->put_buf;
+ downstream_frame.size = PJMEDIA_PIA_AVG_FSZ(&rport->dn_port->info);
+ } else {
+ downstream_frame.buf = frame->buf;
+ downstream_frame.size = frame->size;
+ }
+
+ downstream_frame.type = frame->type;
+ downstream_frame.timestamp.u64 = frame->timestamp.u64;
+
+ return pjmedia_port_put_frame( rport->dn_port, &downstream_frame );
+}
+
+
+
+static pj_status_t resample_get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ struct resample_port *rport = (struct resample_port*) this_port;
+ pjmedia_frame tmp_frame;
+ pj_status_t status;
+
+ /* Return silence if we don't have downstream port */
+ if (rport->dn_port == NULL) {
+ pj_bzero(frame->buf, frame->size);
+ return PJ_SUCCESS;
+ }
+
+ tmp_frame.buf = rport->get_buf;
+ tmp_frame.size = PJMEDIA_PIA_AVG_FSZ(&rport->dn_port->info);
+ tmp_frame.timestamp.u64 = frame->timestamp.u64;
+ tmp_frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+
+ status = pjmedia_port_get_frame( rport->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;
+ /* Copy whatever returned as long as the buffer size is enough */
+ frame->size = tmp_frame.size < PJMEDIA_PIA_AVG_FSZ(&rport->base.info) ?
+ tmp_frame.size : PJMEDIA_PIA_AVG_FSZ(&rport->base.info);
+ if (tmp_frame.size) {
+ pjmedia_copy_samples((pj_int16_t*)frame->buf,
+ (const pj_int16_t*)tmp_frame.buf,
+ frame->size >> 1);
+ }
+ return PJ_SUCCESS;
+ }
+
+ pjmedia_resample_run( rport->resample_get,
+ (const pj_int16_t*) tmp_frame.buf,
+ (pj_int16_t*) frame->buf);
+
+ frame->size = PJMEDIA_PIA_AVG_FSZ(&rport->base.info);
+ frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t resample_destroy(pjmedia_port *this_port)
+{
+ struct resample_port *rport = (struct resample_port*) this_port;
+
+ if ((rport->options & PJMEDIA_RESAMPLE_DONT_DESTROY_DN)==0) {
+ pjmedia_port_destroy(rport->dn_port);
+ rport->dn_port = NULL;
+ }
+
+ if (rport->resample_get) {
+ pjmedia_resample_destroy(rport->resample_get);
+ rport->resample_get = NULL;
+ }
+
+ if (rport->resample_put) {
+ pjmedia_resample_destroy(rport->resample_put);
+ rport->resample_put = NULL;
+ }
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjmedia/src/pjmedia/resample_resample.c b/pjmedia/src/pjmedia/resample_resample.c
new file mode 100644
index 0000000..e1a1b0d
--- /dev/null
+++ b/pjmedia/src/pjmedia/resample_resample.c
@@ -0,0 +1,346 @@
+/* $Id: resample_resample.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/resample.h>
+
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+
+
+#if PJMEDIA_RESAMPLE_IMP==PJMEDIA_RESAMPLE_LIBRESAMPLE
+
+#include <third_party/resample/include/resamplesubs.h>
+
+#define THIS_FILE "resample.c"
+
+
+
+struct pjmedia_resample
+{
+ double factor; /* Conversion factor = rate_out / rate_in. */
+ pj_bool_t large_filter; /* Large filter? */
+ pj_bool_t high_quality; /* Not fast? */
+ unsigned xoff; /* History and lookahead size, in samples */
+ unsigned frame_size; /* Samples per frame. */
+ unsigned channel_cnt; /* Channel count. */
+
+ /* Buffer for monochannel */
+ pj_int16_t *buffer; /* Input buffer. */
+
+ /* Buffer for multichannel */
+ pj_int16_t **in_buffer; /* Array of input buffer for each channel. */
+ pj_int16_t *tmp_buffer; /* Temporary output buffer for processing. */
+};
+
+
+PJ_DEF(pj_status_t) pjmedia_resample_create( pj_pool_t *pool,
+ pj_bool_t high_quality,
+ pj_bool_t large_filter,
+ unsigned channel_count,
+ unsigned rate_in,
+ unsigned rate_out,
+ unsigned samples_per_frame,
+ pjmedia_resample **p_resample)
+{
+ pjmedia_resample *resample;
+
+ PJ_ASSERT_RETURN(pool && p_resample && rate_in &&
+ rate_out && samples_per_frame, PJ_EINVAL);
+
+ resample = PJ_POOL_ZALLOC_T(pool, pjmedia_resample);
+ PJ_ASSERT_RETURN(resample, PJ_ENOMEM);
+
+ /*
+ * If we're downsampling, always use the fast algorithm since it seems
+ * to yield the same quality.
+ */
+ if (rate_out < rate_in) {
+ //no this is not a good idea. It sounds pretty good with speech,
+ //but very poor with background noise etc.
+ //high_quality = 0;
+ }
+
+ resample->factor = rate_out * 1.0 / rate_in;
+ resample->large_filter = large_filter;
+ resample->high_quality = high_quality;
+ resample->channel_cnt = channel_count;
+ resample->frame_size = samples_per_frame;
+
+ if (high_quality) {
+ /* This is a bug in xoff calculation, thanks Stephane Lussier
+ * of Macadamian dot com.
+ * resample->xoff = large_filter ? 32 : 6;
+ */
+ resample->xoff = res_GetXOFF(resample->factor, (char)large_filter);
+ } else {
+ resample->xoff = 1;
+ }
+
+ if (channel_count == 1) {
+ unsigned size;
+
+ /* Allocate input buffer */
+ size = (samples_per_frame + 2*resample->xoff) * sizeof(pj_int16_t);
+ resample->buffer = (pj_int16_t*) pj_pool_alloc(pool, size);
+ PJ_ASSERT_RETURN(resample->buffer, PJ_ENOMEM);
+
+ pjmedia_zero_samples(resample->buffer, resample->xoff*2);
+
+ } else if (channel_count > 1) {
+ unsigned i, size;
+
+ /* Allocate input buffer table */
+ size = channel_count * sizeof(pj_int16_t*);
+ resample->in_buffer = (pj_int16_t**)pj_pool_alloc(pool, size);
+
+ /* Allocate input buffer */
+ size = (samples_per_frame/channel_count + 2*resample->xoff) *
+ sizeof(pj_int16_t);
+ for (i = 0; i < channel_count; ++i) {
+ resample->in_buffer[i] = (pj_int16_t*)pj_pool_alloc(pool, size);
+ PJ_ASSERT_RETURN(resample->in_buffer, PJ_ENOMEM);
+ pjmedia_zero_samples(resample->in_buffer[i], resample->xoff*2);
+ }
+
+ /* Allocate temporary output buffer */
+ size = (unsigned) (resample->frame_size * sizeof(pj_int16_t) *
+ resample->factor / channel_count + 0.5);
+ resample->tmp_buffer = (pj_int16_t*) pj_pool_alloc(pool, size);
+ PJ_ASSERT_RETURN(resample->tmp_buffer, PJ_ENOMEM);
+ }
+
+ *p_resample = resample;
+
+ PJ_LOG(5,(THIS_FILE, "resample created: %s qualiy, %s filter, in/out "
+ "rate=%d/%d",
+ (high_quality?"high":"low"),
+ (large_filter?"large":"small"),
+ rate_in, rate_out));
+ return PJ_SUCCESS;
+}
+
+
+
+PJ_DEF(void) pjmedia_resample_run( pjmedia_resample *resample,
+ const pj_int16_t *input,
+ pj_int16_t *output )
+{
+ PJ_ASSERT_ON_FAIL(resample, return);
+
+ /* Okay chaps, here's how we do resampling.
+ *
+ * The original resample algorithm requires xoff samples *before* the
+ * input buffer as history, and another xoff samples *after* the
+ * end of the input buffer as lookahead. Since application can only
+ * supply framesize buffer on each run, PJMEDIA needs to arrange the
+ * buffer to meet these requirements.
+ *
+ * So here comes the trick.
+ *
+ * First of all, because of the history and lookahead requirement,
+ * resample->buffer need to accomodate framesize+2*xoff samples in its
+ * buffer. This is done when the buffer is created.
+ *
+ * On the first run, the input frame (supplied by application) is
+ * copied to resample->buffer at 2*xoff position. The first 2*xoff
+ * samples are initially zeroed (in the initialization). The resample
+ * algorithm then invoked at resample->buffer+xoff ONLY, thus giving
+ * it one xoff at the beginning as zero, and one xoff at the end
+ * as the end of the original input. The resample algorithm will see
+ * that the first xoff samples in the input as zero.
+ *
+ * So here's the layout of resample->buffer on the first run.
+ *
+ * run 0
+ * +------+------+--------------+
+ * | 0000 | 0000 | frame0... |
+ * +------+------+--------------+
+ * ^ ^ ^ ^
+ * 0 xoff 2*xoff size+2*xoff
+ *
+ * (Note again: resample algorithm is called at resample->buffer+xoff)
+ *
+ * At the end of the run, 2*xoff samples from the end of
+ * resample->buffer are copied to the beginning of resample->buffer.
+ * The first xoff part of this will be used as history for the next
+ * run, and the second xoff part of this is actually the start of
+ * resampling for the next run.
+ *
+ * And the first run completes, the function returns.
+ *
+ *
+ * On the next run, the input frame supplied by application is again
+ * copied at 2*xoff position in the resample->buffer, and the
+ * resample algorithm is again invoked at resample->buffer+xoff
+ * position. So effectively, the resample algorithm will start its
+ * operation on the last xoff from the previous frame, and gets the
+ * history from the last 2*xoff of the previous frame, and the look-
+ * ahead from the last xoff of current frame.
+ *
+ * So on this run, the buffer layout is:
+ *
+ * run 1
+ * +------+------+--------------+
+ * | frm0 | frm0 | frame1... |
+ * +------+------+--------------+
+ * ^ ^ ^ ^
+ * 0 xoff 2*xoff size+2*xoff
+ *
+ * As you can see from above diagram, the resampling algorithm is
+ * actually called from the last xoff part of previous frame (frm0).
+ *
+ * And so on the process continues for the next frame, and the next,
+ * and the next, ...
+ *
+ */
+ if (resample->channel_cnt == 1) {
+ pj_int16_t *dst_buf;
+ const pj_int16_t *src_buf;
+
+ /* Prepare input frame */
+ dst_buf = resample->buffer + resample->xoff*2;
+ pjmedia_copy_samples(dst_buf, input, resample->frame_size);
+
+ /* Resample */
+ if (resample->high_quality) {
+ res_Resample(resample->buffer + resample->xoff, output,
+ resample->factor, (pj_uint16_t)resample->frame_size,
+ (char)resample->large_filter, (char)PJ_TRUE);
+ } else {
+ res_SrcLinear(resample->buffer + resample->xoff, output,
+ resample->factor, (pj_uint16_t)resample->frame_size);
+ }
+
+ /* Update history */
+ dst_buf = resample->buffer;
+ src_buf = input + resample->frame_size - resample->xoff*2;
+ pjmedia_copy_samples(dst_buf, src_buf, resample->xoff * 2);
+
+ } else { /* Multichannel */
+ unsigned i, j;
+
+ for (i = 0; i < resample->channel_cnt; ++i) {
+ pj_int16_t *dst_buf;
+ const pj_int16_t *src_buf;
+ unsigned mono_frm_sz_in;
+ unsigned mono_frm_sz_out;
+
+ mono_frm_sz_in = resample->frame_size / resample->channel_cnt;
+ mono_frm_sz_out = (unsigned)(mono_frm_sz_in * resample->factor + 0.5);
+
+ /* Deinterleave input */
+ dst_buf = resample->in_buffer[i] + resample->xoff*2;
+ src_buf = input + i;
+ for (j = 0; j < mono_frm_sz_in; ++j) {
+ *dst_buf++ = *src_buf;
+ src_buf += resample->channel_cnt;
+ }
+
+ /* Resample this channel */
+ if (resample->high_quality) {
+ res_Resample(resample->in_buffer[i] + resample->xoff,
+ resample->tmp_buffer, resample->factor,
+ (pj_uint16_t)mono_frm_sz_in,
+ (char)resample->large_filter, (char)PJ_TRUE);
+ } else {
+ res_SrcLinear( resample->in_buffer[i],
+ resample->tmp_buffer,
+ resample->factor,
+ (pj_uint16_t)mono_frm_sz_in);
+ }
+
+ /* Update history */
+ dst_buf = resample->in_buffer[i];
+ src_buf = resample->in_buffer[i] + mono_frm_sz_in;
+ pjmedia_copy_samples(dst_buf, src_buf, resample->xoff * 2);
+
+ /* Reinterleave output */
+ dst_buf = output + i;
+ src_buf = resample->tmp_buffer;
+ for (j = 0; j < mono_frm_sz_out; ++j) {
+ *dst_buf = *src_buf++;
+ dst_buf += resample->channel_cnt;
+ }
+ }
+ }
+}
+
+PJ_DEF(unsigned) pjmedia_resample_get_input_size(pjmedia_resample *resample)
+{
+ PJ_ASSERT_RETURN(resample != NULL, 0);
+ return resample->frame_size;
+}
+
+PJ_DEF(void) pjmedia_resample_destroy(pjmedia_resample *resample)
+{
+ PJ_UNUSED_ARG(resample);
+}
+
+
+#elif PJMEDIA_RESAMPLE_IMP==PJMEDIA_RESAMPLE_NONE
+
+/*
+ * This is the configuration when sample rate conversion is disabled.
+ */
+PJ_DEF(pj_status_t) pjmedia_resample_create( pj_pool_t *pool,
+ pj_bool_t high_quality,
+ pj_bool_t large_filter,
+ unsigned channel_count,
+ unsigned rate_in,
+ unsigned rate_out,
+ unsigned samples_per_frame,
+ pjmedia_resample **p_resample)
+{
+ PJ_UNUSED_ARG(pool);
+ PJ_UNUSED_ARG(high_quality);
+ PJ_UNUSED_ARG(large_filter);
+ PJ_UNUSED_ARG(channel_count);
+ PJ_UNUSED_ARG(rate_in);
+ PJ_UNUSED_ARG(rate_out);
+ PJ_UNUSED_ARG(samples_per_frame);
+ PJ_UNUSED_ARG(p_resample);
+
+ return PJ_EINVALIDOP;
+}
+
+PJ_DEF(void) pjmedia_resample_run( pjmedia_resample *resample,
+ const pj_int16_t *input,
+ pj_int16_t *output )
+{
+ PJ_UNUSED_ARG(resample);
+ PJ_UNUSED_ARG(input);
+ PJ_UNUSED_ARG(output);
+}
+
+PJ_DEF(unsigned) pjmedia_resample_get_input_size(pjmedia_resample *resample)
+{
+ PJ_UNUSED_ARG(resample);
+ return 0;
+}
+
+PJ_DEF(void) pjmedia_resample_destroy(pjmedia_resample *resample)
+{
+ PJ_UNUSED_ARG(resample);
+}
+
+#endif /* PJMEDIA_RESAMPLE_IMP */
+
diff --git a/pjmedia/src/pjmedia/resample_speex.c b/pjmedia/src/pjmedia/resample_speex.c
new file mode 100644
index 0000000..c2b0e8c
--- /dev/null
+++ b/pjmedia/src/pjmedia/resample_speex.c
@@ -0,0 +1,127 @@
+/* $Id: resample_speex.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/resample.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+
+#if PJMEDIA_RESAMPLE_IMP==PJMEDIA_RESAMPLE_SPEEX
+
+#include <speex/speex_resampler.h>
+
+#define THIS_FILE "resample_speex.c"
+
+
+struct pjmedia_resample
+{
+ SpeexResamplerState *state;
+ unsigned in_samples_per_frame;
+ unsigned out_samples_per_frame;
+};
+
+
+PJ_DEF(pj_status_t) pjmedia_resample_create( pj_pool_t *pool,
+ pj_bool_t high_quality,
+ pj_bool_t large_filter,
+ unsigned channel_count,
+ unsigned rate_in,
+ unsigned rate_out,
+ unsigned samples_per_frame,
+ pjmedia_resample **p_resample)
+{
+ pjmedia_resample *resample;
+ int quality;
+ int err;
+
+ PJ_ASSERT_RETURN(pool && p_resample && rate_in &&
+ rate_out && samples_per_frame, PJ_EINVAL);
+
+ resample = PJ_POOL_ZALLOC_T(pool, pjmedia_resample);
+ PJ_ASSERT_RETURN(resample, PJ_ENOMEM);
+
+ if (high_quality) {
+ if (large_filter)
+ quality = 10;
+ else
+ quality = 7;
+ } else {
+ quality = 3;
+ }
+
+ resample->in_samples_per_frame = samples_per_frame;
+ resample->out_samples_per_frame = rate_out / (rate_in / samples_per_frame);
+ resample->state = speex_resampler_init(channel_count, rate_in, rate_out,
+ quality, &err);
+ if (resample->state == NULL || err != RESAMPLER_ERR_SUCCESS)
+ return PJ_ENOMEM;
+
+
+ *p_resample = resample;
+
+ PJ_LOG(5,(THIS_FILE,
+ "resample created: quality=%d, ch=%d, in/out rate=%d/%d",
+ quality, channel_count, rate_in, rate_out));
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(void) pjmedia_resample_run( pjmedia_resample *resample,
+ const pj_int16_t *input,
+ pj_int16_t *output )
+{
+ spx_uint32_t in_length, out_length;
+
+ PJ_ASSERT_ON_FAIL(resample, return);
+
+ in_length = resample->in_samples_per_frame;
+ out_length = resample->out_samples_per_frame;
+
+ speex_resampler_process_interleaved_int(resample->state,
+ (const __int16 *)input, &in_length,
+ (__int16 *)output, &out_length);
+
+ pj_assert(in_length == resample->in_samples_per_frame);
+ pj_assert(out_length == resample->out_samples_per_frame);
+}
+
+
+PJ_DEF(unsigned) pjmedia_resample_get_input_size(pjmedia_resample *resample)
+{
+ PJ_ASSERT_RETURN(resample != NULL, 0);
+ return resample->in_samples_per_frame;
+}
+
+
+PJ_DEF(void) pjmedia_resample_destroy(pjmedia_resample *resample)
+{
+ PJ_ASSERT_ON_FAIL(resample, return);
+ if (resample->state) {
+ speex_resampler_destroy(resample->state);
+ resample->state = NULL;
+ }
+}
+
+#else /* PJMEDIA_RESAMPLE_IMP==PJMEDIA_RESAMPLE_SPEEX */
+
+int pjmedia_resample_speex_excluded;
+
+#endif /* PJMEDIA_RESAMPLE_IMP==PJMEDIA_RESAMPLE_SPEEX */
+
diff --git a/pjmedia/src/pjmedia/rtcp.c b/pjmedia/src/pjmedia/rtcp.c
new file mode 100644
index 0000000..f57797c
--- /dev/null
+++ b/pjmedia/src/pjmedia/rtcp.c
@@ -0,0 +1,1104 @@
+/* $Id: rtcp.c 3999 2012-03-30 07:10:13Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/rtcp.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/sock.h>
+#include <pj/string.h>
+
+#define THIS_FILE "rtcp.c"
+
+#define RTCP_SR 200
+#define RTCP_RR 201
+#define RTCP_SDES 202
+#define RTCP_BYE 203
+#define RTCP_XR 207
+
+enum {
+ RTCP_SDES_NULL = 0,
+ RTCP_SDES_CNAME = 1,
+ RTCP_SDES_NAME = 2,
+ RTCP_SDES_EMAIL = 3,
+ RTCP_SDES_PHONE = 4,
+ RTCP_SDES_LOC = 5,
+ RTCP_SDES_TOOL = 6,
+ RTCP_SDES_NOTE = 7
+};
+
+#if PJ_HAS_HIGH_RES_TIMER==0
+# error "High resolution timer needs to be enabled"
+#endif
+
+
+
+#if 0
+# define TRACE_(x) PJ_LOG(3,x)
+#else
+# define TRACE_(x) ;
+#endif
+
+
+/*
+ * Get NTP time.
+ */
+PJ_DEF(pj_status_t) pjmedia_rtcp_get_ntp_time(const pjmedia_rtcp_session *sess,
+ pjmedia_rtcp_ntp_rec *ntp)
+{
+/* Seconds between 1900-01-01 to 1970-01-01 */
+#define JAN_1970 (2208988800UL)
+ pj_timestamp ts;
+ pj_status_t status;
+
+ status = pj_get_timestamp(&ts);
+
+ /* Fill up the high 32bit part */
+ ntp->hi = (pj_uint32_t)((ts.u64 - sess->ts_base.u64) / sess->ts_freq.u64)
+ + sess->tv_base.sec + JAN_1970;
+
+ /* Calculate seconds fractions */
+ ts.u64 = (ts.u64 - sess->ts_base.u64) % sess->ts_freq.u64;
+ pj_assert(ts.u64 < sess->ts_freq.u64);
+ ts.u64 = (ts.u64 << 32) / sess->ts_freq.u64;
+
+ /* Fill up the low 32bit part */
+ ntp->lo = ts.u32.lo;
+
+
+#if (defined(PJ_WIN32) && PJ_WIN32!=0) || \
+ (defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE!=0)
+
+ /* On Win32, since we use QueryPerformanceCounter() as the backend
+ * timestamp API, we need to protect against this bug:
+ * Performance counter value may unexpectedly leap forward
+ * http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q274323
+ */
+ {
+ /*
+ * Compare elapsed time reported by timestamp with actual elapsed
+ * time. If the difference is too excessive, then we use system
+ * time instead.
+ */
+
+ /* MIN_DIFF needs to be large enough so that "normal" diff caused
+ * by system activity or context switch doesn't trigger the time
+ * correction.
+ */
+ enum { MIN_DIFF = 400 };
+
+ pj_time_val ts_time, elapsed, diff;
+
+ pj_gettimeofday(&elapsed);
+
+ ts_time.sec = ntp->hi - sess->tv_base.sec - JAN_1970;
+ ts_time.msec = (long)(ntp->lo * 1000.0 / 0xFFFFFFFF);
+
+ PJ_TIME_VAL_SUB(elapsed, sess->tv_base);
+
+ if (PJ_TIME_VAL_LT(ts_time, elapsed)) {
+ diff = elapsed;
+ PJ_TIME_VAL_SUB(diff, ts_time);
+ } else {
+ diff = ts_time;
+ PJ_TIME_VAL_SUB(diff, elapsed);
+ }
+
+ if (PJ_TIME_VAL_MSEC(diff) >= MIN_DIFF) {
+
+ TRACE_((sess->name, "RTCP NTP timestamp corrected by %d ms",
+ PJ_TIME_VAL_MSEC(diff)));
+
+
+ ntp->hi = elapsed.sec + sess->tv_base.sec + JAN_1970;
+ ntp->lo = (elapsed.msec * 65536 / 1000) << 16;
+ }
+
+ }
+#endif
+
+ return status;
+}
+
+
+/*
+ * Initialize RTCP session setting.
+ */
+PJ_DEF(void) pjmedia_rtcp_session_setting_default(
+ pjmedia_rtcp_session_setting *settings)
+{
+ pj_bzero(settings, sizeof(*settings));
+}
+
+
+/*
+ * Initialize bidirectional RTCP statistics.
+ *
+ */
+PJ_DEF(void) pjmedia_rtcp_init_stat(pjmedia_rtcp_stat *stat)
+{
+ pj_time_val now;
+
+ pj_assert(stat);
+
+ pj_bzero(stat, sizeof(pjmedia_rtcp_stat));
+
+ pj_math_stat_init(&stat->rtt);
+ pj_math_stat_init(&stat->rx.loss_period);
+ pj_math_stat_init(&stat->rx.jitter);
+ pj_math_stat_init(&stat->tx.loss_period);
+ pj_math_stat_init(&stat->tx.jitter);
+
+#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
+ pj_math_stat_init(&stat->rx_ipdv);
+#endif
+
+#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
+ pj_math_stat_init(&stat->rx_raw_jitter);
+#endif
+
+ pj_gettimeofday(&now);
+ stat->start = now;
+}
+
+
+/*
+ * Initialize RTCP session.
+ */
+PJ_DEF(void) pjmedia_rtcp_init(pjmedia_rtcp_session *sess,
+ char *name,
+ unsigned clock_rate,
+ unsigned samples_per_frame,
+ pj_uint32_t ssrc)
+{
+ pjmedia_rtcp_session_setting settings;
+
+ pjmedia_rtcp_session_setting_default(&settings);
+ settings.name = name;
+ settings.clock_rate = clock_rate;
+ settings.samples_per_frame = samples_per_frame;
+ settings.ssrc = ssrc;
+
+ pjmedia_rtcp_init2(sess, &settings);
+}
+
+
+/*
+ * Initialize RTCP session.
+ */
+PJ_DEF(void) pjmedia_rtcp_init2( pjmedia_rtcp_session *sess,
+ const pjmedia_rtcp_session_setting *settings)
+{
+ pjmedia_rtcp_sr_pkt *sr_pkt = &sess->rtcp_sr_pkt;
+ pj_time_val now;
+
+ /* Memset everything */
+ pj_bzero(sess, sizeof(pjmedia_rtcp_session));
+
+ /* Last RX timestamp in RTP packet */
+ sess->rtp_last_ts = (unsigned)-1;
+
+ /* Name */
+ sess->name = settings->name ? settings->name : (char*)THIS_FILE;
+
+ /* Set clock rate */
+ sess->clock_rate = settings->clock_rate;
+ sess->pkt_size = settings->samples_per_frame;
+
+ /* Init common RTCP SR header */
+ sr_pkt->common.version = 2;
+ sr_pkt->common.count = 1;
+ sr_pkt->common.pt = RTCP_SR;
+ sr_pkt->common.length = pj_htons(12);
+ sr_pkt->common.ssrc = pj_htonl(settings->ssrc);
+
+ /* Copy to RTCP RR header */
+ pj_memcpy(&sess->rtcp_rr_pkt.common, &sr_pkt->common,
+ sizeof(pjmedia_rtcp_common));
+ sess->rtcp_rr_pkt.common.pt = RTCP_RR;
+ sess->rtcp_rr_pkt.common.length = pj_htons(7);
+
+ /* Get time and timestamp base and frequency */
+ pj_gettimeofday(&now);
+ sess->tv_base = now;
+ pj_get_timestamp(&sess->ts_base);
+ pj_get_timestamp_freq(&sess->ts_freq);
+ sess->rtp_ts_base = settings->rtp_ts_base;
+
+ /* Initialize statistics states */
+ pjmedia_rtcp_init_stat(&sess->stat);
+
+ /* RR will be initialized on receipt of the first RTP packet. */
+}
+
+
+PJ_DEF(void) pjmedia_rtcp_fini(pjmedia_rtcp_session *sess)
+{
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+ pjmedia_rtcp_xr_fini(&sess->xr_session);
+#else
+ /* Nothing to do. */
+ PJ_UNUSED_ARG(sess);
+#endif
+}
+
+static void rtcp_init_seq(pjmedia_rtcp_session *sess)
+{
+ sess->received = 0;
+ sess->exp_prior = 0;
+ sess->rx_prior = 0;
+ sess->transit = 0;
+ sess->jitter = 0;
+}
+
+PJ_DEF(void) pjmedia_rtcp_rx_rtp( pjmedia_rtcp_session *sess,
+ unsigned seq,
+ unsigned rtp_ts,
+ unsigned payload)
+{
+ pjmedia_rtcp_rx_rtp2(sess, seq, rtp_ts, payload, PJ_FALSE);
+}
+
+PJ_DEF(void) pjmedia_rtcp_rx_rtp2(pjmedia_rtcp_session *sess,
+ unsigned seq,
+ unsigned rtp_ts,
+ unsigned payload,
+ pj_bool_t discarded)
+{
+ pj_timestamp ts;
+ pj_uint32_t arrival;
+ pj_int32_t transit;
+ pjmedia_rtp_status seq_st;
+ unsigned last_seq;
+
+#if !defined(PJMEDIA_HAS_RTCP_XR) || (PJMEDIA_HAS_RTCP_XR == 0)
+ PJ_UNUSED_ARG(discarded);
+#endif
+
+ if (sess->stat.rx.pkt == 0) {
+ /* Init sequence for the first time. */
+ pjmedia_rtp_seq_init(&sess->seq_ctrl, (pj_uint16_t)seq);
+ }
+
+ sess->stat.rx.pkt++;
+ sess->stat.rx.bytes += payload;
+
+ /* Process the RTP packet. */
+ last_seq = sess->seq_ctrl.max_seq;
+ pjmedia_rtp_seq_update(&sess->seq_ctrl, (pj_uint16_t)seq, &seq_st);
+
+ if (seq_st.status.flag.restart) {
+ rtcp_init_seq(sess);
+ }
+
+ if (seq_st.status.flag.dup) {
+ sess->stat.rx.dup++;
+ TRACE_((sess->name, "Duplicate packet detected"));
+ }
+
+ if (seq_st.status.flag.outorder && !seq_st.status.flag.probation) {
+ sess->stat.rx.reorder++;
+ TRACE_((sess->name, "Out-of-order packet detected"));
+ }
+
+ if (seq_st.status.flag.bad) {
+ sess->stat.rx.discard++;
+
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+ pjmedia_rtcp_xr_rx_rtp(&sess->xr_session, seq,
+ -1, /* lost */
+ (seq_st.status.flag.dup? 1:0), /* dup */
+ (!seq_st.status.flag.dup? 1:-1), /* discard */
+ -1, /* jitter */
+ -1, 0); /* toh */
+#endif
+
+ TRACE_((sess->name, "Bad packet discarded"));
+ return;
+ }
+
+ /* Only mark "good" packets */
+ ++sess->received;
+
+ /* Calculate loss periods. */
+ if (seq_st.diff > 1) {
+ unsigned count = seq_st.diff - 1;
+ unsigned period;
+
+ period = count * sess->pkt_size * 1000 / sess->clock_rate;
+ period *= 1000;
+
+ /* Update packet lost.
+ * The packet lost number will also be updated when we're sending
+ * outbound RTCP RR.
+ */
+ sess->stat.rx.loss += (seq_st.diff - 1);
+ TRACE_((sess->name, "%d packet(s) lost", seq_st.diff - 1));
+
+ /* Update loss period stat */
+ pj_math_stat_update(&sess->stat.rx.loss_period, period);
+ }
+
+
+ /*
+ * Calculate jitter only when sequence is good (see RFC 3550 section A.8),
+ * AND only when the timestamp is different than the last packet
+ * (see RTP FAQ).
+ */
+ if (seq_st.diff == 1 && rtp_ts != sess->rtp_last_ts) {
+ /* Get arrival time and convert timestamp to samples */
+ pj_get_timestamp(&ts);
+ ts.u64 = ts.u64 * sess->clock_rate / sess->ts_freq.u64;
+ arrival = ts.u32.lo;
+
+ transit = arrival - rtp_ts;
+
+ /* Ignore the first N packets as they normally have bad jitter
+ * due to other threads working to establish the call
+ */
+ if (sess->transit == 0 ||
+ sess->received < PJMEDIA_RTCP_IGNORE_FIRST_PACKETS)
+ {
+ sess->transit = transit;
+ sess->stat.rx.jitter.min = (unsigned)-1;
+ } else {
+ pj_int32_t d;
+ pj_uint32_t jitter;
+
+ d = transit - sess->transit;
+ if (d < 0)
+ d = -d;
+
+ sess->jitter += d - ((sess->jitter + 8) >> 4);
+
+ /* Update jitter stat */
+ jitter = sess->jitter >> 4;
+
+ /* Convert jitter unit from samples to usec */
+ if (jitter < 4294)
+ jitter = jitter * 1000000 / sess->clock_rate;
+ else {
+ jitter = jitter * 1000 / sess->clock_rate;
+ jitter *= 1000;
+ }
+ pj_math_stat_update(&sess->stat.rx.jitter, jitter);
+
+
+#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
+ {
+ pj_uint32_t raw_jitter;
+
+ /* Convert raw jitter unit from samples to usec */
+ if (d < 4294)
+ raw_jitter = d * 1000000 / sess->clock_rate;
+ else {
+ raw_jitter = d * 1000 / sess->clock_rate;
+ raw_jitter *= 1000;
+ }
+
+ /* Update jitter stat */
+ pj_math_stat_update(&sess->stat.rx_raw_jitter, raw_jitter);
+ }
+#endif
+
+
+#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
+ {
+ pj_int32_t ipdv;
+
+ ipdv = transit - sess->transit;
+ /* Convert IPDV unit from samples to usec */
+ if (ipdv > -2147 && ipdv < 2147)
+ ipdv = ipdv * 1000000 / (int)sess->clock_rate;
+ else {
+ ipdv = ipdv * 1000 / (int)sess->clock_rate;
+ ipdv *= 1000;
+ }
+
+ /* Update jitter stat */
+ pj_math_stat_update(&sess->stat.rx_ipdv, ipdv);
+ }
+#endif
+
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+ pjmedia_rtcp_xr_rx_rtp(&sess->xr_session, seq,
+ 0, /* lost */
+ 0, /* dup */
+ discarded, /* discard */
+ (sess->jitter >> 4), /* jitter */
+ -1, 0); /* toh */
+#endif
+
+ /* Update session transit */
+ sess->transit = transit;
+ }
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+ } else if (seq_st.diff > 1) {
+ int i;
+
+ /* Report RTCP XR about packet losses */
+ for (i=seq_st.diff-1; i>0; --i) {
+ pjmedia_rtcp_xr_rx_rtp(&sess->xr_session, seq - i,
+ 1, /* lost */
+ 0, /* dup */
+ 0, /* discard */
+ -1, /* jitter */
+ -1, 0); /* toh */
+ }
+
+ /* Report RTCP XR this packet */
+ pjmedia_rtcp_xr_rx_rtp(&sess->xr_session, seq,
+ 0, /* lost */
+ 0, /* dup */
+ discarded, /* discard */
+ -1, /* jitter */
+ -1, 0); /* toh */
+#endif
+ }
+
+ /* Update timestamp of last RX RTP packet */
+ sess->rtp_last_ts = rtp_ts;
+}
+
+PJ_DEF(void) pjmedia_rtcp_tx_rtp(pjmedia_rtcp_session *sess,
+ unsigned bytes_payload_size)
+{
+ /* Update statistics */
+ sess->stat.tx.pkt++;
+ sess->stat.tx.bytes += bytes_payload_size;
+}
+
+
+static void parse_rtcp_report( pjmedia_rtcp_session *sess,
+ const void *pkt,
+ pj_size_t size)
+{
+ pjmedia_rtcp_common *common = (pjmedia_rtcp_common*) pkt;
+ const pjmedia_rtcp_rr *rr = NULL;
+ const pjmedia_rtcp_sr *sr = NULL;
+ pj_uint32_t last_loss, jitter_samp, jitter;
+
+ /* Parse RTCP */
+ if (common->pt == RTCP_SR) {
+ sr = (pjmedia_rtcp_sr*) (((char*)pkt) + sizeof(pjmedia_rtcp_common));
+ if (common->count > 0 && size >= (sizeof(pjmedia_rtcp_sr_pkt))) {
+ rr = (pjmedia_rtcp_rr*)(((char*)pkt) + (sizeof(pjmedia_rtcp_common)
+ + sizeof(pjmedia_rtcp_sr)));
+ }
+ } else if (common->pt == RTCP_RR && common->count > 0) {
+ rr = (pjmedia_rtcp_rr*)(((char*)pkt) + sizeof(pjmedia_rtcp_common));
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+ } else if (common->pt == RTCP_XR) {
+ if (sess->xr_enabled)
+ pjmedia_rtcp_xr_rx_rtcp_xr(&sess->xr_session, pkt, size);
+
+ return;
+#endif
+ }
+
+
+ if (sr) {
+ /* Save LSR from NTP timestamp of RTCP packet */
+ sess->rx_lsr = ((pj_ntohl(sr->ntp_sec) & 0x0000FFFF) << 16) |
+ ((pj_ntohl(sr->ntp_frac) >> 16) & 0xFFFF);
+
+ /* Calculate SR arrival time for DLSR */
+ pj_get_timestamp(&sess->rx_lsr_time);
+
+ TRACE_((sess->name, "Rx RTCP SR: ntp_ts=%p",
+ sess->rx_lsr,
+ (pj_uint32_t)(sess->rx_lsr_time.u64*65536/sess->ts_freq.u64)));
+ }
+
+
+ /* Nothing more to do if there's no RR packet */
+ if (rr == NULL)
+ return;
+
+
+ last_loss = sess->stat.tx.loss;
+
+ /* Get packet loss */
+ sess->stat.tx.loss = (rr->total_lost_2 << 16) +
+ (rr->total_lost_1 << 8) +
+ rr->total_lost_0;
+
+ TRACE_((sess->name, "Rx RTCP RR: total_lost_2=%x, 1=%x, 0=%x, lost=%d",
+ (int)rr->total_lost_2,
+ (int)rr->total_lost_1,
+ (int)rr->total_lost_0,
+ sess->stat.tx.loss));
+
+ /* We can't calculate the exact loss period for TX, so just give the
+ * best estimation.
+ */
+ if (sess->stat.tx.loss > last_loss) {
+ unsigned period;
+
+ /* Loss period in msec */
+ period = (sess->stat.tx.loss - last_loss) * sess->pkt_size *
+ 1000 / sess->clock_rate;
+
+ /* Loss period in usec */
+ period *= 1000;
+
+ /* Update loss period stat */
+ pj_math_stat_update(&sess->stat.tx.loss_period, period);
+ }
+
+ /* Get jitter value in usec */
+ jitter_samp = pj_ntohl(rr->jitter);
+ /* Calculate jitter in usec, avoiding overflows */
+ if (jitter_samp <= 4294)
+ jitter = jitter_samp * 1000000 / sess->clock_rate;
+ else {
+ jitter = jitter_samp * 1000 / sess->clock_rate;
+ jitter *= 1000;
+ }
+
+ /* Update jitter statistics */
+ pj_math_stat_update(&sess->stat.tx.jitter, jitter);
+
+ /* Can only calculate if LSR and DLSR is present in RR */
+ if (rr->lsr && rr->dlsr) {
+ pj_uint32_t lsr, now, dlsr;
+ pj_uint64_t eedelay;
+ pjmedia_rtcp_ntp_rec ntp;
+
+ /* LSR is the middle 32bit of NTP. It has 1/65536 second
+ * resolution
+ */
+ lsr = pj_ntohl(rr->lsr);
+
+ /* DLSR is delay since LSR, also in 1/65536 resolution */
+ dlsr = pj_ntohl(rr->dlsr);
+
+ /* Get current time, and convert to 1/65536 resolution */
+ pjmedia_rtcp_get_ntp_time(sess, &ntp);
+ now = ((ntp.hi & 0xFFFF) << 16) + (ntp.lo >> 16);
+
+ /* End-to-end delay is (now-lsr-dlsr) */
+ eedelay = now - lsr - dlsr;
+
+ /* Convert end to end delay to usec (keeping the calculation in
+ * 64bit space)::
+ * sess->ee_delay = (eedelay * 1000) / 65536;
+ */
+ if (eedelay < 4294) {
+ eedelay = (eedelay * 1000000) >> 16;
+ } else {
+ eedelay = (eedelay * 1000) >> 16;
+ eedelay *= 1000;
+ }
+
+ TRACE_((sess->name, "Rx RTCP RR: lsr=%p, dlsr=%p (%d:%03dms), "
+ "now=%p, rtt=%p",
+ lsr, dlsr, dlsr/65536, (dlsr%65536)*1000/65536,
+ now, (pj_uint32_t)eedelay));
+
+ /* Only save calculation if "now" is greater than lsr, or
+ * otherwise rtt will be invalid
+ */
+ if (now-dlsr >= lsr) {
+ unsigned rtt = (pj_uint32_t)eedelay;
+
+ /* Check that eedelay value really makes sense.
+ * We allow up to 30 seconds RTT!
+ */
+ if (eedelay > 30 * 1000 * 1000UL) {
+
+ TRACE_((sess->name, "RTT not making any sense, ignored.."));
+ goto end_rtt_calc;
+ }
+
+#if defined(PJMEDIA_RTCP_NORMALIZE_FACTOR) && PJMEDIA_RTCP_NORMALIZE_FACTOR!=0
+ /* "Normalize" rtt value that is exceptionally high. For such
+ * values, "normalize" the rtt to be PJMEDIA_RTCP_NORMALIZE_FACTOR
+ * times the average value.
+ */
+ if (rtt > ((unsigned)sess->stat.rtt.mean *
+ PJMEDIA_RTCP_NORMALIZE_FACTOR) && sess->stat.rtt.n!=0)
+ {
+ unsigned orig_rtt = rtt;
+ rtt = sess->stat.rtt.mean * PJMEDIA_RTCP_NORMALIZE_FACTOR;
+ PJ_LOG(5,(sess->name,
+ "RTT value %d usec is normalized to %d usec",
+ orig_rtt, rtt));
+ }
+#endif
+ TRACE_((sess->name, "RTCP RTT is set to %d usec", rtt));
+
+ /* Update RTT stat */
+ pj_math_stat_update(&sess->stat.rtt, rtt);
+
+ } else {
+ PJ_LOG(5, (sess->name, "Internal RTCP NTP clock skew detected: "
+ "lsr=%p, now=%p, dlsr=%p (%d:%03dms), "
+ "diff=%d",
+ lsr, now, dlsr, dlsr/65536,
+ (dlsr%65536)*1000/65536,
+ dlsr-(now-lsr)));
+ }
+ }
+
+end_rtt_calc:
+
+ pj_gettimeofday(&sess->stat.tx.update);
+ sess->stat.tx.update_cnt++;
+}
+
+
+static void parse_rtcp_sdes(pjmedia_rtcp_session *sess,
+ const void *pkt,
+ pj_size_t size)
+{
+ pjmedia_rtcp_sdes *sdes = &sess->stat.peer_sdes;
+ char *p, *p_end;
+ char *b, *b_end;
+
+ p = (char*)pkt + 8;
+ p_end = (char*)pkt + size;
+
+ pj_bzero(sdes, sizeof(*sdes));
+ b = sess->stat.peer_sdes_buf_;
+ b_end = b + sizeof(sess->stat.peer_sdes_buf_);
+
+ while (p < p_end) {
+ pj_uint8_t sdes_type, sdes_len;
+ pj_str_t sdes_value = {NULL, 0};
+
+ sdes_type = *p++;
+
+ /* Check for end of SDES item list */
+ if (sdes_type == RTCP_SDES_NULL || p == p_end)
+ break;
+
+ sdes_len = *p++;
+
+ /* Check for corrupted SDES packet */
+ if (p + sdes_len > p_end)
+ break;
+
+ /* Get SDES item */
+ if (b + sdes_len < b_end) {
+ pj_memcpy(b, p, sdes_len);
+ sdes_value.ptr = b;
+ sdes_value.slen = sdes_len;
+ b += sdes_len;
+ } else {
+ /* Insufficient SDES buffer */
+ PJ_LOG(5, (sess->name,
+ "Unsufficient buffer to save RTCP SDES type %d:%.*s",
+ sdes_type, sdes_len, p));
+ p += sdes_len;
+ continue;
+ }
+
+ switch (sdes_type) {
+ case RTCP_SDES_CNAME:
+ sdes->cname = sdes_value;
+ break;
+ case RTCP_SDES_NAME:
+ sdes->name = sdes_value;
+ break;
+ case RTCP_SDES_EMAIL:
+ sdes->email = sdes_value;
+ break;
+ case RTCP_SDES_PHONE:
+ sdes->phone = sdes_value;
+ break;
+ case RTCP_SDES_LOC:
+ sdes->loc = sdes_value;
+ break;
+ case RTCP_SDES_TOOL:
+ sdes->tool = sdes_value;
+ break;
+ case RTCP_SDES_NOTE:
+ sdes->note = sdes_value;
+ break;
+ default:
+ TRACE_((sess->name, "Received unknown RTCP SDES type %d:%.*s",
+ sdes_type, sdes_value.slen, sdes_value.ptr));
+ break;
+ }
+
+ p += sdes_len;
+ }
+}
+
+
+static void parse_rtcp_bye(pjmedia_rtcp_session *sess,
+ const void *pkt,
+ pj_size_t size)
+{
+ pj_str_t reason = {"-", 1};
+
+ /* Check and get BYE reason */
+ if (size > 8) {
+ reason.slen = *((pj_uint8_t*)pkt+8);
+ pj_memcpy(sess->stat.peer_sdes_buf_, ((pj_uint8_t*)pkt+9),
+ reason.slen);
+ reason.ptr = sess->stat.peer_sdes_buf_;
+ }
+
+ /* Just print RTCP BYE log */
+ PJ_LOG(5, (sess->name, "Received RTCP BYE, reason: %.*s",
+ reason.slen, reason.ptr));
+}
+
+
+PJ_DEF(void) pjmedia_rtcp_rx_rtcp( pjmedia_rtcp_session *sess,
+ const void *pkt,
+ pj_size_t size)
+{
+ pj_uint8_t *p, *p_end;
+
+ p = (pj_uint8_t*)pkt;
+ p_end = p + size;
+ while (p < p_end) {
+ pjmedia_rtcp_common *common = (pjmedia_rtcp_common*)p;
+ unsigned len;
+
+ len = (pj_ntohs((pj_uint16_t)common->length)+1) * 4;
+ switch(common->pt) {
+ case RTCP_SR:
+ case RTCP_RR:
+ case RTCP_XR:
+ parse_rtcp_report(sess, p, len);
+ break;
+ case RTCP_SDES:
+ parse_rtcp_sdes(sess, p, len);
+ break;
+ case RTCP_BYE:
+ parse_rtcp_bye(sess, p, len);
+ break;
+ default:
+ /* Ignore unknown RTCP */
+ TRACE_((sess->name, "Received unknown RTCP packet type=%d",
+ common->pt));
+ break;
+ }
+
+ p += len;
+ }
+}
+
+
+PJ_DEF(void) pjmedia_rtcp_build_rtcp(pjmedia_rtcp_session *sess,
+ void **ret_p_pkt, int *len)
+{
+ pj_uint32_t expected, expected_interval, received_interval, lost_interval;
+ pjmedia_rtcp_common *common;
+ pjmedia_rtcp_sr *sr;
+ pjmedia_rtcp_rr *rr;
+ pj_timestamp ts_now;
+ pjmedia_rtcp_ntp_rec ntp;
+
+ /* Get current NTP time. */
+ pj_get_timestamp(&ts_now);
+ pjmedia_rtcp_get_ntp_time(sess, &ntp);
+
+
+ /* See if we have transmitted RTP packets since last time we
+ * sent RTCP SR.
+ */
+ if (sess->stat.tx.pkt != pj_ntohl(sess->rtcp_sr_pkt.sr.sender_pcount)) {
+ pj_time_val ts_time;
+ pj_uint32_t rtp_ts;
+
+ /* So we should send RTCP SR */
+ *ret_p_pkt = (void*) &sess->rtcp_sr_pkt;
+ *len = sizeof(pjmedia_rtcp_sr_pkt);
+ common = &sess->rtcp_sr_pkt.common;
+ rr = &sess->rtcp_sr_pkt.rr;
+ sr = &sess->rtcp_sr_pkt.sr;
+
+ /* Update packet count */
+ sr->sender_pcount = pj_htonl(sess->stat.tx.pkt);
+
+ /* Update octets count */
+ sr->sender_bcount = pj_htonl(sess->stat.tx.bytes);
+
+ /* Fill in NTP timestamp in SR. */
+ sr->ntp_sec = pj_htonl(ntp.hi);
+ sr->ntp_frac = pj_htonl(ntp.lo);
+
+ /* Fill in RTP timestamp (corresponds to NTP timestamp) in SR. */
+ ts_time.sec = ntp.hi - sess->tv_base.sec - JAN_1970;
+ ts_time.msec = (long)(ntp.lo * 1000.0 / 0xFFFFFFFF);
+ rtp_ts = sess->rtp_ts_base +
+ (pj_uint32_t)(sess->clock_rate*ts_time.sec) +
+ (pj_uint32_t)(sess->clock_rate*ts_time.msec/1000);
+ sr->rtp_ts = pj_htonl(rtp_ts);
+
+ TRACE_((sess->name, "TX RTCP SR: ntp_ts=%p",
+ ((ntp.hi & 0xFFFF) << 16) + ((ntp.lo & 0xFFFF0000)
+ >> 16)));
+
+
+ } else {
+ /* We should send RTCP RR then */
+ *ret_p_pkt = (void*) &sess->rtcp_rr_pkt;
+ *len = sizeof(pjmedia_rtcp_rr_pkt);
+ common = &sess->rtcp_rr_pkt.common;
+ rr = &sess->rtcp_rr_pkt.rr;
+ sr = NULL;
+ }
+
+ /* SSRC and last_seq */
+ rr->ssrc = pj_htonl(sess->peer_ssrc);
+ rr->last_seq = (sess->seq_ctrl.cycles & 0xFFFF0000L);
+ /* Since this is an "+=" operation, make sure we update last_seq on
+ * both RR and SR.
+ */
+ sess->rtcp_sr_pkt.rr.last_seq += sess->seq_ctrl.max_seq;
+ sess->rtcp_rr_pkt.rr.last_seq += sess->seq_ctrl.max_seq;
+ rr->last_seq = pj_htonl(rr->last_seq);
+
+
+ /* Jitter */
+ rr->jitter = pj_htonl(sess->jitter >> 4);
+
+
+ /* Total lost. */
+ expected = pj_ntohl(rr->last_seq) - sess->seq_ctrl.base_seq;
+
+ /* This is bug: total lost already calculated on each incoming RTP!
+ if (expected >= sess->received)
+ sess->stat.rx.loss = expected - sess->received;
+ else
+ sess->stat.rx.loss = 0;
+ */
+
+ rr->total_lost_2 = (sess->stat.rx.loss >> 16) & 0xFF;
+ rr->total_lost_1 = (sess->stat.rx.loss >> 8) & 0xFF;
+ rr->total_lost_0 = (sess->stat.rx.loss & 0xFF);
+
+ /* Fraction lost calculation */
+ expected_interval = expected - sess->exp_prior;
+ sess->exp_prior = expected;
+
+ received_interval = sess->received - sess->rx_prior;
+ sess->rx_prior = sess->received;
+
+ if (expected_interval >= received_interval)
+ lost_interval = expected_interval - received_interval;
+ else
+ lost_interval = 0;
+
+ if (expected_interval==0 || lost_interval == 0) {
+ rr->fract_lost = 0;
+ } else {
+ rr->fract_lost = (lost_interval << 8) / expected_interval;
+ }
+
+ if (sess->rx_lsr_time.u64 == 0 || sess->rx_lsr == 0) {
+ rr->lsr = 0;
+ rr->dlsr = 0;
+ } else {
+ pj_timestamp ts;
+ pj_uint32_t lsr = sess->rx_lsr;
+ pj_uint64_t lsr_time = sess->rx_lsr_time.u64;
+ pj_uint32_t dlsr;
+
+ /* Convert LSR time to 1/65536 seconds resolution */
+ lsr_time = (lsr_time << 16) / sess->ts_freq.u64;
+
+ /* Fill in LSR.
+ LSR is the middle 32bit of the last SR NTP time received.
+ */
+ rr->lsr = pj_htonl(lsr);
+
+ /* Fill in DLSR.
+ DLSR is Delay since Last SR, in 1/65536 seconds.
+ */
+ ts.u64 = ts_now.u64;
+
+ /* Convert interval to 1/65536 seconds value */
+ ts.u64 = (ts.u64 << 16) / sess->ts_freq.u64;
+
+ /* Get DLSR */
+ dlsr = (pj_uint32_t)(ts.u64 - lsr_time);
+ rr->dlsr = pj_htonl(dlsr);
+
+ TRACE_((sess->name,"Tx RTCP RR: lsr=%p, lsr_time=%p, now=%p, dlsr=%p"
+ "(%ds:%03dms)",
+ lsr,
+ (pj_uint32_t)lsr_time,
+ (pj_uint32_t)ts.u64,
+ dlsr,
+ dlsr/65536,
+ (dlsr%65536)*1000/65536 ));
+ }
+
+ /* Update counter */
+ pj_gettimeofday(&sess->stat.rx.update);
+ sess->stat.rx.update_cnt++;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_rtcp_build_rtcp_sdes(
+ pjmedia_rtcp_session *session,
+ void *buf,
+ pj_size_t *length,
+ const pjmedia_rtcp_sdes *sdes)
+{
+ pjmedia_rtcp_common *hdr;
+ pj_uint8_t *p;
+ unsigned len;
+
+ PJ_ASSERT_RETURN(session && buf && length && sdes, PJ_EINVAL);
+
+ /* Verify SDES item length */
+ if (sdes->cname.slen > 255 || sdes->name.slen > 255 ||
+ sdes->email.slen > 255 || sdes->phone.slen > 255 ||
+ sdes->loc.slen > 255 || sdes->tool.slen > 255 ||
+ sdes->note.slen > 255)
+ {
+ return PJ_EINVAL;
+ }
+
+ /* Verify buffer length */
+ len = sizeof(*hdr);
+ if (sdes->cname.slen) len += sdes->cname.slen + 2;
+ if (sdes->name.slen) len += sdes->name.slen + 2;
+ if (sdes->email.slen) len += sdes->email.slen + 2;
+ if (sdes->phone.slen) len += sdes->phone.slen + 2;
+ if (sdes->loc.slen) len += sdes->loc.slen + 2;
+ if (sdes->tool.slen) len += sdes->tool.slen + 2;
+ if (sdes->note.slen) len += sdes->note.slen + 2;
+ len++; /* null termination */
+ len = ((len+3)/4) * 4;
+ if (len > *length)
+ return PJ_ETOOSMALL;
+
+ /* Build RTCP SDES header */
+ hdr = (pjmedia_rtcp_common*)buf;
+ pj_memcpy(hdr, &session->rtcp_sr_pkt.common, sizeof(*hdr));
+ hdr->pt = RTCP_SDES;
+ hdr->length = pj_htons((pj_uint16_t)(len/4 - 1));
+
+ /* Build RTCP SDES items */
+ p = (pj_uint8_t*)hdr + sizeof(*hdr);
+#define BUILD_SDES_ITEM(SDES_NAME, SDES_TYPE) \
+ if (sdes->SDES_NAME.slen) { \
+ *p++ = SDES_TYPE; \
+ *p++ = (pj_uint8_t)sdes->SDES_NAME.slen; \
+ pj_memcpy(p, sdes->SDES_NAME.ptr, sdes->SDES_NAME.slen); \
+ p += sdes->SDES_NAME.slen; \
+ }
+ BUILD_SDES_ITEM(cname, RTCP_SDES_CNAME);
+ BUILD_SDES_ITEM(name, RTCP_SDES_NAME);
+ BUILD_SDES_ITEM(email, RTCP_SDES_EMAIL);
+ BUILD_SDES_ITEM(phone, RTCP_SDES_PHONE);
+ BUILD_SDES_ITEM(loc, RTCP_SDES_LOC);
+ BUILD_SDES_ITEM(tool, RTCP_SDES_TOOL);
+ BUILD_SDES_ITEM(note, RTCP_SDES_NOTE);
+#undef BUILD_SDES_ITEM
+
+ /* Null termination */
+ *p++ = 0;
+
+ /* Pad to 32bit */
+ while ((p-(pj_uint8_t*)buf) % 4)
+ *p++ = 0;
+
+ /* Finally */
+ pj_assert((int)len == p-(pj_uint8_t*)buf);
+ *length = len;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_rtcp_build_rtcp_bye(pjmedia_rtcp_session *session,
+ void *buf,
+ pj_size_t *length,
+ const pj_str_t *reason)
+{
+ pjmedia_rtcp_common *hdr;
+ pj_uint8_t *p;
+ unsigned len;
+
+ PJ_ASSERT_RETURN(session && buf && length, PJ_EINVAL);
+
+ /* Verify BYE reason length */
+ if (reason && reason->slen > 255)
+ return PJ_EINVAL;
+
+ /* Verify buffer length */
+ len = sizeof(*hdr);
+ if (reason && reason->slen) len += reason->slen + 1;
+ len = ((len+3)/4) * 4;
+ if (len > *length)
+ return PJ_ETOOSMALL;
+
+ /* Build RTCP BYE header */
+ hdr = (pjmedia_rtcp_common*)buf;
+ pj_memcpy(hdr, &session->rtcp_sr_pkt.common, sizeof(*hdr));
+ hdr->pt = RTCP_BYE;
+ hdr->length = pj_htons((pj_uint16_t)(len/4 - 1));
+
+ /* Write RTCP BYE reason */
+ p = (pj_uint8_t*)hdr + sizeof(*hdr);
+ if (reason && reason->slen) {
+ *p++ = (pj_uint8_t)reason->slen;
+ pj_memcpy(p, reason->ptr, reason->slen);
+ p += reason->slen;
+ }
+
+ /* Pad to 32bit */
+ while ((p-(pj_uint8_t*)buf) % 4)
+ *p++ = 0;
+
+ pj_assert((int)len == p-(pj_uint8_t*)buf);
+ *length = len;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_rtcp_enable_xr( pjmedia_rtcp_session *sess,
+ pj_bool_t enable)
+{
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+
+ /* Check if request won't change anything */
+ if (!(enable ^ sess->xr_enabled))
+ return PJ_SUCCESS;
+
+ if (!enable) {
+ sess->xr_enabled = PJ_FALSE;
+ return PJ_SUCCESS;
+ }
+
+ pjmedia_rtcp_xr_init(&sess->xr_session, sess, 0, 1);
+ sess->xr_enabled = PJ_TRUE;
+
+ return PJ_SUCCESS;
+
+#else
+
+ PJ_UNUSED_ARG(sess);
+ PJ_UNUSED_ARG(enable);
+ return PJ_ENOTSUP;
+
+#endif
+}
diff --git a/pjmedia/src/pjmedia/rtcp_xr.c b/pjmedia/src/pjmedia/rtcp_xr.c
new file mode 100644
index 0000000..1dcd286
--- /dev/null
+++ b/pjmedia/src/pjmedia/rtcp_xr.c
@@ -0,0 +1,858 @@
+/* $Id: rtcp_xr.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/rtcp_xr.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/rtcp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/sock.h>
+#include <pj/string.h>
+
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+
+#define THIS_FILE "rtcp_xr.c"
+
+
+#if PJ_HAS_HIGH_RES_TIMER==0
+# error "High resolution timer needs to be enabled"
+#endif
+
+
+/* RTCP XR payload type */
+#define RTCP_XR 207
+
+/* RTCP XR block types */
+#define BT_LOSS_RLE 1
+#define BT_DUP_RLE 2
+#define BT_RCPT_TIMES 3
+#define BT_RR_TIME 4
+#define BT_DLRR 5
+#define BT_STATS 6
+#define BT_VOIP_METRICS 7
+
+
+#define DEFAULT_GMIN 16
+
+
+#if 0
+# define TRACE_(x) PJ_LOG(3,x)
+#else
+# define TRACE_(x) ;
+#endif
+
+void pjmedia_rtcp_xr_init( pjmedia_rtcp_xr_session *session,
+ struct pjmedia_rtcp_session *parent_session,
+ pj_uint8_t gmin,
+ unsigned frames_per_packet)
+{
+ pj_bzero(session, sizeof(pjmedia_rtcp_xr_session));
+
+ session->name = parent_session->name;
+ session->rtcp_session = parent_session;
+ pj_memcpy(&session->pkt.common, &session->rtcp_session->rtcp_sr_pkt.common,
+ sizeof(pjmedia_rtcp_common));
+ session->pkt.common.pt = RTCP_XR;
+
+ /* Init config */
+ session->stat.rx.voip_mtc.gmin = (pj_uint8_t)(gmin? gmin : DEFAULT_GMIN);
+ session->ptime = session->rtcp_session->pkt_size * 1000 /
+ session->rtcp_session->clock_rate;
+ session->frames_per_packet = frames_per_packet;
+
+ /* Init Statistics Summary fields which have non-zero default */
+ session->stat.rx.stat_sum.jitter.min = (unsigned) -1;
+ session->stat.rx.stat_sum.toh.min = (unsigned) -1;
+
+ /* Init VoIP Metrics fields which have non-zero default */
+ session->stat.rx.voip_mtc.signal_lvl = 127;
+ session->stat.rx.voip_mtc.noise_lvl = 127;
+ session->stat.rx.voip_mtc.rerl = 127;
+ session->stat.rx.voip_mtc.r_factor = 127;
+ session->stat.rx.voip_mtc.ext_r_factor = 127;
+ session->stat.rx.voip_mtc.mos_lq = 127;
+ session->stat.rx.voip_mtc.mos_cq = 127;
+
+ session->stat.tx.voip_mtc.signal_lvl = 127;
+ session->stat.tx.voip_mtc.noise_lvl = 127;
+ session->stat.tx.voip_mtc.rerl = 127;
+ session->stat.tx.voip_mtc.r_factor = 127;
+ session->stat.tx.voip_mtc.ext_r_factor = 127;
+ session->stat.tx.voip_mtc.mos_lq = 127;
+ session->stat.tx.voip_mtc.mos_cq = 127;
+}
+
+void pjmedia_rtcp_xr_fini(pjmedia_rtcp_xr_session *session)
+{
+ PJ_UNUSED_ARG(session);
+}
+
+PJ_DEF(void) pjmedia_rtcp_build_rtcp_xr( pjmedia_rtcp_xr_session *sess,
+ unsigned rpt_types,
+ void **rtcp_pkt, int *len)
+{
+ pj_uint16_t size = 0;
+
+ /* Receiver Reference Time Report Block */
+ /* Build this block if we have received packets since last build */
+ if ((rpt_types == 0 || (rpt_types & PJMEDIA_RTCP_XR_RR_TIME)) &&
+ sess->rx_last_rr != sess->rtcp_session->stat.rx.pkt)
+ {
+ pjmedia_rtcp_xr_rb_rr_time *r;
+ pjmedia_rtcp_ntp_rec ntp;
+
+ r = (pjmedia_rtcp_xr_rb_rr_time*) &sess->pkt.buf[size];
+ pj_bzero(r, sizeof(pjmedia_rtcp_xr_rb_rr_time));
+
+ /* Init block header */
+ r->header.bt = BT_RR_TIME;
+ r->header.specific = 0;
+ r->header.length = pj_htons(2);
+
+ /* Generate block contents */
+ pjmedia_rtcp_get_ntp_time(sess->rtcp_session, &ntp);
+ r->ntp_sec = pj_htonl(ntp.hi);
+ r->ntp_frac = pj_htonl(ntp.lo);
+
+ /* Finally */
+ size += sizeof(pjmedia_rtcp_xr_rb_rr_time);
+ sess->rx_last_rr = sess->rtcp_session->stat.rx.pkt;
+ }
+
+ /* DLRR Report Block */
+ /* Build this block if we have received RR NTP (rx_lrr) before */
+ if ((rpt_types == 0 || (rpt_types & PJMEDIA_RTCP_XR_DLRR)) &&
+ sess->rx_lrr)
+ {
+ pjmedia_rtcp_xr_rb_dlrr *r;
+ pjmedia_rtcp_xr_rb_dlrr_item *dlrr_item;
+ pj_timestamp ts;
+
+ r = (pjmedia_rtcp_xr_rb_dlrr*) &sess->pkt.buf[size];
+ pj_bzero(r, sizeof(pjmedia_rtcp_xr_rb_dlrr));
+
+ /* Init block header */
+ r->header.bt = BT_DLRR;
+ r->header.specific = 0;
+ r->header.length = pj_htons(sizeof(pjmedia_rtcp_xr_rb_dlrr)/4 - 1);
+
+ /* Generate block contents */
+ dlrr_item = &r->item;
+ dlrr_item->ssrc = pj_htonl(sess->rtcp_session->peer_ssrc);
+ dlrr_item->lrr = pj_htonl(sess->rx_lrr);
+
+ /* Calculate DLRR */
+ if (sess->rx_lrr != 0) {
+ pj_get_timestamp(&ts);
+ ts.u64 -= sess->rx_lrr_time.u64;
+
+ /* Convert DLRR time to 1/65536 seconds resolution */
+ ts.u64 = (ts.u64 << 16) / sess->rtcp_session->ts_freq.u64;
+ dlrr_item->dlrr = pj_htonl(ts.u32.lo);
+ } else {
+ dlrr_item->dlrr = 0;
+ }
+
+ /* Finally */
+ size += sizeof(pjmedia_rtcp_xr_rb_dlrr);
+ }
+
+ /* Statistics Summary Block */
+ /* Build this block if we have received packets since last build */
+ if ((rpt_types == 0 || (rpt_types & PJMEDIA_RTCP_XR_STATS)) &&
+ sess->stat.rx.stat_sum.count > 0)
+ {
+ pjmedia_rtcp_xr_rb_stats *r;
+ pj_uint8_t specific = 0;
+
+ r = (pjmedia_rtcp_xr_rb_stats*) &sess->pkt.buf[size];
+ pj_bzero(r, sizeof(pjmedia_rtcp_xr_rb_stats));
+
+ /* Init block header */
+ specific |= sess->stat.rx.stat_sum.l ? (1 << 7) : 0;
+ specific |= sess->stat.rx.stat_sum.d ? (1 << 6) : 0;
+ specific |= sess->stat.rx.stat_sum.j ? (1 << 5) : 0;
+ specific |= (sess->stat.rx.stat_sum.t & 3) << 3;
+ r->header.bt = BT_STATS;
+ r->header.specific = specific;
+ r->header.length = pj_htons(9);
+
+ /* Generate block contents */
+ r->ssrc = pj_htonl(sess->rtcp_session->peer_ssrc);
+ r->begin_seq = pj_htons((pj_uint16_t)
+ (sess->stat.rx.stat_sum.begin_seq & 0xFFFF));
+ r->end_seq = pj_htons((pj_uint16_t)
+ (sess->stat.rx.stat_sum.end_seq & 0xFFFF));
+ if (sess->stat.rx.stat_sum.l) {
+ r->lost = pj_htonl(sess->stat.rx.stat_sum.lost);
+ }
+ if (sess->stat.rx.stat_sum.d) {
+ r->dup = pj_htonl(sess->stat.rx.stat_sum.dup);
+ }
+ if (sess->stat.rx.stat_sum.j) {
+ r->jitter_min = pj_htonl(sess->stat.rx.stat_sum.jitter.min);
+ r->jitter_max = pj_htonl(sess->stat.rx.stat_sum.jitter.max);
+ r->jitter_mean =
+ pj_htonl((unsigned)sess->stat.rx.stat_sum.jitter.mean);
+ r->jitter_dev =
+ pj_htonl(pj_math_stat_get_stddev(&sess->stat.rx.stat_sum.jitter));
+ }
+ if (sess->stat.rx.stat_sum.t) {
+ r->toh_min = sess->stat.rx.stat_sum.toh.min;
+ r->toh_max = sess->stat.rx.stat_sum.toh.max;
+ r->toh_mean = (unsigned) sess->stat.rx.stat_sum.toh.mean;
+ r->toh_dev = pj_math_stat_get_stddev(&sess->stat.rx.stat_sum.toh);
+ }
+
+ /* Reset TX statistics summary each time built */
+ pj_bzero(&sess->stat.rx.stat_sum, sizeof(sess->stat.rx.stat_sum));
+ sess->stat.rx.stat_sum.jitter.min = (unsigned) -1;
+ sess->stat.rx.stat_sum.toh.min = (unsigned) -1;
+
+ /* Finally */
+ size += sizeof(pjmedia_rtcp_xr_rb_stats);
+ pj_gettimeofday(&sess->stat.rx.stat_sum.update);
+ }
+
+ /* Voip Metrics Block */
+ /* Build this block if we have received packets */
+ if ((rpt_types == 0 || (rpt_types & PJMEDIA_RTCP_XR_VOIP_METRICS)) &&
+ sess->rtcp_session->stat.rx.pkt)
+ {
+ pjmedia_rtcp_xr_rb_voip_mtc *r;
+ pj_uint32_t c11;
+ pj_uint32_t c13;
+ pj_uint32_t c14;
+ pj_uint32_t c22;
+ pj_uint32_t c23;
+ pj_uint32_t c31;
+ pj_uint32_t c32;
+ pj_uint32_t c33;
+ pj_uint32_t ctotal, m;
+ unsigned est_extra_delay;
+
+ r = (pjmedia_rtcp_xr_rb_voip_mtc*) &sess->pkt.buf[size];
+ pj_bzero(r, sizeof(pjmedia_rtcp_xr_rb_voip_mtc));
+
+ /* Init block header */
+ r->header.bt = BT_VOIP_METRICS;
+ r->header.specific = 0;
+ r->header.length = pj_htons(8);
+
+ /* Use temp vars for easiness. */
+ c11 = sess->voip_mtc_stat.c11;
+ c13 = sess->voip_mtc_stat.c13;
+ c14 = sess->voip_mtc_stat.c14;
+ c22 = sess->voip_mtc_stat.c22;
+ c23 = sess->voip_mtc_stat.c23;
+ c33 = sess->voip_mtc_stat.c33;
+ m = sess->ptime * sess->frames_per_packet;
+
+ /* Calculate additional transition counts. */
+ c31 = c13;
+ c32 = c23;
+ ctotal = c11 + c14 + c13 + c22 + c23 + c31 + c32 + c33;
+
+ if (ctotal) {
+ pj_uint32_t p32, p23;
+
+ //original version:
+ //p32 = c32 / (c31 + c32 + c33);
+ if (c31 + c32 + c33 == 0)
+ p32 = 0;
+ else
+ p32 = (c32 << 16) / (c31 + c32 + c33);
+
+ //original version:
+ //if ((c22 + c23) < 1) {
+ // p23 = 1;
+ //} else {
+ // p23 = 1 - c22 / (c22 + c23);
+ //}
+ if (c23 == 0) {
+ p23 = 0;
+ } else {
+ p23 = (c23 << 16) / (c22 + c23);
+ }
+
+ /* Calculate loss/discard densities, scaled of 0-256 */
+ if (c11 == 0)
+ sess->stat.rx.voip_mtc.gap_den = 0;
+ else
+ sess->stat.rx.voip_mtc.gap_den = (pj_uint8_t)
+ ((c14 << 8) / (c11 + c14));
+ if (p23 == 0)
+ sess->stat.rx.voip_mtc.burst_den = 0;
+ else
+ sess->stat.rx.voip_mtc.burst_den = (pj_uint8_t)
+ ((p23 << 8) / (p23 + p32));
+
+ /* Calculate (average) durations, in ms */
+ if (c13 == 0) {
+ c13 = 1;
+ ctotal += 1;
+ }
+ sess->stat.rx.voip_mtc.gap_dur = (pj_uint16_t)
+ ((c11+c14+c13) * m / c13);
+ sess->stat.rx.voip_mtc.burst_dur = (pj_uint16_t)
+ ((ctotal - (c11+c14+c13)) * m / c13);
+
+ /* Callculate loss/discard rates, scaled 0-256 */
+ sess->stat.rx.voip_mtc.loss_rate = (pj_uint8_t)
+ ((sess->voip_mtc_stat.loss_count << 8) / ctotal);
+ sess->stat.rx.voip_mtc.discard_rate = (pj_uint8_t)
+ ((sess->voip_mtc_stat.discard_count << 8) / ctotal);
+ } else {
+ /* No lost/discarded packet yet. */
+ sess->stat.rx.voip_mtc.gap_den = 0;
+ sess->stat.rx.voip_mtc.burst_den = 0;
+ sess->stat.rx.voip_mtc.gap_dur = 0;
+ sess->stat.rx.voip_mtc.burst_dur = 0;
+ sess->stat.rx.voip_mtc.loss_rate = 0;
+ sess->stat.rx.voip_mtc.discard_rate = 0;
+ }
+
+ /* Set round trip delay (in ms) to RTT calculated after receiving
+ * DLRR or DLSR.
+ */
+ if (sess->stat.rtt.last)
+ sess->stat.rx.voip_mtc.rnd_trip_delay = (pj_uint16_t)
+ (sess->stat.rtt.last / 1000);
+ else if (sess->rtcp_session->stat.rtt.last)
+ sess->stat.rx.voip_mtc.rnd_trip_delay = (pj_uint16_t)
+ (sess->rtcp_session->stat.rtt.last / 1000);
+
+ /* End system delay = RTT/2 + current jitter buffer size +
+ * EXTRA (estimated extra delay)
+ * EXTRA will cover additional delay introduced by other components of
+ * audio engine, e.g: sound device, codec, AEC, PLC, WSOLA.
+ * Since it is difficult to get the exact value of EXTRA, estimation
+ * is taken to be totally around 30ms + sound device latency.
+ */
+ est_extra_delay = 30;
+
+#if PJMEDIA_SOUND_IMPLEMENTATION!=PJMEDIA_SOUND_NULL_SOUND
+ est_extra_delay += PJMEDIA_SND_DEFAULT_REC_LATENCY +
+ PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+#endif
+
+ sess->stat.rx.voip_mtc.end_sys_delay = (pj_uint16_t)
+ (sess->stat.rx.voip_mtc.rnd_trip_delay / 2 +
+ sess->stat.rx.voip_mtc.jb_nom +
+ est_extra_delay);
+
+ /* Generate block contents */
+ r->ssrc = pj_htonl(sess->rtcp_session->peer_ssrc);
+ r->loss_rate = sess->stat.rx.voip_mtc.loss_rate;
+ r->discard_rate = sess->stat.rx.voip_mtc.discard_rate;
+ r->burst_den = sess->stat.rx.voip_mtc.burst_den;
+ r->gap_den = sess->stat.rx.voip_mtc.gap_den;
+ r->burst_dur = pj_htons(sess->stat.rx.voip_mtc.burst_dur);
+ r->gap_dur = pj_htons(sess->stat.rx.voip_mtc.gap_dur);
+ r->rnd_trip_delay = pj_htons(sess->stat.rx.voip_mtc.rnd_trip_delay);
+ r->end_sys_delay = pj_htons(sess->stat.rx.voip_mtc.end_sys_delay);
+ /* signal & noise level encoded in two's complement form */
+ r->signal_lvl = (pj_uint8_t)
+ ((sess->stat.rx.voip_mtc.signal_lvl >= 0)?
+ sess->stat.rx.voip_mtc.signal_lvl :
+ (sess->stat.rx.voip_mtc.signal_lvl + 256));
+ r->noise_lvl = (pj_uint8_t)
+ ((sess->stat.rx.voip_mtc.noise_lvl >= 0)?
+ sess->stat.rx.voip_mtc.noise_lvl :
+ (sess->stat.rx.voip_mtc.noise_lvl + 256));
+ r->rerl = sess->stat.rx.voip_mtc.rerl;
+ r->gmin = sess->stat.rx.voip_mtc.gmin;
+ r->r_factor = sess->stat.rx.voip_mtc.r_factor;
+ r->ext_r_factor = sess->stat.rx.voip_mtc.ext_r_factor;
+ r->mos_lq = sess->stat.rx.voip_mtc.mos_lq;
+ r->mos_cq = sess->stat.rx.voip_mtc.mos_cq;
+ r->rx_config = sess->stat.rx.voip_mtc.rx_config;
+ r->jb_nom = pj_htons(sess->stat.rx.voip_mtc.jb_nom);
+ r->jb_max = pj_htons(sess->stat.rx.voip_mtc.jb_max);
+ r->jb_abs_max = pj_htons(sess->stat.rx.voip_mtc.jb_abs_max);
+
+ /* Finally */
+ size += sizeof(pjmedia_rtcp_xr_rb_voip_mtc);
+ pj_gettimeofday(&sess->stat.rx.voip_mtc.update);
+ }
+
+ /* Add RTCP XR header size */
+ size += sizeof(sess->pkt.common);
+
+ /* Set RTCP XR header 'length' to packet size in 32-bit unit minus one */
+ sess->pkt.common.length = pj_htons((pj_uint16_t)(size/4 - 1));
+
+ /* Set the return values */
+ *rtcp_pkt = (void*) &sess->pkt;
+ *len = size;
+}
+
+
+void pjmedia_rtcp_xr_rx_rtcp_xr( pjmedia_rtcp_xr_session *sess,
+ const void *pkt,
+ pj_size_t size)
+{
+ const pjmedia_rtcp_xr_pkt *rtcp_xr = (pjmedia_rtcp_xr_pkt*) pkt;
+ const pjmedia_rtcp_xr_rb_rr_time *rb_rr_time = NULL;
+ const pjmedia_rtcp_xr_rb_dlrr *rb_dlrr = NULL;
+ const pjmedia_rtcp_xr_rb_stats *rb_stats = NULL;
+ const pjmedia_rtcp_xr_rb_voip_mtc *rb_voip_mtc = NULL;
+ const pjmedia_rtcp_xr_rb_header *rb_hdr = (pjmedia_rtcp_xr_rb_header*)
+ rtcp_xr->buf;
+ unsigned pkt_len, rb_len;
+
+ if (rtcp_xr->common.pt != RTCP_XR)
+ return;
+
+ pkt_len = pj_ntohs((pj_uint16_t)rtcp_xr->common.length);
+
+ if ((pkt_len + 1) > (size / 4))
+ return;
+
+ /* Parse report rpt_types */
+ while ((pj_int32_t*)rb_hdr < (pj_int32_t*)pkt + pkt_len)
+ {
+ rb_len = pj_ntohs((pj_uint16_t)rb_hdr->length);
+
+ /* Just skip any block with length == 0 (no report content) */
+ if (rb_len) {
+ switch (rb_hdr->bt) {
+ case BT_RR_TIME:
+ rb_rr_time = (pjmedia_rtcp_xr_rb_rr_time*) rb_hdr;
+ break;
+ case BT_DLRR:
+ rb_dlrr = (pjmedia_rtcp_xr_rb_dlrr*) rb_hdr;
+ break;
+ case BT_STATS:
+ rb_stats = (pjmedia_rtcp_xr_rb_stats*) rb_hdr;
+ break;
+ case BT_VOIP_METRICS:
+ rb_voip_mtc = (pjmedia_rtcp_xr_rb_voip_mtc*) rb_hdr;
+ break;
+ default:
+ break;
+ }
+ }
+ rb_hdr = (pjmedia_rtcp_xr_rb_header*)
+ ((pj_int32_t*)rb_hdr + rb_len + 1);
+ }
+
+ /* Receiving RR Time */
+ if (rb_rr_time) {
+ /* Save LRR from NTP timestamp of the RR time block report */
+ sess->rx_lrr = ((pj_ntohl(rb_rr_time->ntp_sec) & 0x0000FFFF) << 16) |
+ ((pj_ntohl(rb_rr_time->ntp_frac) >> 16) & 0xFFFF);
+
+ /* Calculate RR arrival time for DLRR */
+ pj_get_timestamp(&sess->rx_lrr_time);
+
+ TRACE_((sess->name, "Rx RTCP SR: ntp_ts=%p", sess->rx_lrr,
+ (pj_uint32_t)(sess->rx_lrr_time.u64*65536/
+ sess->rtcp_session->ts_freq.u64)));
+ }
+
+ /* Receiving DLRR */
+ if (rb_dlrr) {
+ pj_uint32_t lrr, now, dlrr;
+ pj_uint64_t eedelay;
+ pjmedia_rtcp_ntp_rec ntp;
+
+ /* LRR is the middle 32bit of NTP. It has 1/65536 second
+ * resolution
+ */
+ lrr = pj_ntohl(rb_dlrr->item.lrr);
+
+ /* DLRR is delay since LRR, also in 1/65536 resolution */
+ dlrr = pj_ntohl(rb_dlrr->item.dlrr);
+
+ /* Get current time, and convert to 1/65536 resolution */
+ pjmedia_rtcp_get_ntp_time(sess->rtcp_session, &ntp);
+ now = ((ntp.hi & 0xFFFF) << 16) + (ntp.lo >> 16);
+
+ /* End-to-end delay is (now-lrr-dlrr) */
+ eedelay = now - lrr - dlrr;
+
+ /* Convert end to end delay to usec (keeping the calculation in
+ * 64bit space)::
+ * sess->ee_delay = (eedelay * 1000) / 65536;
+ */
+ if (eedelay < 4294) {
+ eedelay = (eedelay * 1000000) >> 16;
+ } else {
+ eedelay = (eedelay * 1000) >> 16;
+ eedelay *= 1000;
+ }
+
+ TRACE_((sess->name, "Rx RTCP XR DLRR: lrr=%p, dlrr=%p (%d:%03dms), "
+ "now=%p, rtt=%p",
+ lrr, dlrr, dlrr/65536, (dlrr%65536)*1000/65536,
+ now, (pj_uint32_t)eedelay));
+
+ /* Only save calculation if "now" is greater than lrr, or
+ * otherwise rtt will be invalid
+ */
+ if (now-dlrr >= lrr) {
+ unsigned rtt = (pj_uint32_t)eedelay;
+
+ /* Check that eedelay value really makes sense.
+ * We allow up to 30 seconds RTT!
+ */
+ if (eedelay <= 30 * 1000 * 1000UL) {
+ /* "Normalize" rtt value that is exceptionally high.
+ * For such values, "normalize" the rtt to be three times
+ * the average value.
+ */
+ if (rtt>((unsigned)sess->stat.rtt.mean*3) && sess->stat.rtt.n!=0)
+ {
+ unsigned orig_rtt = rtt;
+ rtt = (unsigned)sess->stat.rtt.mean*3;
+ PJ_LOG(5,(sess->name,
+ "RTT value %d usec is normalized to %d usec",
+ orig_rtt, rtt));
+ }
+
+ TRACE_((sess->name, "RTCP RTT is set to %d usec", rtt));
+ pj_math_stat_update(&sess->stat.rtt, rtt);
+ }
+ } else {
+ PJ_LOG(5, (sess->name, "Internal RTCP NTP clock skew detected: "
+ "lrr=%p, now=%p, dlrr=%p (%d:%03dms), "
+ "diff=%d",
+ lrr, now, dlrr, dlrr/65536,
+ (dlrr%65536)*1000/65536,
+ dlrr-(now-lrr)));
+ }
+ }
+
+ /* Receiving Statistics Summary */
+ if (rb_stats) {
+ pj_uint8_t flags = rb_stats->header.specific;
+
+ pj_bzero(&sess->stat.tx.stat_sum, sizeof(sess->stat.tx.stat_sum));
+
+ /* Range of packets sequence reported in this blocks */
+ sess->stat.tx.stat_sum.begin_seq = pj_ntohs(rb_stats->begin_seq);
+ sess->stat.tx.stat_sum.end_seq = pj_ntohs(rb_stats->end_seq);
+
+ /* Get flags of valid fields */
+ sess->stat.tx.stat_sum.l = (flags & (1 << 7)) != 0;
+ sess->stat.tx.stat_sum.d = (flags & (1 << 6)) != 0;
+ sess->stat.tx.stat_sum.j = (flags & (1 << 5)) != 0;
+ sess->stat.tx.stat_sum.t = (flags & (3 << 3)) != 0;
+
+ /* Fetch the reports info */
+ if (sess->stat.tx.stat_sum.l) {
+ sess->stat.tx.stat_sum.lost = pj_ntohl(rb_stats->lost);
+ }
+
+ if (sess->stat.tx.stat_sum.d) {
+ sess->stat.tx.stat_sum.dup = pj_ntohl(rb_stats->dup);
+ }
+
+ if (sess->stat.tx.stat_sum.j) {
+ sess->stat.tx.stat_sum.jitter.min = pj_ntohl(rb_stats->jitter_min);
+ sess->stat.tx.stat_sum.jitter.max = pj_ntohl(rb_stats->jitter_max);
+ sess->stat.tx.stat_sum.jitter.mean= pj_ntohl(rb_stats->jitter_mean);
+ pj_math_stat_set_stddev(&sess->stat.tx.stat_sum.jitter,
+ pj_ntohl(rb_stats->jitter_dev));
+ }
+
+ if (sess->stat.tx.stat_sum.t) {
+ sess->stat.tx.stat_sum.toh.min = rb_stats->toh_min;
+ sess->stat.tx.stat_sum.toh.max = rb_stats->toh_max;
+ sess->stat.tx.stat_sum.toh.mean= rb_stats->toh_mean;
+ pj_math_stat_set_stddev(&sess->stat.tx.stat_sum.toh,
+ pj_ntohl(rb_stats->toh_dev));
+ }
+
+ pj_gettimeofday(&sess->stat.tx.stat_sum.update);
+ }
+
+ /* Receiving VoIP Metrics */
+ if (rb_voip_mtc) {
+ sess->stat.tx.voip_mtc.loss_rate = rb_voip_mtc->loss_rate;
+ sess->stat.tx.voip_mtc.discard_rate = rb_voip_mtc->discard_rate;
+ sess->stat.tx.voip_mtc.burst_den = rb_voip_mtc->burst_den;
+ sess->stat.tx.voip_mtc.gap_den = rb_voip_mtc->gap_den;
+ sess->stat.tx.voip_mtc.burst_dur = pj_ntohs(rb_voip_mtc->burst_dur);
+ sess->stat.tx.voip_mtc.gap_dur = pj_ntohs(rb_voip_mtc->gap_dur);
+ sess->stat.tx.voip_mtc.rnd_trip_delay =
+ pj_ntohs(rb_voip_mtc->rnd_trip_delay);
+ sess->stat.tx.voip_mtc.end_sys_delay =
+ pj_ntohs(rb_voip_mtc->end_sys_delay);
+ /* signal & noise level encoded in two's complement form */
+ sess->stat.tx.voip_mtc.signal_lvl = (pj_int8_t)
+ ((rb_voip_mtc->signal_lvl > 127)?
+ ((int)rb_voip_mtc->signal_lvl - 256) :
+ rb_voip_mtc->signal_lvl);
+ sess->stat.tx.voip_mtc.noise_lvl = (pj_int8_t)
+ ((rb_voip_mtc->noise_lvl > 127)?
+ ((int)rb_voip_mtc->noise_lvl - 256) :
+ rb_voip_mtc->noise_lvl);
+ sess->stat.tx.voip_mtc.rerl = rb_voip_mtc->rerl;
+ sess->stat.tx.voip_mtc.gmin = rb_voip_mtc->gmin;
+ sess->stat.tx.voip_mtc.r_factor = rb_voip_mtc->r_factor;
+ sess->stat.tx.voip_mtc.ext_r_factor = rb_voip_mtc->ext_r_factor;
+ sess->stat.tx.voip_mtc.mos_lq = rb_voip_mtc->mos_lq;
+ sess->stat.tx.voip_mtc.mos_cq = rb_voip_mtc->mos_cq;
+ sess->stat.tx.voip_mtc.rx_config = rb_voip_mtc->rx_config;
+ sess->stat.tx.voip_mtc.jb_nom = pj_ntohs(rb_voip_mtc->jb_nom);
+ sess->stat.tx.voip_mtc.jb_max = pj_ntohs(rb_voip_mtc->jb_max);
+ sess->stat.tx.voip_mtc.jb_abs_max = pj_ntohs(rb_voip_mtc->jb_abs_max);
+
+ pj_gettimeofday(&sess->stat.tx.voip_mtc.update);
+ }
+}
+
+/* Place seq into a 32-bit sequence number space based upon a
+ * heuristic for its most likely location.
+ */
+static pj_uint32_t extend_seq(pjmedia_rtcp_xr_session *sess,
+ const pj_uint16_t seq)
+{
+
+ pj_uint32_t extended_seq, seq_a, seq_b, diff_a, diff_b;
+ if(sess->uninitialized_src_ref_seq) {
+ /* This is the first sequence number received. Place
+ * it in the middle of the extended sequence number
+ * space.
+ */
+ sess->src_ref_seq = seq | 0x80000000u;
+ sess->uninitialized_src_ref_seq = PJ_FALSE;
+ extended_seq = sess->src_ref_seq;
+ } else {
+ /* Prior sequence numbers have been received.
+ * Propose two candidates for the extended sequence
+ * number: seq_a is without wraparound, seq_b with
+ * wraparound.
+ */
+ seq_a = seq | (sess->src_ref_seq & 0xFFFF0000u);
+ if(sess->src_ref_seq < seq_a) {
+ seq_b = seq_a - 0x00010000u;
+ diff_a = seq_a - sess->src_ref_seq;
+ diff_b = sess->src_ref_seq - seq_b;
+ } else {
+ seq_b = seq_a + 0x00010000u;
+ diff_a = sess->src_ref_seq - seq_a;
+ diff_b = seq_b - sess->src_ref_seq;
+ }
+
+ /* Choose the closer candidate. If they are equally
+ * close, the choice is somewhat arbitrary: we choose
+ * the candidate for which no rollover is necessary.
+ */
+ if(diff_a < diff_b) {
+ extended_seq = seq_a;
+ } else {
+ extended_seq = seq_b;
+ }
+
+ /* Set the reference sequence number to be this most
+ * recently-received sequence number.
+ */
+ sess->src_ref_seq = extended_seq;
+ }
+
+ /* Return our best guess for a 32-bit sequence number that
+ * corresponds to the 16-bit number we were given.
+ */
+ return extended_seq;
+}
+
+void pjmedia_rtcp_xr_rx_rtp( pjmedia_rtcp_xr_session *sess,
+ unsigned seq,
+ int lost,
+ int dup,
+ int discarded,
+ int jitter,
+ int toh, pj_bool_t toh_ipv4)
+{
+ pj_uint32_t ext_seq;
+
+ /* Get 32 bit version of sequence */
+ ext_seq = extend_seq(sess, (pj_uint16_t)seq);
+
+ /* Update statistics summary */
+ sess->stat.rx.stat_sum.count++;
+
+ if (sess->stat.rx.stat_sum.begin_seq == 0 ||
+ sess->stat.rx.stat_sum.begin_seq > ext_seq)
+ {
+ sess->stat.rx.stat_sum.begin_seq = ext_seq;
+ }
+
+ if (sess->stat.rx.stat_sum.end_seq == 0 ||
+ sess->stat.rx.stat_sum.end_seq < ext_seq)
+ {
+ sess->stat.rx.stat_sum.end_seq = ext_seq;
+ }
+
+ if (lost >= 0) {
+ sess->stat.rx.stat_sum.l = PJ_TRUE;
+ if (lost > 0)
+ sess->stat.rx.stat_sum.lost++;
+ }
+
+ if (dup >= 0) {
+ sess->stat.rx.stat_sum.d = PJ_TRUE;
+ if (dup > 0)
+ sess->stat.rx.stat_sum.dup++;
+ }
+
+ if (jitter >= 0) {
+ sess->stat.rx.stat_sum.j = PJ_TRUE;
+ pj_math_stat_update(&sess->stat.rx.stat_sum.jitter, jitter);
+ }
+
+ if (toh >= 0) {
+ sess->stat.rx.stat_sum.t = toh_ipv4? 1 : 2;
+ pj_math_stat_update(&sess->stat.rx.stat_sum.toh, toh);
+ }
+
+ /* Update burst metrics.
+ * There are two terms introduced in the RFC 3611: gap & burst.
+ * Gap represents good stream condition, lost+discard rate <= 1/Gmin.
+ * Burst represents the opposite, lost+discard rate > 1/Gmin.
+ */
+ if (lost >= 0 && discarded >= 0) {
+ if(lost > 0) {
+ sess->voip_mtc_stat.loss_count++;
+ }
+ if(discarded > 0) {
+ sess->voip_mtc_stat.discard_count++;
+ }
+ if(!lost && !discarded) {
+ /* Number of good packets since last lost/discarded */
+ sess->voip_mtc_stat.pkt++;
+ }
+ else {
+ if(sess->voip_mtc_stat.pkt >= sess->stat.rx.voip_mtc.gmin) {
+ /* Gap condition */
+ if(sess->voip_mtc_stat.lost == 1) {
+ /* Gap -> Gap */
+ sess->voip_mtc_stat.c14++;
+ }
+ else {
+ /* Burst -> Gap */
+ sess->voip_mtc_stat.c13++;
+ }
+ sess->voip_mtc_stat.lost = 1;
+ sess->voip_mtc_stat.c11 += sess->voip_mtc_stat.pkt;
+ }
+ else {
+ /* Burst condition */
+ sess->voip_mtc_stat.lost++;
+ if(sess->voip_mtc_stat.pkt == 0) {
+ /* Consecutive losts */
+ sess->voip_mtc_stat.c33++;
+ }
+ else {
+ /* Any good packets, but still bursting */
+ sess->voip_mtc_stat.c23++;
+ sess->voip_mtc_stat.c22 += (sess->voip_mtc_stat.pkt - 1);
+ }
+ }
+
+ sess->voip_mtc_stat.pkt = 0;
+ }
+ }
+}
+
+void pjmedia_rtcp_xr_tx_rtp( pjmedia_rtcp_xr_session *session,
+ unsigned ptsize )
+{
+ PJ_UNUSED_ARG(session);
+ PJ_UNUSED_ARG(ptsize);
+}
+
+PJ_DEF(pj_status_t) pjmedia_rtcp_xr_update_info(
+ pjmedia_rtcp_xr_session *sess,
+ unsigned info,
+ pj_int32_t val)
+{
+ int v = val;
+
+ switch(info) {
+ case PJMEDIA_RTCP_XR_INFO_SIGNAL_LVL:
+ sess->stat.rx.voip_mtc.signal_lvl = (pj_int8_t) v;
+ break;
+
+ case PJMEDIA_RTCP_XR_INFO_NOISE_LVL:
+ sess->stat.rx.voip_mtc.noise_lvl = (pj_int8_t) v;
+ break;
+
+ case PJMEDIA_RTCP_XR_INFO_RERL:
+ sess->stat.rx.voip_mtc.rerl = (pj_uint8_t) v;
+ break;
+
+ case PJMEDIA_RTCP_XR_INFO_R_FACTOR:
+ sess->stat.rx.voip_mtc.ext_r_factor = (pj_uint8_t) v;
+ break;
+
+ case PJMEDIA_RTCP_XR_INFO_MOS_LQ:
+ sess->stat.rx.voip_mtc.mos_lq = (pj_uint8_t) v;
+ break;
+
+ case PJMEDIA_RTCP_XR_INFO_MOS_CQ:
+ sess->stat.rx.voip_mtc.mos_cq = (pj_uint8_t) v;
+ break;
+
+ case PJMEDIA_RTCP_XR_INFO_CONF_PLC:
+ if (v >= 0 && v <= 3) {
+ sess->stat.rx.voip_mtc.rx_config &= 0x3F;
+ sess->stat.rx.voip_mtc.rx_config |= (pj_uint8_t) (v << 6);
+ }
+ break;
+
+ case PJMEDIA_RTCP_XR_INFO_CONF_JBA:
+ if (v >= 0 && v <= 3) {
+ sess->stat.rx.voip_mtc.rx_config &= 0xCF;
+ sess->stat.rx.voip_mtc.rx_config |= (pj_uint8_t) (v << 4);
+ }
+ break;
+
+ case PJMEDIA_RTCP_XR_INFO_CONF_JBR:
+ if (v >= 0 && v <= 15) {
+ sess->stat.rx.voip_mtc.rx_config &= 0xF0;
+ sess->stat.rx.voip_mtc.rx_config |= (pj_uint8_t) v;
+ }
+ break;
+
+ case PJMEDIA_RTCP_XR_INFO_JB_NOM:
+ sess->stat.rx.voip_mtc.jb_nom = (pj_uint16_t) v;
+ break;
+
+ case PJMEDIA_RTCP_XR_INFO_JB_MAX:
+ sess->stat.rx.voip_mtc.jb_max = (pj_uint16_t) v;
+ break;
+
+ case PJMEDIA_RTCP_XR_INFO_JB_ABS_MAX:
+ sess->stat.rx.voip_mtc.jb_abs_max = (pj_uint16_t) v;
+ break;
+
+ default:
+ return PJ_EINVAL;
+ }
+
+ return PJ_SUCCESS;
+}
+
+#endif
diff --git a/pjmedia/src/pjmedia/rtp.c b/pjmedia/src/pjmedia/rtp.c
new file mode 100644
index 0000000..7fe6f89
--- /dev/null
+++ b/pjmedia/src/pjmedia/rtp.c
@@ -0,0 +1,366 @@
+/* $Id: rtp.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/rtp.h>
+#include <pjmedia/errno.h>
+#include <pj/log.h>
+#include <pj/sock.h> /* pj_htonx, pj_htonx */
+#include <pj/assert.h>
+#include <pj/rand.h>
+#include <pj/string.h>
+
+
+#define THIS_FILE "rtp.c"
+
+#define RTP_VERSION 2
+
+#define RTP_SEQ_MOD (1 << 16)
+#define MAX_DROPOUT ((pj_int16_t)3000)
+#define MAX_MISORDER ((pj_int16_t)100)
+#define MIN_SEQUENTIAL ((pj_int16_t)2)
+
+static void pjmedia_rtp_seq_restart(pjmedia_rtp_seq_session *seq_ctrl,
+ pj_uint16_t seq);
+
+
+PJ_DEF(pj_status_t) pjmedia_rtp_session_init( pjmedia_rtp_session *ses,
+ int default_pt,
+ pj_uint32_t sender_ssrc )
+{
+ PJ_LOG(5, (THIS_FILE,
+ "pjmedia_rtp_session_init: ses=%p, default_pt=%d, ssrc=0x%x",
+ ses, default_pt, sender_ssrc));
+
+ /* Check RTP header packing. */
+ if (sizeof(struct pjmedia_rtp_hdr) != 12) {
+ pj_assert(!"Wrong RTP header packing!");
+ return PJMEDIA_RTP_EINPACK;
+ }
+
+ /* If sender_ssrc is not specified, create from random value. */
+ if (sender_ssrc == 0 || sender_ssrc == (pj_uint32_t)-1) {
+ sender_ssrc = pj_htonl(pj_rand());
+ } else {
+ sender_ssrc = pj_htonl(sender_ssrc);
+ }
+
+ /* Initialize session. */
+ pj_bzero(ses, sizeof(*ses));
+
+ /* Initial sequence number SHOULD be random, according to RFC 3550. */
+ /* According to RFC 3711, it should be random within 2^15 bit */
+ ses->out_extseq = pj_rand() & 0x7FFF;
+ ses->peer_ssrc = 0;
+
+ /* Build default header for outgoing RTP packet. */
+ ses->out_hdr.v = RTP_VERSION;
+ ses->out_hdr.p = 0;
+ ses->out_hdr.x = 0;
+ ses->out_hdr.cc = 0;
+ ses->out_hdr.m = 0;
+ ses->out_hdr.pt = (pj_uint8_t) default_pt;
+ ses->out_hdr.seq = (pj_uint16_t) pj_htons( (pj_uint16_t)ses->out_extseq );
+ ses->out_hdr.ts = 0;
+ ses->out_hdr.ssrc = sender_ssrc;
+
+ /* Keep some arguments as session defaults. */
+ ses->out_pt = (pj_uint16_t) default_pt;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_rtp_session_init2(
+ pjmedia_rtp_session *ses,
+ pjmedia_rtp_session_setting settings)
+{
+ pj_status_t status;
+ int pt = 0;
+ pj_uint32_t sender_ssrc = 0;
+
+ if (settings.flags & 1)
+ pt = settings.default_pt;
+ if (settings.flags & 2)
+ sender_ssrc = settings.sender_ssrc;
+
+ status = pjmedia_rtp_session_init(ses, pt, sender_ssrc);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (settings.flags & 4) {
+ ses->out_extseq = settings.seq;
+ ses->out_hdr.seq = pj_htons((pj_uint16_t)ses->out_extseq);
+ }
+ if (settings.flags & 8)
+ ses->out_hdr.ts = pj_htonl(settings.ts);
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_rtp_encode_rtp( pjmedia_rtp_session *ses,
+ int pt, int m,
+ int payload_len, int ts_len,
+ const void **rtphdr, int *hdrlen )
+{
+ PJ_UNUSED_ARG(payload_len);
+
+ /* Update timestamp */
+ ses->out_hdr.ts = pj_htonl(pj_ntohl(ses->out_hdr.ts)+ts_len);
+
+ /* If payload_len is zero, bail out.
+ * This is a clock frame; we're not really transmitting anything.
+ */
+ if (payload_len == 0)
+ return PJ_SUCCESS;
+
+ /* Update session. */
+ ses->out_extseq++;
+
+ /* Create outgoing header. */
+ ses->out_hdr.pt = (pj_uint8_t) ((pt == -1) ? ses->out_pt : pt);
+ ses->out_hdr.m = (pj_uint16_t) m;
+ ses->out_hdr.seq = pj_htons( (pj_uint16_t) ses->out_extseq);
+
+ /* Return values */
+ *rtphdr = &ses->out_hdr;
+ *hdrlen = sizeof(pjmedia_rtp_hdr);
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_rtp_decode_rtp( pjmedia_rtp_session *ses,
+ const void *pkt, int pkt_len,
+ const pjmedia_rtp_hdr **hdr,
+ const void **payload,
+ unsigned *payloadlen)
+{
+ int offset;
+
+ PJ_UNUSED_ARG(ses);
+
+ /* Assume RTP header at the start of packet. We'll verify this later. */
+ *hdr = (pjmedia_rtp_hdr*)pkt;
+
+ /* Check RTP header sanity. */
+ if ((*hdr)->v != RTP_VERSION) {
+ return PJMEDIA_RTP_EINVER;
+ }
+
+ /* Payload is located right after header plus CSRC */
+ offset = sizeof(pjmedia_rtp_hdr) + ((*hdr)->cc * sizeof(pj_uint32_t));
+
+ /* Adjust offset if RTP extension is used. */
+ if ((*hdr)->x) {
+ pjmedia_rtp_ext_hdr *ext = (pjmedia_rtp_ext_hdr*)
+ (((pj_uint8_t*)pkt) + offset);
+ offset += ((pj_ntohs(ext->length)+1) * sizeof(pj_uint32_t));
+ }
+
+ /* Check that offset is less than packet size */
+ if (offset > pkt_len)
+ return PJMEDIA_RTP_EINLEN;
+
+ /* Find and set payload. */
+ *payload = ((pj_uint8_t*)pkt) + offset;
+ *payloadlen = pkt_len - offset;
+
+ /* Remove payload padding if any */
+ if ((*hdr)->p && *payloadlen > 0) {
+ pj_uint8_t pad_len;
+
+ pad_len = ((pj_uint8_t*)(*payload))[*payloadlen - 1];
+ if (pad_len <= *payloadlen)
+ *payloadlen -= pad_len;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(void) pjmedia_rtp_session_update( pjmedia_rtp_session *ses,
+ const pjmedia_rtp_hdr *hdr,
+ pjmedia_rtp_status *p_seq_st)
+{
+ pjmedia_rtp_session_update2(ses, hdr, p_seq_st, PJ_TRUE);
+}
+
+PJ_DEF(void) pjmedia_rtp_session_update2( pjmedia_rtp_session *ses,
+ const pjmedia_rtp_hdr *hdr,
+ pjmedia_rtp_status *p_seq_st,
+ pj_bool_t check_pt)
+{
+ pjmedia_rtp_status seq_st;
+
+ /* for now check_pt MUST be either PJ_TRUE or PJ_FALSE.
+ * In the future we might change check_pt from boolean to
+ * unsigned integer to accommodate more flags.
+ */
+ pj_assert(check_pt==PJ_TRUE || check_pt==PJ_FALSE);
+
+ /* Init status */
+ seq_st.status.value = 0;
+ seq_st.diff = 0;
+
+ /* Check SSRC. */
+ if (ses->peer_ssrc == 0) ses->peer_ssrc = pj_ntohl(hdr->ssrc);
+
+ if (pj_ntohl(hdr->ssrc) != ses->peer_ssrc) {
+ seq_st.status.flag.badssrc = 1;
+ ses->peer_ssrc = pj_ntohl(hdr->ssrc);
+ }
+
+ /* Check payload type. */
+ if (check_pt && hdr->pt != ses->out_pt) {
+ if (p_seq_st) {
+ p_seq_st->status.value = seq_st.status.value;
+ p_seq_st->status.flag.bad = 1;
+ p_seq_st->status.flag.badpt = 1;
+ }
+ return;
+ }
+
+ /* Initialize sequence number on first packet received. */
+ if (ses->received == 0)
+ pjmedia_rtp_seq_init( &ses->seq_ctrl, pj_ntohs(hdr->seq) );
+
+ /* Check sequence number to see if remote session has been restarted. */
+ pjmedia_rtp_seq_update( &ses->seq_ctrl, pj_ntohs(hdr->seq), &seq_st);
+ if (seq_st.status.flag.restart) {
+ ++ses->received;
+
+ } else if (!seq_st.status.flag.bad) {
+ ++ses->received;
+ }
+
+ if (p_seq_st) {
+ p_seq_st->status.value = seq_st.status.value;
+ p_seq_st->diff = seq_st.diff;
+ }
+}
+
+
+
+void pjmedia_rtp_seq_restart(pjmedia_rtp_seq_session *sess, pj_uint16_t seq)
+{
+ sess->base_seq = seq;
+ sess->max_seq = seq;
+ sess->bad_seq = RTP_SEQ_MOD + 1;
+ sess->cycles = 0;
+}
+
+
+void pjmedia_rtp_seq_init(pjmedia_rtp_seq_session *sess, pj_uint16_t seq)
+{
+ pjmedia_rtp_seq_restart(sess, seq);
+
+ sess->max_seq = (pj_uint16_t) (seq - 1);
+ sess->probation = MIN_SEQUENTIAL;
+}
+
+
+void pjmedia_rtp_seq_update( pjmedia_rtp_seq_session *sess,
+ pj_uint16_t seq,
+ pjmedia_rtp_status *seq_status)
+{
+ pj_uint16_t udelta = (pj_uint16_t) (seq - sess->max_seq);
+ pjmedia_rtp_status st;
+
+ /* Init status */
+ st.status.value = 0;
+ st.diff = 0;
+
+ /*
+ * Source is not valid until MIN_SEQUENTIAL packets with
+ * sequential sequence numbers have been received.
+ */
+ if (sess->probation) {
+
+ st.status.flag.probation = 1;
+
+ if (seq == sess->max_seq+ 1) {
+ /* packet is in sequence */
+ st.diff = 1;
+ sess->probation--;
+ sess->max_seq = seq;
+ if (sess->probation == 0) {
+ st.status.flag.probation = 0;
+ }
+ } else {
+
+ st.diff = 0;
+
+ st.status.flag.bad = 1;
+ if (seq == sess->max_seq)
+ st.status.flag.dup = 1;
+ else
+ st.status.flag.outorder = 1;
+
+ sess->probation = MIN_SEQUENTIAL - 1;
+ sess->max_seq = seq;
+ }
+
+
+ } else if (udelta == 0) {
+
+ st.status.flag.dup = 1;
+
+ } else if (udelta < MAX_DROPOUT) {
+ /* in order, with permissible gap */
+ if (seq < sess->max_seq) {
+ /* Sequence number wrapped - count another 64K cycle. */
+ sess->cycles += RTP_SEQ_MOD;
+ }
+ sess->max_seq = seq;
+
+ st.diff = udelta;
+
+ } else if (udelta <= (RTP_SEQ_MOD - MAX_MISORDER)) {
+ /* the sequence number made a very large jump */
+ if (seq == sess->bad_seq) {
+ /*
+ * Two sequential packets -- assume that the other side
+ * restarted without telling us so just re-sync
+ * (i.e., pretend this was the first packet).
+ */
+ pjmedia_rtp_seq_restart(sess, seq);
+ st.status.flag.restart = 1;
+ st.status.flag.probation = 1;
+ st.diff = 1;
+ }
+ else {
+ sess->bad_seq = (seq + 1) & (RTP_SEQ_MOD-1);
+ st.status.flag.bad = 1;
+ st.status.flag.outorder = 1;
+ }
+ } else {
+ /* old duplicate or reordered packet.
+ * Not necessarily bad packet (?)
+ */
+ st.status.flag.outorder = 1;
+ }
+
+
+ if (seq_status) {
+ seq_status->diff = st.diff;
+ seq_status->status.value = st.status.value;
+ }
+}
+
+
diff --git a/pjmedia/src/pjmedia/sdp.c b/pjmedia/src/pjmedia/sdp.c
new file mode 100644
index 0000000..e54bfcf
--- /dev/null
+++ b/pjmedia/src/pjmedia/sdp.c
@@ -0,0 +1,1576 @@
+/* $Id: sdp.c 3945 2012-01-27 09:12:59Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/sdp.h>
+#include <pjmedia/errno.h>
+#include <pjlib-util/scanner.h>
+#include <pj/array.h>
+#include <pj/except.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+
+
+enum {
+ SKIP_WS = 0,
+ SYNTAX_ERROR = 1,
+};
+// New token definition from RFC 4566 (SDP)
+#define TOKEN "!#$%&'*+-.^_`{|}~"
+//#define TOKEN "-.!%*_=`'~"
+//#define TOKEN "'`-./:?\"#$&*;=@[]^_`{|}+~!"
+#define NTP_OFFSET ((pj_uint32_t)2208988800)
+#define THIS_FILE "sdp.c"
+
+typedef struct parse_context
+{
+ pj_status_t last_error;
+} parse_context;
+
+
+/*
+ * Prototypes for line parser.
+ */
+static void parse_version(pj_scanner *scanner, parse_context *ctx);
+static void parse_origin(pj_scanner *scanner, pjmedia_sdp_session *ses,
+ parse_context *ctx);
+static void parse_time(pj_scanner *scanner, pjmedia_sdp_session *ses,
+ parse_context *ctx);
+static void parse_generic_line(pj_scanner *scanner, pj_str_t *str,
+ parse_context *ctx);
+static void parse_connection_info(pj_scanner *scanner, pjmedia_sdp_conn *conn,
+ parse_context *ctx);
+static void parse_bandwidth_info(pj_scanner *scanner, pjmedia_sdp_bandw *bandw,
+ parse_context *ctx);
+static pjmedia_sdp_attr *parse_attr(pj_pool_t *pool, pj_scanner *scanner,
+ parse_context *ctx);
+static void parse_media(pj_scanner *scanner, pjmedia_sdp_media *med,
+ parse_context *ctx);
+static void on_scanner_error(pj_scanner *scanner);
+
+/*
+ * Scanner character specification.
+ */
+static int is_initialized;
+static pj_cis_buf_t cis_buf;
+static pj_cis_t cs_digit, cs_token;
+
+static void init_sdp_parser(void)
+{
+ if (is_initialized != 0)
+ return;
+
+ pj_enter_critical_section();
+
+ if (is_initialized != 0) {
+ pj_leave_critical_section();
+ return;
+ }
+
+ pj_cis_buf_init(&cis_buf);
+
+ pj_cis_init(&cis_buf, &cs_token);
+ pj_cis_add_alpha(&cs_token);
+ pj_cis_add_num(&cs_token);
+ pj_cis_add_str(&cs_token, TOKEN);
+
+ pj_cis_init(&cis_buf, &cs_digit);
+ pj_cis_add_num(&cs_digit);
+
+ is_initialized = 1;
+ pj_leave_critical_section();
+}
+
+PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_create( pj_pool_t *pool,
+ const char *name,
+ const pj_str_t *value)
+{
+ pjmedia_sdp_attr *attr;
+
+ PJ_ASSERT_RETURN(pool && name, NULL);
+
+ attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr);
+ pj_strdup2(pool, &attr->name, name);
+
+ if (value)
+ pj_strdup_with_null(pool, &attr->value, value);
+ else {
+ attr->value.ptr = NULL;
+ attr->value.slen = 0;
+ }
+
+ return attr;
+}
+
+PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_clone(pj_pool_t *pool,
+ const pjmedia_sdp_attr *rhs)
+{
+ pjmedia_sdp_attr *attr;
+
+ PJ_ASSERT_RETURN(pool && rhs, NULL);
+
+ attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr);
+
+ pj_strdup(pool, &attr->name, &rhs->name);
+ pj_strdup_with_null(pool, &attr->value, &rhs->value);
+
+ return attr;
+}
+
+PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_find (unsigned count,
+ pjmedia_sdp_attr *const attr_array[],
+ const pj_str_t *name,
+ const pj_str_t *c_fmt)
+{
+ unsigned i;
+ unsigned c_pt = 0xFFFF;
+
+ if (c_fmt)
+ c_pt = pj_strtoul(c_fmt);
+
+ for (i=0; i<count; ++i) {
+ if (pj_strcmp(&attr_array[i]->name, name) == 0) {
+ const pjmedia_sdp_attr *a = attr_array[i];
+ if (c_fmt) {
+ unsigned pt = (unsigned) pj_strtoul2(&a->value, NULL, 10);
+ if (pt == c_pt) {
+ return (pjmedia_sdp_attr*)a;
+ }
+ } else
+ return (pjmedia_sdp_attr*)a;
+ }
+ }
+ return NULL;
+}
+
+PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_find2(unsigned count,
+ pjmedia_sdp_attr *const attr_array[],
+ const char *c_name,
+ const pj_str_t *c_fmt)
+{
+ pj_str_t name;
+
+ name.ptr = (char*)c_name;
+ name.slen = pj_ansi_strlen(c_name);
+
+ return pjmedia_sdp_attr_find(count, attr_array, &name, c_fmt);
+}
+
+
+
+PJ_DEF(pj_status_t) pjmedia_sdp_attr_add(unsigned *count,
+ pjmedia_sdp_attr *attr_array[],
+ pjmedia_sdp_attr *attr)
+{
+ PJ_ASSERT_RETURN(count && attr_array && attr, PJ_EINVAL);
+ PJ_ASSERT_RETURN(*count < PJMEDIA_MAX_SDP_ATTR, PJ_ETOOMANY);
+
+ attr_array[*count] = attr;
+ (*count)++;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(unsigned) pjmedia_sdp_attr_remove_all(unsigned *count,
+ pjmedia_sdp_attr *attr_array[],
+ const char *name)
+{
+ unsigned i, removed = 0;
+ pj_str_t attr_name;
+
+ PJ_ASSERT_RETURN(count && attr_array && name, PJ_EINVAL);
+
+ attr_name.ptr = (char*)name;
+ attr_name.slen = pj_ansi_strlen(name);
+
+ for (i=0; i<*count; ) {
+ if (pj_strcmp(&attr_array[i]->name, &attr_name)==0) {
+ pj_array_erase(attr_array, sizeof(pjmedia_sdp_attr*),
+ *count, i);
+ --(*count);
+ ++removed;
+ } else {
+ ++i;
+ }
+ }
+
+ return removed;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_sdp_attr_remove( unsigned *count,
+ pjmedia_sdp_attr *attr_array[],
+ pjmedia_sdp_attr *attr )
+{
+ unsigned i, removed=0;
+
+ PJ_ASSERT_RETURN(count && attr_array && attr, PJ_EINVAL);
+
+ for (i=0; i<*count; ) {
+ if (attr_array[i] == attr) {
+ pj_array_erase(attr_array, sizeof(pjmedia_sdp_attr*),
+ *count, i);
+ --(*count);
+ ++removed;
+ } else {
+ ++i;
+ }
+ }
+
+ return removed ? PJ_SUCCESS : PJ_ENOTFOUND;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_rtpmap( const pjmedia_sdp_attr *attr,
+ pjmedia_sdp_rtpmap *rtpmap)
+{
+ pj_scanner scanner;
+ pj_str_t token;
+ pj_status_t status = -1;
+ char term = 0;
+ PJ_USE_EXCEPTION;
+
+ PJ_ASSERT_RETURN(pj_strcmp2(&attr->name, "rtpmap")==0, PJ_EINVALIDOP);
+
+ PJ_ASSERT_RETURN(attr->value.slen != 0, PJMEDIA_SDP_EINATTR);
+
+ init_sdp_parser();
+
+ /* Check if input is null terminated, and null terminate if
+ * necessary. Unfortunately this may crash the application if
+ * attribute was allocated from a read-only memory location.
+ * But this shouldn't happen as attribute's value normally is
+ * null terminated.
+ */
+ if (attr->value.ptr[attr->value.slen] != 0 &&
+ attr->value.ptr[attr->value.slen] != '\r' &&
+ attr->value.ptr[attr->value.slen] != '\n')
+ {
+ pj_assert(!"Shouldn't happen");
+ term = attr->value.ptr[attr->value.slen];
+ attr->value.ptr[attr->value.slen] = '\0';
+ }
+
+ pj_scan_init(&scanner, (char*)attr->value.ptr, attr->value.slen,
+ PJ_SCAN_AUTOSKIP_WS, &on_scanner_error);
+
+ /* rtpmap sample:
+ * a=rtpmap:98 L16/16000/2.
+ */
+
+ /* Init */
+ rtpmap->pt.slen = rtpmap->param.slen = rtpmap->enc_name.slen = 0;
+ rtpmap->clock_rate = 0;
+
+ /* Parse */
+ PJ_TRY {
+
+ /* Get payload type. */
+ pj_scan_get(&scanner, &cs_token, &rtpmap->pt);
+
+
+ /* Get encoding name. */
+ pj_scan_get(&scanner, &cs_token, &rtpmap->enc_name);
+
+ /* Expecting '/' after encoding name. */
+ if (pj_scan_get_char(&scanner) != '/') {
+ status = PJMEDIA_SDP_EINRTPMAP;
+ goto on_return;
+ }
+
+
+ /* Get the clock rate. */
+ pj_scan_get(&scanner, &cs_digit, &token);
+ rtpmap->clock_rate = pj_strtoul(&token);
+
+ /* Expecting either '/' or EOF */
+ if (*scanner.curptr == '/') {
+ pj_scan_get_char(&scanner);
+ rtpmap->param.ptr = scanner.curptr;
+ rtpmap->param.slen = scanner.end - scanner.curptr;
+ } else {
+ rtpmap->param.slen = 0;
+ }
+
+ status = PJ_SUCCESS;
+ }
+ PJ_CATCH_ANY {
+ status = PJMEDIA_SDP_EINRTPMAP;
+ }
+ PJ_END;
+
+
+on_return:
+ pj_scan_fini(&scanner);
+ if (term) {
+ attr->value.ptr[attr->value.slen] = term;
+ }
+ return status;
+}
+
+PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_fmtp( const pjmedia_sdp_attr *attr,
+ pjmedia_sdp_fmtp *fmtp)
+{
+ const char *p = attr->value.ptr;
+ const char *end = attr->value.ptr + attr->value.slen;
+ pj_str_t token;
+
+ PJ_ASSERT_RETURN(pj_strcmp2(&attr->name, "fmtp")==0, PJ_EINVALIDOP);
+
+ /* fmtp BNF:
+ * a=fmtp:<format> <format specific parameter>
+ */
+
+ /* Get format. */
+ token.ptr = (char*)p;
+ while (pj_isdigit(*p) && p!=end)
+ ++p;
+ token.slen = p - token.ptr;
+ if (token.slen == 0)
+ return PJMEDIA_SDP_EINFMTP;
+
+ fmtp->fmt = token;
+
+ /* Expecting space after format. */
+ if (*p != ' ') return PJMEDIA_SDP_EINFMTP;
+
+ /* Get space. */
+ ++p;
+
+ /* Set the remaining string as fmtp format parameter. */
+ fmtp->fmt_param.ptr = (char*)p;
+ fmtp->fmt_param.slen = end - p;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_rtcp(const pjmedia_sdp_attr *attr,
+ pjmedia_sdp_rtcp_attr *rtcp)
+{
+ pj_scanner scanner;
+ pj_str_t token;
+ pj_status_t status = -1;
+ PJ_USE_EXCEPTION;
+
+ PJ_ASSERT_RETURN(pj_strcmp2(&attr->name, "rtcp")==0, PJ_EINVALIDOP);
+
+ init_sdp_parser();
+
+ /* fmtp BNF:
+ * a=rtcp:<port> [nettype addrtype address]
+ */
+
+ pj_scan_init(&scanner, (char*)attr->value.ptr, attr->value.slen,
+ PJ_SCAN_AUTOSKIP_WS, &on_scanner_error);
+
+ /* Init */
+ rtcp->net_type.slen = rtcp->addr_type.slen = rtcp->addr.slen = 0;
+
+ /* Parse */
+ PJ_TRY {
+
+ /* Get the port */
+ pj_scan_get(&scanner, &cs_token, &token);
+ rtcp->port = pj_strtoul(&token);
+
+ /* Have address? */
+ if (!pj_scan_is_eof(&scanner)) {
+
+ /* Get network type */
+ pj_scan_get(&scanner, &cs_token, &rtcp->net_type);
+
+ /* Get address type */
+ pj_scan_get(&scanner, &cs_token, &rtcp->addr_type);
+
+ /* Get the address */
+ pj_scan_get(&scanner, &cs_token, &rtcp->addr);
+
+ }
+
+ status = PJ_SUCCESS;
+
+ }
+ PJ_CATCH_ANY {
+ status = PJMEDIA_SDP_EINRTCP;
+ }
+ PJ_END;
+
+ pj_scan_fini(&scanner);
+ return status;
+}
+
+
+PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_create_rtcp(pj_pool_t *pool,
+ const pj_sockaddr *a)
+{
+ enum {
+ ATTR_LEN = PJ_INET6_ADDRSTRLEN+16
+ };
+ pjmedia_sdp_attr *attr;
+
+ attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr);
+ attr->name = pj_str("rtcp");
+ attr->value.ptr = (char*) pj_pool_alloc(pool, ATTR_LEN);
+ if (a->addr.sa_family == pj_AF_INET()) {
+ attr->value.slen =
+ pj_ansi_snprintf(attr->value.ptr, ATTR_LEN,
+ "%u IN IP4 %s",
+ pj_ntohs(a->ipv4.sin_port),
+ pj_inet_ntoa(a->ipv4.sin_addr));
+ } else if (a->addr.sa_family == pj_AF_INET6()) {
+ char tmp_addr[PJ_INET6_ADDRSTRLEN];
+ attr->value.slen =
+ pj_ansi_snprintf(attr->value.ptr, ATTR_LEN,
+ "%u IN IP6 %s",
+ pj_sockaddr_get_port(a),
+ pj_sockaddr_print(a, tmp_addr,
+ sizeof(tmp_addr), 0));
+
+ } else {
+ pj_assert(!"Unsupported address family");
+ return NULL;
+ }
+
+ return attr;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_sdp_attr_to_rtpmap(pj_pool_t *pool,
+ const pjmedia_sdp_attr *attr,
+ pjmedia_sdp_rtpmap **p_rtpmap)
+{
+ PJ_ASSERT_RETURN(pool && attr && p_rtpmap, PJ_EINVAL);
+
+ *p_rtpmap = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_rtpmap);
+ PJ_ASSERT_RETURN(*p_rtpmap, PJ_ENOMEM);
+
+ return pjmedia_sdp_attr_get_rtpmap(attr, *p_rtpmap);
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_sdp_rtpmap_to_attr(pj_pool_t *pool,
+ const pjmedia_sdp_rtpmap *rtpmap,
+ pjmedia_sdp_attr **p_attr)
+{
+ pjmedia_sdp_attr *attr;
+ char tempbuf[128];
+ int len;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(pool && rtpmap && p_attr, PJ_EINVAL);
+
+ /* Check that mandatory attributes are specified. */
+ PJ_ASSERT_RETURN(rtpmap->enc_name.slen && rtpmap->clock_rate,
+ PJMEDIA_SDP_EINRTPMAP);
+
+
+ attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr);
+ PJ_ASSERT_RETURN(attr != NULL, PJ_ENOMEM);
+
+ attr->name.ptr = "rtpmap";
+ attr->name.slen = 6;
+
+ /* Format: ":pt enc_name/clock_rate[/param]" */
+ len = pj_ansi_snprintf(tempbuf, sizeof(tempbuf),
+ "%.*s %.*s/%u%s%.*s",
+ (int)rtpmap->pt.slen,
+ rtpmap->pt.ptr,
+ (int)rtpmap->enc_name.slen,
+ rtpmap->enc_name.ptr,
+ rtpmap->clock_rate,
+ (rtpmap->param.slen ? "/" : ""),
+ (int)rtpmap->param.slen,
+ rtpmap->param.ptr);
+
+ if (len < 1 || len > (int)sizeof(tempbuf))
+ return PJMEDIA_SDP_ERTPMAPTOOLONG;
+
+ attr->value.slen = len;
+ attr->value.ptr = (char*) pj_pool_alloc(pool, attr->value.slen+1);
+ pj_memcpy(attr->value.ptr, tempbuf, attr->value.slen+1);
+
+ *p_attr = attr;
+ return PJ_SUCCESS;
+}
+
+
+static int print_connection_info( pjmedia_sdp_conn *c, char *buf, int len)
+{
+ int printed;
+
+ printed = pj_ansi_snprintf(buf, len, "c=%.*s %.*s %.*s\r\n",
+ (int)c->net_type.slen,
+ c->net_type.ptr,
+ (int)c->addr_type.slen,
+ c->addr_type.ptr,
+ (int)c->addr.slen,
+ c->addr.ptr);
+ if (printed < 1 || printed > len)
+ return -1;
+
+ return printed;
+}
+
+
+PJ_DEF(pjmedia_sdp_conn*) pjmedia_sdp_conn_clone (pj_pool_t *pool,
+ const pjmedia_sdp_conn *rhs)
+{
+ pjmedia_sdp_conn *c = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_conn);
+ if (!c) return NULL;
+
+ if (!pj_strdup (pool, &c->net_type, &rhs->net_type)) return NULL;
+ if (!pj_strdup (pool, &c->addr_type, &rhs->addr_type)) return NULL;
+ if (!pj_strdup (pool, &c->addr, &rhs->addr)) return NULL;
+
+ return c;
+}
+
+PJ_DEF(pjmedia_sdp_bandw*)
+pjmedia_sdp_bandw_clone (pj_pool_t *pool,
+ const pjmedia_sdp_bandw *rhs)
+{
+ pjmedia_sdp_bandw *b = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_bandw);
+ if (!b) return NULL;
+
+ if (!pj_strdup (pool, &b->modifier, &rhs->modifier)) return NULL;
+ b->value = rhs->value;
+
+ return b;
+}
+
+static pj_ssize_t print_bandw(const pjmedia_sdp_bandw *bandw,
+ char *buf, pj_size_t len)
+{
+ char *p = buf;
+
+ if ((int)len < bandw->modifier.slen + 10 + 5)
+ return -1;
+
+ *p++ = 'b';
+ *p++ = '=';
+ pj_memcpy(p, bandw->modifier.ptr, bandw->modifier.slen);
+ p += bandw->modifier.slen;
+ *p++ = ':';
+ p += pj_utoa(bandw->value, p);
+
+ *p++ = '\r';
+ *p++ = '\n';
+ return p-buf;
+}
+
+static pj_ssize_t print_attr(const pjmedia_sdp_attr *attr,
+ char *buf, pj_size_t len)
+{
+ char *p = buf;
+
+ if ((int)len < attr->name.slen + attr->value.slen + 10)
+ return -1;
+
+ *p++ = 'a';
+ *p++ = '=';
+ pj_memcpy(p, attr->name.ptr, attr->name.slen);
+ p += attr->name.slen;
+
+
+ if (attr->value.slen) {
+ *p++ = ':';
+ pj_memcpy(p, attr->value.ptr, attr->value.slen);
+ p += attr->value.slen;
+ }
+
+ *p++ = '\r';
+ *p++ = '\n';
+ return p-buf;
+}
+
+static int print_media_desc( pjmedia_sdp_media *m, char *buf, int len)
+{
+ char *p = buf;
+ char *end = buf+len;
+ unsigned i;
+ int printed;
+
+ /* check length for the "m=" line. */
+ if (len < m->desc.media.slen+m->desc.transport.slen+12+24) {
+ return -1;
+ }
+ *p++ = 'm'; /* m= */
+ *p++ = '=';
+ pj_memcpy(p, m->desc.media.ptr, m->desc.media.slen);
+ p += m->desc.media.slen;
+ *p++ = ' ';
+ printed = pj_utoa(m->desc.port, p);
+ p += printed;
+ if (m->desc.port_count > 1) {
+ *p++ = '/';
+ printed = pj_utoa(m->desc.port_count, p);
+ p += printed;
+ }
+ *p++ = ' ';
+ pj_memcpy(p, m->desc.transport.ptr, m->desc.transport.slen);
+ p += m->desc.transport.slen;
+ for (i=0; i<m->desc.fmt_count; ++i) {
+ *p++ = ' ';
+ pj_memcpy(p, m->desc.fmt[i].ptr, m->desc.fmt[i].slen);
+ p += m->desc.fmt[i].slen;
+ }
+ *p++ = '\r';
+ *p++ = '\n';
+
+ /* print connection info, if present. */
+ if (m->conn) {
+ printed = print_connection_info(m->conn, p, end-p);
+ if (printed < 0) {
+ return -1;
+ }
+ p += printed;
+ }
+
+ /* print optional bandwidth info. */
+ for (i=0; i<m->bandw_count; ++i) {
+ printed = print_bandw(m->bandw[i], p, end-p);
+ if (printed < 0) {
+ return -1;
+ }
+ p += printed;
+ }
+
+ /* print attributes. */
+ for (i=0; i<m->attr_count; ++i) {
+ printed = print_attr(m->attr[i], p, end-p);
+ if (printed < 0) {
+ return -1;
+ }
+ p += printed;
+ }
+
+ return p-buf;
+}
+
+PJ_DEF(pjmedia_sdp_media*) pjmedia_sdp_media_clone(
+ pj_pool_t *pool,
+ const pjmedia_sdp_media *rhs)
+{
+ unsigned int i;
+ pjmedia_sdp_media *m = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_media);
+ PJ_ASSERT_RETURN(m != NULL, NULL);
+
+ pj_strdup (pool, &m->desc.media, &rhs->desc.media);
+ m->desc.port = rhs->desc.port;
+ m->desc.port_count = rhs->desc.port_count;
+ pj_strdup (pool, &m->desc.transport, &rhs->desc.transport);
+ m->desc.fmt_count = rhs->desc.fmt_count;
+ for (i=0; i<rhs->desc.fmt_count; ++i)
+ pj_strdup(pool, &m->desc.fmt[i], &rhs->desc.fmt[i]);
+
+ if (rhs->conn) {
+ m->conn = pjmedia_sdp_conn_clone (pool, rhs->conn);
+ PJ_ASSERT_RETURN(m->conn != NULL, NULL);
+ } else {
+ m->conn = NULL;
+ }
+
+ m->bandw_count = rhs->bandw_count;
+ for (i=0; i < rhs->bandw_count; ++i) {
+ m->bandw[i] = pjmedia_sdp_bandw_clone (pool, rhs->bandw[i]);
+ PJ_ASSERT_RETURN(m->bandw[i] != NULL, NULL);
+ }
+
+ m->attr_count = rhs->attr_count;
+ for (i=0; i < rhs->attr_count; ++i) {
+ m->attr[i] = pjmedia_sdp_attr_clone (pool, rhs->attr[i]);
+ PJ_ASSERT_RETURN(m->attr[i] != NULL, NULL);
+ }
+
+ return m;
+}
+
+PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_media_find_attr(
+ const pjmedia_sdp_media *m,
+ const pj_str_t *name, const pj_str_t *fmt)
+{
+ PJ_ASSERT_RETURN(m && name, NULL);
+ return pjmedia_sdp_attr_find(m->attr_count, m->attr, name, fmt);
+}
+
+
+
+PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_media_find_attr2(
+ const pjmedia_sdp_media *m,
+ const char *name, const pj_str_t *fmt)
+{
+ PJ_ASSERT_RETURN(m && name, NULL);
+ return pjmedia_sdp_attr_find2(m->attr_count, m->attr, name, fmt);
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_sdp_media_add_attr( pjmedia_sdp_media *m,
+ pjmedia_sdp_attr *attr)
+{
+ return pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
+}
+
+PJ_DEF(pj_status_t) pjmedia_sdp_session_add_attr(pjmedia_sdp_session *s,
+ pjmedia_sdp_attr *attr)
+{
+ return pjmedia_sdp_attr_add(&s->attr_count, s->attr, attr);
+}
+
+PJ_DEF(unsigned) pjmedia_sdp_media_remove_all_attr(pjmedia_sdp_media *m,
+ const char *name)
+{
+ return pjmedia_sdp_attr_remove_all(&m->attr_count, m->attr, name);
+}
+
+PJ_DEF(pj_status_t) pjmedia_sdp_media_remove_attr(pjmedia_sdp_media *m,
+ pjmedia_sdp_attr *attr)
+{
+ return pjmedia_sdp_attr_remove(&m->attr_count, m->attr, attr);
+}
+
+static int print_session(const pjmedia_sdp_session *ses,
+ char *buf, pj_ssize_t len)
+{
+ char *p = buf;
+ char *end = buf+len;
+ unsigned i;
+ int printed;
+
+ /* Check length for v= and o= lines. */
+ if (len < 5+
+ 2+ses->origin.user.slen+18+
+ ses->origin.net_type.slen+ses->origin.addr.slen + 2)
+ {
+ return -1;
+ }
+
+ /* SDP version (v= line) */
+ pj_memcpy(p, "v=0\r\n", 5);
+ p += 5;
+
+ /* Owner (o=) line. */
+ *p++ = 'o';
+ *p++ = '=';
+ pj_memcpy(p, ses->origin.user.ptr, ses->origin.user.slen);
+ p += ses->origin.user.slen;
+ *p++ = ' ';
+ printed = pj_utoa(ses->origin.id, p);
+ p += printed;
+ *p++ = ' ';
+ printed = pj_utoa(ses->origin.version, p);
+ p += printed;
+ *p++ = ' ';
+ pj_memcpy(p, ses->origin.net_type.ptr, ses->origin.net_type.slen);
+ p += ses->origin.net_type.slen;
+ *p++ = ' ';
+ pj_memcpy(p, ses->origin.addr_type.ptr, ses->origin.addr_type.slen);
+ p += ses->origin.addr_type.slen;
+ *p++ = ' ';
+ pj_memcpy(p, ses->origin.addr.ptr, ses->origin.addr.slen);
+ p += ses->origin.addr.slen;
+ *p++ = '\r';
+ *p++ = '\n';
+
+ /* Session name (s=) line. */
+ if ((end-p) < 8+ses->name.slen) {
+ return -1;
+ }
+ *p++ = 's';
+ *p++ = '=';
+ pj_memcpy(p, ses->name.ptr, ses->name.slen);
+ p += ses->name.slen;
+ *p++ = '\r';
+ *p++ = '\n';
+
+ /* Connection line (c=) if exist. */
+ if (ses->conn) {
+ printed = print_connection_info(ses->conn, p, end-p);
+ if (printed < 1) {
+ return -1;
+ }
+ p += printed;
+ }
+
+ /* print optional bandwidth info. */
+ for (i=0; i<ses->bandw_count; ++i) {
+ printed = print_bandw(ses->bandw[i], p, end-p);
+ if (printed < 1) {
+ return -1;
+ }
+ p += printed;
+ }
+
+ /* Time */
+ if ((end-p) < 24) {
+ return -1;
+ }
+ *p++ = 't';
+ *p++ = '=';
+ printed = pj_utoa(ses->time.start, p);
+ p += printed;
+ *p++ = ' ';
+ printed = pj_utoa(ses->time.stop, p);
+ p += printed;
+ *p++ = '\r';
+ *p++ = '\n';
+
+ /* Print all attribute (a=) lines. */
+ for (i=0; i<ses->attr_count; ++i) {
+ printed = print_attr(ses->attr[i], p, end-p);
+ if (printed < 0) {
+ return -1;
+ }
+ p += printed;
+ }
+
+ /* Print media (m=) lines. */
+ for (i=0; i<ses->media_count; ++i) {
+ printed = print_media_desc(ses->media[i], p, end-p);
+ if (printed < 0) {
+ return -1;
+ }
+ p += printed;
+ }
+
+ return p-buf;
+}
+
+/******************************************************************************
+ * PARSERS
+ */
+
+static void parse_version(pj_scanner *scanner, parse_context *ctx)
+{
+ ctx->last_error = PJMEDIA_SDP_EINVER;
+
+ /* check equal sign */
+ if (*(scanner->curptr+1) != '=') {
+ on_scanner_error(scanner);
+ return;
+ }
+
+ /* check version is 0 */
+ if (*(scanner->curptr+2) != '0') {
+ on_scanner_error(scanner);
+ return;
+ }
+
+ /* We've got what we're looking for, skip anything until newline */
+ pj_scan_skip_line(scanner);
+}
+
+static void parse_origin(pj_scanner *scanner, pjmedia_sdp_session *ses,
+ parse_context *ctx)
+{
+ pj_str_t str;
+
+ ctx->last_error = PJMEDIA_SDP_EINORIGIN;
+
+ /* check equal sign */
+ if (*(scanner->curptr+1) != '=') {
+ on_scanner_error(scanner);
+ return;
+ }
+
+ /* o= */
+ pj_scan_advance_n(scanner, 2, SKIP_WS);
+
+ /* username. */
+ pj_scan_get_until_ch(scanner, ' ', &ses->origin.user);
+ pj_scan_get_char(scanner);
+
+ /* id */
+ pj_scan_get_until_ch(scanner, ' ', &str);
+ ses->origin.id = pj_strtoul(&str);
+ pj_scan_get_char(scanner);
+
+ /* version */
+ pj_scan_get_until_ch(scanner, ' ', &str);
+ ses->origin.version = pj_strtoul(&str);
+ pj_scan_get_char(scanner);
+
+ /* network-type */
+ pj_scan_get_until_ch(scanner, ' ', &ses->origin.net_type);
+ pj_scan_get_char(scanner);
+
+ /* addr-type */
+ pj_scan_get_until_ch(scanner, ' ', &ses->origin.addr_type);
+ pj_scan_get_char(scanner);
+
+ /* address */
+ pj_scan_get_until_chr(scanner, " \t\r\n", &ses->origin.addr);
+
+ /* We've got what we're looking for, skip anything until newline */
+ pj_scan_skip_line(scanner);
+
+}
+
+static void parse_time(pj_scanner *scanner, pjmedia_sdp_session *ses,
+ parse_context *ctx)
+{
+ pj_str_t str;
+
+ ctx->last_error = PJMEDIA_SDP_EINTIME;
+
+ /* check equal sign */
+ if (*(scanner->curptr+1) != '=') {
+ on_scanner_error(scanner);
+ return;
+ }
+
+ /* t= */
+ pj_scan_advance_n(scanner, 2, SKIP_WS);
+
+ /* start time */
+ pj_scan_get_until_ch(scanner, ' ', &str);
+ ses->time.start = pj_strtoul(&str);
+
+ pj_scan_get_char(scanner);
+
+ /* stop time */
+ pj_scan_get_until_chr(scanner, " \t\r\n", &str);
+ ses->time.stop = pj_strtoul(&str);
+
+ /* We've got what we're looking for, skip anything until newline */
+ pj_scan_skip_line(scanner);
+}
+
+static void parse_generic_line(pj_scanner *scanner, pj_str_t *str,
+ parse_context *ctx)
+{
+ ctx->last_error = PJMEDIA_SDP_EINSDP;
+
+ /* check equal sign */
+ if (*(scanner->curptr+1) != '=') {
+ on_scanner_error(scanner);
+ return;
+ }
+
+ /* x= */
+ pj_scan_advance_n(scanner, 2, SKIP_WS);
+
+ /* get anything until newline (including whitespaces). */
+ pj_scan_get_until_chr(scanner, "\r\n", str);
+
+ /* newline. */
+ pj_scan_get_newline(scanner);
+}
+
+static void parse_connection_info(pj_scanner *scanner, pjmedia_sdp_conn *conn,
+ parse_context *ctx)
+{
+ ctx->last_error = PJMEDIA_SDP_EINCONN;
+
+ /* c= */
+ pj_scan_advance_n(scanner, 2, SKIP_WS);
+
+ /* network-type */
+ pj_scan_get_until_ch(scanner, ' ', &conn->net_type);
+ pj_scan_get_char(scanner);
+
+ /* addr-type */
+ pj_scan_get_until_ch(scanner, ' ', &conn->addr_type);
+ pj_scan_get_char(scanner);
+
+ /* address. */
+ pj_scan_get_until_chr(scanner, "/ \t\r\n", &conn->addr);
+ PJ_TODO(PARSE_SDP_CONN_ADDRESS_SUBFIELDS);
+
+ /* We've got what we're looking for, skip anything until newline */
+ pj_scan_skip_line(scanner);
+}
+
+static void parse_bandwidth_info(pj_scanner *scanner, pjmedia_sdp_bandw *bandw,
+ parse_context *ctx)
+{
+ pj_str_t str;
+
+ ctx->last_error = PJMEDIA_SDP_EINBANDW;
+
+ /* b= */
+ pj_scan_advance_n(scanner, 2, SKIP_WS);
+
+ /* modifier */
+ pj_scan_get_until_ch(scanner, ':', &bandw->modifier);
+ pj_scan_get_char(scanner);
+
+ /* value */
+ pj_scan_get_until_chr(scanner, " \t\r\n", &str);
+ bandw->value = pj_strtoul(&str);
+
+ /* We've got what we're looking for, skip anything until newline */
+ pj_scan_skip_line(scanner);
+}
+
+static void parse_media(pj_scanner *scanner, pjmedia_sdp_media *med,
+ parse_context *ctx)
+{
+ pj_str_t str;
+
+ ctx->last_error = PJMEDIA_SDP_EINMEDIA;
+
+ /* check the equal sign */
+ if (*(scanner->curptr+1) != '=') {
+ on_scanner_error(scanner);
+ return;
+ }
+
+ /* m= */
+ pj_scan_advance_n(scanner, 2, SKIP_WS);
+
+ /* type */
+ pj_scan_get_until_ch(scanner, ' ', &med->desc.media);
+ pj_scan_get_char(scanner);
+
+ /* port */
+ pj_scan_get(scanner, &cs_token, &str);
+ med->desc.port = (unsigned short)pj_strtoul(&str);
+ if (*scanner->curptr == '/') {
+ /* port count */
+ pj_scan_get_char(scanner);
+ pj_scan_get(scanner, &cs_token, &str);
+ med->desc.port_count = pj_strtoul(&str);
+
+ } else {
+ med->desc.port_count = 0;
+ }
+
+ if (pj_scan_get_char(scanner) != ' ') {
+ PJ_THROW(SYNTAX_ERROR);
+ }
+
+ /* transport */
+ pj_scan_get_until_chr(scanner, " \t\r\n", &med->desc.transport);
+
+ /* format list */
+ med->desc.fmt_count = 0;
+ while (*scanner->curptr == ' ') {
+ pj_str_t fmt;
+
+ pj_scan_get_char(scanner);
+
+ /* Check again for the end of the line */
+ if ((*scanner->curptr == '\r') || (*scanner->curptr == '\n'))
+ break;
+
+ pj_scan_get(scanner, &cs_token, &fmt);
+ if (med->desc.fmt_count < PJMEDIA_MAX_SDP_FMT)
+ med->desc.fmt[med->desc.fmt_count++] = fmt;
+ else
+ PJ_PERROR(2,(THIS_FILE, PJ_ETOOMANY,
+ "Error adding SDP media format %.*s, "
+ "format is ignored",
+ (int)fmt.slen, fmt.ptr));
+ }
+
+ /* We've got what we're looking for, skip anything until newline */
+ pj_scan_skip_line(scanner);
+}
+
+static void on_scanner_error(pj_scanner *scanner)
+{
+ PJ_UNUSED_ARG(scanner);
+
+ PJ_THROW(SYNTAX_ERROR);
+}
+
+static pjmedia_sdp_attr *parse_attr( pj_pool_t *pool, pj_scanner *scanner,
+ parse_context *ctx)
+{
+ pjmedia_sdp_attr *attr;
+
+ ctx->last_error = PJMEDIA_SDP_EINATTR;
+
+ attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr);
+
+ /* check equal sign */
+ if (*(scanner->curptr+1) != '=') {
+ on_scanner_error(scanner);
+ return NULL;
+ }
+
+ /* skip a= */
+ pj_scan_advance_n(scanner, 2, SKIP_WS);
+
+ /* get attr name. */
+ pj_scan_get(scanner, &cs_token, &attr->name);
+
+ if (*scanner->curptr && *scanner->curptr != '\r' &&
+ *scanner->curptr != '\n')
+ {
+ /* skip ':' if present. */
+ if (*scanner->curptr == ':')
+ pj_scan_get_char(scanner);
+
+ /* get value */
+ if (*scanner->curptr != '\r' && *scanner->curptr != '\n') {
+ pj_scan_get_until_chr(scanner, "\r\n", &attr->value);
+ } else {
+ attr->value.ptr = NULL;
+ attr->value.slen = 0;
+ }
+
+ } else {
+ attr->value.ptr = NULL;
+ attr->value.slen = 0;
+ }
+
+ /* We've got what we're looking for, skip anything until newline */
+ pj_scan_skip_line(scanner);
+
+ return attr;
+}
+
+
+/*
+ * Apply direction attribute in session to all media.
+ */
+static void apply_media_direction(pjmedia_sdp_session *sdp)
+{
+ pjmedia_sdp_attr *dir_attr = NULL;
+ unsigned i;
+
+ const pj_str_t inactive = { "inactive", 8 };
+ const pj_str_t sendonly = { "sendonly", 8 };
+ const pj_str_t recvonly = { "recvonly", 8 };
+ const pj_str_t sendrecv = { "sendrecv", 8 };
+
+ /* Find direction attribute in session, don't need to find default
+ * direction "sendrecv".
+ */
+ for (i = 0; i < sdp->attr_count && !dir_attr; ++i) {
+ if (!pj_strcmp(&sdp->attr[i]->name, &sendonly) ||
+ !pj_strcmp(&sdp->attr[i]->name, &recvonly) ||
+ !pj_strcmp(&sdp->attr[i]->name, &inactive))
+ {
+ dir_attr = sdp->attr[i];
+ }
+ }
+
+ /* Found the direction attribute */
+ if (dir_attr) {
+ /* Remove the direction attribute in session */
+ pjmedia_sdp_attr_remove(&sdp->attr_count, sdp->attr, dir_attr);
+
+ /* Apply the direction attribute to all media, but not overriding it
+ * if media already has direction attribute.
+ */
+ for (i = 0; i < sdp->media_count; ++i) {
+ pjmedia_sdp_media *m;
+ unsigned j;
+
+ /* Find direction attribute in this media */
+ m = sdp->media[i];
+ for (j = 0; j < m->attr_count; ++j) {
+ if (!pj_strcmp(&m->attr[j]->name, &sendrecv) ||
+ !pj_strcmp(&m->attr[j]->name, &sendonly) ||
+ !pj_strcmp(&m->attr[j]->name, &recvonly) ||
+ !pj_strcmp(&m->attr[j]->name, &inactive))
+ {
+ break;
+ }
+ }
+
+ /* Not found, apply direction attribute from session */
+ if (j == m->attr_count)
+ pjmedia_sdp_media_add_attr(m, dir_attr);
+ }
+ }
+}
+
+
+/*
+ * Parse SDP message.
+ */
+PJ_DEF(pj_status_t) pjmedia_sdp_parse( pj_pool_t *pool,
+ char *buf, pj_size_t len,
+ pjmedia_sdp_session **p_sdp)
+{
+ pj_scanner scanner;
+ pjmedia_sdp_session *session;
+ pjmedia_sdp_media *media = NULL;
+ pjmedia_sdp_attr *attr;
+ pjmedia_sdp_conn *conn;
+ pjmedia_sdp_bandw *bandw;
+ pj_str_t dummy;
+ int cur_name = 254;
+ parse_context ctx;
+ PJ_USE_EXCEPTION;
+
+ ctx.last_error = PJ_SUCCESS;
+
+ init_sdp_parser();
+
+ pj_scan_init(&scanner, buf, len, 0, &on_scanner_error);
+ session = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_session);
+ PJ_ASSERT_RETURN(session != NULL, PJ_ENOMEM);
+
+ /* Ignore leading newlines */
+ while (*scanner.curptr=='\r' || *scanner.curptr=='\n')
+ pj_scan_get_char(&scanner);
+
+ PJ_TRY {
+ while (!pj_scan_is_eof(&scanner)) {
+ cur_name = *scanner.curptr;
+ switch (cur_name) {
+ case 'a':
+ attr = parse_attr(pool, &scanner, &ctx);
+ if (attr) {
+ if (media) {
+ pjmedia_sdp_media_add_attr(media, attr);
+ } else {
+ pjmedia_sdp_session_add_attr(session, attr);
+ }
+ }
+ break;
+ case 'o':
+ parse_origin(&scanner, session, &ctx);
+ break;
+ case 's':
+ parse_generic_line(&scanner, &session->name, &ctx);
+ break;
+ case 'c':
+ conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
+ parse_connection_info(&scanner, conn, &ctx);
+ if (media) {
+ media->conn = conn;
+ } else {
+ session->conn = conn;
+ }
+ break;
+ case 't':
+ parse_time(&scanner, session, &ctx);
+ break;
+ case 'm':
+ media = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
+ parse_media(&scanner, media, &ctx);
+ if (session->media_count < PJMEDIA_MAX_SDP_MEDIA)
+ session->media[ session->media_count++ ] = media;
+ else
+ PJ_PERROR(2,(THIS_FILE, PJ_ETOOMANY,
+ "Error adding media, media is ignored"));
+ break;
+ case 'v':
+ parse_version(&scanner, &ctx);
+ break;
+ case 13:
+ case 10:
+ pj_scan_get_char(&scanner);
+ /* Allow empty newlines at the end of the message */
+ while (!pj_scan_is_eof(&scanner)) {
+ if (*scanner.curptr != 13 && *scanner.curptr != 10) {
+ ctx.last_error = PJMEDIA_SDP_EINSDP;
+ on_scanner_error(&scanner);
+ }
+ pj_scan_get_char(&scanner);
+ }
+ break;
+ case 'b':
+ bandw = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_bandw);
+ parse_bandwidth_info(&scanner, bandw, &ctx);
+ if (media) {
+ media->bandw[media->bandw_count++] = bandw;
+ } else {
+ session->bandw[session->bandw_count++] = bandw;
+ }
+ break;
+ default:
+ if (cur_name >= 'a' && cur_name <= 'z')
+ parse_generic_line(&scanner, &dummy, &ctx);
+ else {
+ ctx.last_error = PJMEDIA_SDP_EINSDP;
+ on_scanner_error(&scanner);
+ }
+ break;
+ }
+ }
+
+ ctx.last_error = PJ_SUCCESS;
+
+ }
+ PJ_CATCH_ANY {
+
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(ctx.last_error, errmsg, sizeof(errmsg));
+
+ PJ_LOG(4, (THIS_FILE, "Error parsing SDP in line %d col %d: %s",
+ scanner.line, pj_scan_get_col(&scanner),
+ errmsg));
+
+ session = NULL;
+
+ pj_assert(ctx.last_error != PJ_SUCCESS);
+ }
+ PJ_END;
+
+ pj_scan_fini(&scanner);
+
+ if (session)
+ apply_media_direction(session);
+
+ *p_sdp = session;
+ return ctx.last_error;
+}
+
+/*
+ * Print SDP description.
+ */
+PJ_DEF(int) pjmedia_sdp_print( const pjmedia_sdp_session *desc,
+ char *buf, pj_size_t size)
+{
+ return print_session(desc, buf, size);
+}
+
+
+/*
+ * Clone session
+ */
+PJ_DEF(pjmedia_sdp_session*) pjmedia_sdp_session_clone( pj_pool_t *pool,
+ const pjmedia_sdp_session *rhs)
+{
+ pjmedia_sdp_session *sess;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(pool && rhs, NULL);
+
+ sess = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_session);
+ PJ_ASSERT_RETURN(sess != NULL, NULL);
+
+ /* Clone origin line. */
+ pj_strdup(pool, &sess->origin.user, &rhs->origin.user);
+ sess->origin.id = rhs->origin.id;
+ sess->origin.version = rhs->origin.version;
+ pj_strdup(pool, &sess->origin.net_type, &rhs->origin.net_type);
+ pj_strdup(pool, &sess->origin.addr_type, &rhs->origin.addr_type);
+ pj_strdup(pool, &sess->origin.addr, &rhs->origin.addr);
+
+ /* Clone subject line. */
+ pj_strdup(pool, &sess->name, &rhs->name);
+
+ /* Clone connection line */
+ if (rhs->conn) {
+ sess->conn = pjmedia_sdp_conn_clone(pool, rhs->conn);
+ PJ_ASSERT_RETURN(sess->conn != NULL, NULL);
+ }
+
+ /* Duplicate bandwidth info */
+ sess->bandw_count = rhs->bandw_count;
+ for (i=0; i<rhs->bandw_count; ++i) {
+ sess->bandw[i] = pjmedia_sdp_bandw_clone(pool, rhs->bandw[i]);
+ }
+
+ /* Clone time line. */
+ sess->time.start = rhs->time.start;
+ sess->time.stop = rhs->time.stop;
+
+ /* Duplicate session attributes. */
+ sess->attr_count = rhs->attr_count;
+ for (i=0; i<rhs->attr_count; ++i) {
+ sess->attr[i] = pjmedia_sdp_attr_clone(pool, rhs->attr[i]);
+ }
+
+ /* Duplicate media descriptors. */
+ sess->media_count = rhs->media_count;
+ for (i=0; i<rhs->media_count; ++i) {
+ sess->media[i] = pjmedia_sdp_media_clone(pool, rhs->media[i]);
+ }
+
+ return sess;
+}
+
+
+#define CHECK(exp,ret) do { \
+ /*pj_assert(exp);*/ \
+ if (!(exp)) \
+ return ret; \
+ } while (0)
+
+/* Validate SDP connetion info. */
+static pj_status_t validate_sdp_conn(const pjmedia_sdp_conn *c)
+{
+ CHECK( c, PJ_EINVAL);
+ CHECK( pj_strcmp2(&c->net_type, "IN")==0, PJMEDIA_SDP_EINCONN);
+ CHECK( pj_strcmp2(&c->addr_type, "IP4")==0 ||
+ pj_strcmp2(&c->addr_type, "IP6")==0,
+ PJMEDIA_SDP_EINCONN);
+ CHECK( c->addr.slen != 0, PJMEDIA_SDP_EINCONN);
+
+ return PJ_SUCCESS;
+}
+
+
+/* Validate SDP session descriptor. */
+PJ_DEF(pj_status_t) pjmedia_sdp_validate(const pjmedia_sdp_session *sdp)
+{
+ unsigned i;
+ const pj_str_t STR_RTPMAP = { "rtpmap", 6 };
+
+ CHECK( sdp != NULL, PJ_EINVAL);
+
+ /* Validate origin line. */
+ CHECK( sdp->origin.user.slen != 0, PJMEDIA_SDP_EINORIGIN);
+ CHECK( pj_strcmp2(&sdp->origin.net_type, "IN")==0,
+ PJMEDIA_SDP_EINORIGIN);
+ CHECK( pj_strcmp2(&sdp->origin.addr_type, "IP4")==0 ||
+ pj_strcmp2(&sdp->origin.addr_type, "IP6")==0,
+ PJMEDIA_SDP_EINORIGIN);
+ CHECK( sdp->origin.addr.slen != 0, PJMEDIA_SDP_EINORIGIN);
+
+ /* Validate subject line. */
+ CHECK( sdp->name.slen != 0, PJMEDIA_SDP_EINNAME);
+
+ /* Ignore start and stop time. */
+
+ /* If session level connection info is present, validate it. */
+ if (sdp->conn) {
+ pj_status_t status = validate_sdp_conn(sdp->conn);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /* Validate each media. */
+ for (i=0; i<sdp->media_count; ++i) {
+ const pjmedia_sdp_media *m = sdp->media[i];
+ unsigned j;
+
+ /* Validate the m= line. */
+ CHECK( m->desc.media.slen != 0, PJMEDIA_SDP_EINMEDIA);
+ CHECK( m->desc.transport.slen != 0, PJMEDIA_SDP_EINMEDIA);
+ CHECK( m->desc.fmt_count != 0 || m->desc.port==0, PJMEDIA_SDP_ENOFMT);
+
+ /* If media level connection info is present, validate it. */
+ if (m->conn) {
+ pj_status_t status = validate_sdp_conn(m->conn);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /* If media doesn't have connection info, then connection info
+ * must be present in the session.
+ */
+ if (m->conn == NULL) {
+ if (sdp->conn == NULL)
+ return PJMEDIA_SDP_EMISSINGCONN;
+ }
+
+ /* Verify payload type. */
+ for (j=0; j<m->desc.fmt_count; ++j) {
+
+ /* Arrgh noo!! Payload type can be non-numeric!!
+ * RTC based programs sends "null" for instant messaging!
+ */
+ if (pj_isdigit(*m->desc.fmt[j].ptr)) {
+ unsigned pt = pj_strtoul(&m->desc.fmt[j]);
+
+ /* Payload type is between 0 and 127.
+ */
+ CHECK( pt <= 127, PJMEDIA_SDP_EINPT);
+
+ /* If port is not zero, then for each dynamic payload type, an
+ * rtpmap attribute must be specified.
+ */
+ if (m->desc.port != 0 && pt >= 96) {
+ const pjmedia_sdp_attr *a;
+
+ a = pjmedia_sdp_media_find_attr(m, &STR_RTPMAP,
+ &m->desc.fmt[j]);
+ CHECK( a != NULL, PJMEDIA_SDP_EMISSINGRTPMAP);
+ }
+ }
+ }
+ }
+
+ /* Looks good. */
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_sdp_transport_cmp( const pj_str_t *t1,
+ const pj_str_t *t2)
+{
+ static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 };
+ static const pj_str_t ID_RTP_SAVP = { "RTP/SAVP", 8 };
+
+ /* Exactly equal? */
+ if (pj_stricmp(t1, t2) == 0)
+ return PJ_SUCCESS;
+
+ /* Compatible? */
+ if ((!pj_stricmp(t1, &ID_RTP_AVP) || !pj_stricmp(t1, &ID_RTP_SAVP)) &&
+ (!pj_stricmp(t2, &ID_RTP_AVP) || !pj_stricmp(t2, &ID_RTP_SAVP)))
+ return PJ_SUCCESS;
+
+ return PJMEDIA_SDP_ETPORTNOTEQUAL;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_sdp_media_deactivate(pj_pool_t *pool,
+ pjmedia_sdp_media *m)
+{
+ PJ_ASSERT_RETURN(m, PJ_EINVAL);
+ PJ_UNUSED_ARG(pool);
+
+ /* Set port to zero */
+ m->desc.port = 0;
+
+ /* And remove attributes */
+ m->attr_count = 0;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pjmedia_sdp_media*) pjmedia_sdp_media_clone_deactivate(
+ pj_pool_t *pool,
+ const pjmedia_sdp_media *rhs)
+{
+ unsigned int i;
+ pjmedia_sdp_media *m;
+
+ PJ_ASSERT_RETURN(pool && rhs, NULL);
+
+ m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
+ pj_memcpy(m, rhs, sizeof(*m));
+
+ /* Clone the media line only */
+ pj_strdup (pool, &m->desc.media, &rhs->desc.media);
+ pj_strdup (pool, &m->desc.transport, &rhs->desc.transport);
+ for (i=0; i<rhs->desc.fmt_count; ++i)
+ pj_strdup(pool, &m->desc.fmt[i], &rhs->desc.fmt[i]);
+
+ if (rhs->conn) {
+ m->conn = pjmedia_sdp_conn_clone (pool, rhs->conn);
+ PJ_ASSERT_RETURN(m->conn != NULL, NULL);
+ }
+
+ m->bandw_count = rhs->bandw_count;
+ for (i=0; i < rhs->bandw_count; ++i) {
+ m->bandw[i] = pjmedia_sdp_bandw_clone (pool, rhs->bandw[i]);
+ PJ_ASSERT_RETURN(m->bandw[i] != NULL, NULL);
+ }
+
+ /* And deactivate it */
+ pjmedia_sdp_media_deactivate(pool, m);
+
+ return m;
+}
diff --git a/pjmedia/src/pjmedia/sdp_cmp.c b/pjmedia/src/pjmedia/sdp_cmp.c
new file mode 100644
index 0000000..f75df85
--- /dev/null
+++ b/pjmedia/src/pjmedia/sdp_cmp.c
@@ -0,0 +1,304 @@
+/* $Id: sdp_cmp.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/sdp.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/string.h>
+
+
+/* Compare connection line. */
+static pj_status_t compare_conn(const pjmedia_sdp_conn *c1,
+ const pjmedia_sdp_conn *c2)
+{
+ /* Compare network type. */
+ if (pj_strcmp(&c1->net_type, &c2->net_type) != 0)
+ return PJMEDIA_SDP_ECONNNOTEQUAL;
+
+ /* Compare address type. */
+ if (pj_strcmp(&c1->addr_type, &c2->addr_type) != 0)
+ return PJMEDIA_SDP_ECONNNOTEQUAL;
+
+ /* Compare address. */
+ if (pj_strcmp(&c1->addr, &c2->addr) != 0)
+ return PJMEDIA_SDP_ECONNNOTEQUAL;
+
+ return PJ_SUCCESS;
+}
+
+/* Compare attributes array. */
+static pj_status_t compare_attr_imp(unsigned count1,
+ pjmedia_sdp_attr *const attr1[],
+ unsigned count2,
+ pjmedia_sdp_attr *const attr2[])
+{
+ pj_status_t status;
+ unsigned i;
+ const pj_str_t inactive = { "inactive", 8 };
+ const pj_str_t sendrecv = { "sendrecv", 8 };
+ const pj_str_t sendonly = { "sendonly", 8 };
+ const pj_str_t recvonly = { "recvonly", 8 };
+ const pj_str_t fmtp = { "fmtp", 4 };
+ const pj_str_t rtpmap = { "rtpmap", 6 };
+
+ /* For simplicity, we only compare the following attributes, and ignore
+ * the others:
+ * - direction, eg. inactive, sendonly, recvonly, sendrecv
+ * - fmtp for each payload.
+ * - rtpmap for each payload.
+ */
+ for (i=0; i<count1; ++i) {
+ const pjmedia_sdp_attr *a1 = attr1[i];
+
+ if (pj_strcmp(&a1->name, &inactive) == 0 ||
+ pj_strcmp(&a1->name, &sendrecv) == 0 ||
+ pj_strcmp(&a1->name, &sendonly) == 0 ||
+ pj_strcmp(&a1->name, &recvonly) == 0)
+ {
+ /* For inactive, sendrecv, sendonly, and recvonly attributes,
+ * the same attribute must be present on the other SDP.
+ */
+ const pjmedia_sdp_attr *a2;
+ a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, NULL);
+ if (!a2)
+ return PJMEDIA_SDP_EDIRNOTEQUAL;
+
+ } else if (pj_strcmp(&a1->name, &fmtp) == 0) {
+ /* For fmtp attribute, find the fmtp attribute in the other SDP
+ * for the same payload type, and compare the fmtp param/value.
+ */
+ pjmedia_sdp_fmtp fmtp1, fmtp2;
+ const pjmedia_sdp_attr *a2;
+
+ status = pjmedia_sdp_attr_get_fmtp(a1, &fmtp1);
+ if (status != PJ_SUCCESS)
+ return PJMEDIA_SDP_EFMTPNOTEQUAL;
+
+ a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, &fmtp1.fmt);
+ if (!a2)
+ return PJMEDIA_SDP_EFMTPNOTEQUAL;
+
+ status = pjmedia_sdp_attr_get_fmtp(a2, &fmtp2);
+ if (status != PJ_SUCCESS)
+ return PJMEDIA_SDP_EFMTPNOTEQUAL;
+
+ if (pj_strcmp(&fmtp1.fmt_param, &fmtp2.fmt_param) != 0)
+ return PJMEDIA_SDP_EFMTPNOTEQUAL;
+
+ } else if (pj_strcmp(&a1->name, &rtpmap) == 0) {
+ /* For rtpmap attribute, find rtpmap attribute on the other SDP
+ * for the same payload type, and compare both rtpmap atribute
+ * values.
+ */
+ pjmedia_sdp_rtpmap r1, r2;
+ const pjmedia_sdp_attr *a2;
+
+ status = pjmedia_sdp_attr_get_rtpmap(a1, &r1);
+ if (status != PJ_SUCCESS)
+ return PJMEDIA_SDP_ERTPMAPNOTEQUAL;
+
+ a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, &r1.pt);
+ if (!a2)
+ return PJMEDIA_SDP_ERTPMAPNOTEQUAL;
+
+ status = pjmedia_sdp_attr_get_rtpmap(a2, &r2);
+ if (status != PJ_SUCCESS)
+ return PJMEDIA_SDP_ERTPMAPNOTEQUAL;
+
+ if (pj_strcmp(&r1.pt, &r2.pt) != 0)
+ return PJMEDIA_SDP_ERTPMAPNOTEQUAL;
+ if (pj_strcmp(&r1.enc_name, &r2.enc_name) != 0)
+ return PJMEDIA_SDP_ERTPMAPNOTEQUAL;
+ if (r1.clock_rate != r2.clock_rate)
+ return PJMEDIA_SDP_ERTPMAPNOTEQUAL;
+ if (pj_strcmp(&r1.param, &r2.param) != 0)
+ return PJMEDIA_SDP_ERTPMAPNOTEQUAL;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* Compare attributes array. */
+static pj_status_t compare_attr(unsigned count1,
+ pjmedia_sdp_attr *const attr1[],
+ unsigned count2,
+ pjmedia_sdp_attr *const attr2[])
+{
+ pj_status_t status;
+
+ status = compare_attr_imp(count1, attr1, count2, attr2);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = compare_attr_imp(count2, attr2, count1, attr1);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ return PJ_SUCCESS;
+}
+
+/* Compare media descriptor */
+PJ_DEF(pj_status_t) pjmedia_sdp_media_cmp( const pjmedia_sdp_media *sd1,
+ const pjmedia_sdp_media *sd2,
+ unsigned option)
+{
+ unsigned i;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(sd1 && sd2 && option==0, PJ_EINVAL);
+
+ PJ_UNUSED_ARG(option);
+
+ /* Compare media type. */
+ if (pj_strcmp(&sd1->desc.media, &sd2->desc.media) != 0)
+ return PJMEDIA_SDP_EMEDIANOTEQUAL;
+
+ /* Compare port number. */
+ if (sd1->desc.port != sd2->desc.port)
+ return PJMEDIA_SDP_EPORTNOTEQUAL;
+
+ /* Compare port count. */
+ if (sd1->desc.port_count != sd2->desc.port_count)
+ return PJMEDIA_SDP_EPORTNOTEQUAL;
+
+ /* Compare transports. */
+ if (pj_strcmp(&sd1->desc.transport, &sd2->desc.transport) != 0)
+ return PJMEDIA_SDP_ETPORTNOTEQUAL;
+
+ /* For zeroed port media, stop comparing here */
+ if (sd1->desc.port == 0)
+ return PJ_SUCCESS;
+
+ /* Compare number of formats. */
+ if (sd1->desc.fmt_count != sd2->desc.fmt_count)
+ return PJMEDIA_SDP_EFORMATNOTEQUAL;
+
+ /* Compare formats, in order. */
+ for (i=0; i<sd1->desc.fmt_count; ++i) {
+ if (pj_strcmp(&sd1->desc.fmt[i], &sd2->desc.fmt[i]) != 0)
+ return PJMEDIA_SDP_EFORMATNOTEQUAL;
+ }
+
+ /* Compare connection line, if they exist. */
+ if (sd1->conn) {
+ if (!sd2->conn)
+ return PJMEDIA_SDP_EMEDIANOTEQUAL;
+ status = compare_conn(sd1->conn, sd2->conn);
+ } else {
+ if (sd2->conn)
+ return PJMEDIA_SDP_EMEDIANOTEQUAL;
+ }
+
+ /* Compare attributes. */
+ status = compare_attr(sd1->attr_count, sd1->attr,
+ sd2->attr_count, sd2->attr);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Looks equal */
+ return PJ_SUCCESS;
+}
+
+/*
+ * Compare two SDP session for equality.
+ */
+PJ_DEF(pj_status_t) pjmedia_sdp_session_cmp( const pjmedia_sdp_session *sd1,
+ const pjmedia_sdp_session *sd2,
+ unsigned option)
+{
+ unsigned i;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(sd1 && sd2 && option==0, PJ_EINVAL);
+
+ PJ_UNUSED_ARG(option);
+
+ /* Compare the origin line. */
+ if (pj_strcmp(&sd1->origin.user, &sd2->origin.user) != 0)
+ return PJMEDIA_SDP_EORIGINNOTEQUAL;
+
+ if (sd1->origin.id != sd2->origin.id)
+ return PJMEDIA_SDP_EORIGINNOTEQUAL;
+
+ if (sd1->origin.version != sd2->origin.version)
+ return PJMEDIA_SDP_EORIGINNOTEQUAL;
+
+ if (pj_strcmp(&sd1->origin.net_type, &sd2->origin.net_type) != 0)
+ return PJMEDIA_SDP_EORIGINNOTEQUAL;
+
+ if (pj_strcmp(&sd1->origin.addr_type, &sd2->origin.addr_type) != 0)
+ return PJMEDIA_SDP_EORIGINNOTEQUAL;
+
+ if (pj_strcmp(&sd1->origin.addr, &sd2->origin.addr) != 0)
+ return PJMEDIA_SDP_EORIGINNOTEQUAL;
+
+
+ /* Compare the subject line. */
+ if (pj_strcmp(&sd1->name, &sd2->name) != 0)
+ return PJMEDIA_SDP_ENAMENOTEQUAL;
+
+ /* Compare connection line, when they exist */
+ if (sd1->conn) {
+ if (!sd2->conn)
+ return PJMEDIA_SDP_ECONNNOTEQUAL;
+ status = compare_conn(sd1->conn, sd2->conn);
+ if (status != PJ_SUCCESS)
+ return status;
+ } else {
+ if (sd2->conn)
+ return PJMEDIA_SDP_ECONNNOTEQUAL;
+ }
+
+ /* Compare time line. */
+ if (sd1->time.start != sd2->time.start)
+ return PJMEDIA_SDP_ETIMENOTEQUAL;
+
+ if (sd1->time.stop != sd2->time.stop)
+ return PJMEDIA_SDP_ETIMENOTEQUAL;
+
+ /* Compare attributes. */
+ status = compare_attr(sd1->attr_count, sd1->attr,
+ sd2->attr_count, sd2->attr);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Compare media lines. */
+ if (sd1->media_count != sd2->media_count)
+ return PJMEDIA_SDP_EMEDIANOTEQUAL;
+
+ for (i=0; i<sd1->media_count; ++i) {
+ status = pjmedia_sdp_media_cmp(sd1->media[i], sd2->media[i], 0);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /* Looks equal. */
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_sdp_conn_cmp(const pjmedia_sdp_conn *conn1,
+ const pjmedia_sdp_conn *conn2,
+ unsigned option)
+{
+ PJ_UNUSED_ARG(option);
+ return compare_conn(conn1, conn2);
+}
diff --git a/pjmedia/src/pjmedia/sdp_neg.c b/pjmedia/src/pjmedia/sdp_neg.c
new file mode 100644
index 0000000..bdce18d
--- /dev/null
+++ b/pjmedia/src/pjmedia/sdp_neg.c
@@ -0,0 +1,1549 @@
+/* $Id: sdp_neg.c 3980 2012-03-20 09:23:20Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/sdp_neg.h>
+#include <pjmedia/sdp.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/ctype.h>
+#include <pj/array.h>
+
+/**
+ * This structure describes SDP media negotiator.
+ */
+struct pjmedia_sdp_neg
+{
+ pjmedia_sdp_neg_state state; /**< Negotiator state. */
+ pj_bool_t prefer_remote_codec_order;
+ pj_bool_t has_remote_answer;
+ pj_bool_t answer_was_remote;
+
+ pjmedia_sdp_session *initial_sdp, /**< Initial local SDP */
+ *active_local_sdp, /**< Currently active local SDP. */
+ *active_remote_sdp, /**< Currently active remote's. */
+ *neg_local_sdp, /**< Temporary local SDP. */
+ *neg_remote_sdp; /**< Temporary remote SDP. */
+};
+
+static const char *state_str[] =
+{
+ "STATE_NULL",
+ "STATE_LOCAL_OFFER",
+ "STATE_REMOTE_OFFER",
+ "STATE_WAIT_NEGO",
+ "STATE_DONE",
+};
+
+/* Definition of customized SDP format negotiation callback */
+struct fmt_match_cb_t
+{
+ pj_str_t fmt_name;
+ pjmedia_sdp_neg_fmt_match_cb cb;
+};
+
+/* Number of registered customized SDP format negotiation callbacks */
+static unsigned fmt_match_cb_cnt;
+
+/* The registered customized SDP format negotiation callbacks */
+static struct fmt_match_cb_t
+ fmt_match_cb[PJMEDIA_SDP_NEG_MAX_CUSTOM_FMT_NEG_CB];
+
+/* Redefining a very long identifier name, just for convenience */
+#define ALLOW_MODIFY_ANSWER PJMEDIA_SDP_NEG_FMT_MATCH_ALLOW_MODIFY_ANSWER
+
+static pj_status_t custom_fmt_match( pj_pool_t *pool,
+ const pj_str_t *fmt_name,
+ pjmedia_sdp_media *offer,
+ unsigned o_fmt_idx,
+ pjmedia_sdp_media *answer,
+ unsigned a_fmt_idx,
+ unsigned option);
+
+
+/*
+ * Get string representation of negotiator state.
+ */
+PJ_DEF(const char*) pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_state state)
+{
+ if (state >=0 && state < (pjmedia_sdp_neg_state)PJ_ARRAY_SIZE(state_str))
+ return state_str[state];
+
+ return "<?UNKNOWN?>";
+}
+
+
+/*
+ * Create with local offer.
+ */
+PJ_DEF(pj_status_t) pjmedia_sdp_neg_create_w_local_offer( pj_pool_t *pool,
+ const pjmedia_sdp_session *local,
+ pjmedia_sdp_neg **p_neg)
+{
+ pjmedia_sdp_neg *neg;
+ pj_status_t status;
+
+ /* Check arguments are valid. */
+ PJ_ASSERT_RETURN(pool && local && p_neg, PJ_EINVAL);
+
+ *p_neg = NULL;
+
+ /* Validate local offer. */
+ PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(local))==PJ_SUCCESS, status);
+
+ /* Create and initialize negotiator. */
+ neg = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_neg);
+ PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM);
+
+ neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER;
+ neg->prefer_remote_codec_order = PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER;
+ neg->initial_sdp = pjmedia_sdp_session_clone(pool, local);
+ neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local);
+
+ *p_neg = neg;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Create with remote offer and initial local offer/answer.
+ */
+PJ_DEF(pj_status_t) pjmedia_sdp_neg_create_w_remote_offer(pj_pool_t *pool,
+ const pjmedia_sdp_session *initial,
+ const pjmedia_sdp_session *remote,
+ pjmedia_sdp_neg **p_neg)
+{
+ pjmedia_sdp_neg *neg;
+ pj_status_t status;
+
+ /* Check arguments are valid. */
+ PJ_ASSERT_RETURN(pool && remote && p_neg, PJ_EINVAL);
+
+ *p_neg = NULL;
+
+ /* Validate remote offer and initial answer */
+ status = pjmedia_sdp_validate(remote);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Create and initialize negotiator. */
+ neg = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_neg);
+ PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM);
+
+ neg->prefer_remote_codec_order = PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER;
+ neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote);
+
+ if (initial) {
+ PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(initial))==PJ_SUCCESS,
+ status);
+
+ neg->initial_sdp = pjmedia_sdp_session_clone(pool, initial);
+ neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, initial);
+
+ neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO;
+
+ } else {
+
+ neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER;
+
+ }
+
+ *p_neg = neg;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Set codec order preference.
+ */
+PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_prefer_remote_codec_order(
+ pjmedia_sdp_neg *neg,
+ pj_bool_t prefer_remote)
+{
+ PJ_ASSERT_RETURN(neg, PJ_EINVAL);
+ neg->prefer_remote_codec_order = prefer_remote;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get SDP negotiator state.
+ */
+PJ_DEF(pjmedia_sdp_neg_state) pjmedia_sdp_neg_get_state( pjmedia_sdp_neg *neg )
+{
+ /* Check arguments are valid. */
+ PJ_ASSERT_RETURN(neg != NULL, PJMEDIA_SDP_NEG_STATE_NULL);
+ return neg->state;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_active_local( pjmedia_sdp_neg *neg,
+ const pjmedia_sdp_session **local)
+{
+ PJ_ASSERT_RETURN(neg && local, PJ_EINVAL);
+ PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE);
+
+ *local = neg->active_local_sdp;
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_active_remote( pjmedia_sdp_neg *neg,
+ const pjmedia_sdp_session **remote)
+{
+ PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL);
+ PJ_ASSERT_RETURN(neg->active_remote_sdp, PJMEDIA_SDPNEG_ENOACTIVE);
+
+ *remote = neg->active_remote_sdp;
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_bool_t) pjmedia_sdp_neg_was_answer_remote(pjmedia_sdp_neg *neg)
+{
+ PJ_ASSERT_RETURN(neg, PJ_FALSE);
+
+ return neg->answer_was_remote;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_neg_remote( pjmedia_sdp_neg *neg,
+ const pjmedia_sdp_session **remote)
+{
+ PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL);
+ PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJMEDIA_SDPNEG_ENONEG);
+
+ *remote = neg->neg_remote_sdp;
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_neg_local( pjmedia_sdp_neg *neg,
+ const pjmedia_sdp_session **local)
+{
+ PJ_ASSERT_RETURN(neg && local, PJ_EINVAL);
+ PJ_ASSERT_RETURN(neg->neg_local_sdp, PJMEDIA_SDPNEG_ENONEG);
+
+ *local = neg->neg_local_sdp;
+ return PJ_SUCCESS;
+}
+
+static pjmedia_sdp_media *sdp_media_clone_deactivate(
+ pj_pool_t *pool,
+ const pjmedia_sdp_media *rem_med,
+ const pjmedia_sdp_media *local_med,
+ const pjmedia_sdp_session *local_sess)
+{
+ pjmedia_sdp_media *res;
+
+ res = pjmedia_sdp_media_clone_deactivate(pool, rem_med);
+ if (!res)
+ return NULL;
+
+ if (!res->conn && (!local_sess || !local_sess->conn)) {
+ if (local_med && local_med->conn)
+ res->conn = pjmedia_sdp_conn_clone(pool, local_med->conn);
+ else {
+ res->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
+ res->conn->net_type = pj_str("IN");
+ res->conn->addr_type = pj_str("IP4");
+ res->conn->addr = pj_str("127.0.0.1");
+ }
+ }
+
+ return res;
+}
+
+/*
+ * Modify local SDP and wait for remote answer.
+ */
+PJ_DEF(pj_status_t) pjmedia_sdp_neg_modify_local_offer( pj_pool_t *pool,
+ pjmedia_sdp_neg *neg,
+ const pjmedia_sdp_session *local)
+{
+ pjmedia_sdp_session *new_offer;
+ pjmedia_sdp_session *old_offer;
+ char media_used[PJMEDIA_MAX_SDP_MEDIA];
+ unsigned oi; /* old offer media index */
+ pj_status_t status;
+
+ /* Check arguments are valid. */
+ PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL);
+
+ /* Can only do this in STATE_DONE. */
+ PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE,
+ PJMEDIA_SDPNEG_EINSTATE);
+
+ /* Validate the new offer */
+ status = pjmedia_sdp_validate(local);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Change state to STATE_LOCAL_OFFER */
+ neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER;
+
+ /* Init vars */
+ pj_bzero(media_used, sizeof(media_used));
+ old_offer = neg->active_local_sdp;
+ new_offer = pjmedia_sdp_session_clone(pool, local);
+
+ /* RFC 3264 Section 8: When issuing an offer that modifies the session,
+ * the "o=" line of the new SDP MUST be identical to that in the
+ * previous SDP, except that the version in the origin field MUST
+ * increment by one from the previous SDP.
+ */
+ pj_strdup(pool, &new_offer->origin.user, &old_offer->origin.user);
+ new_offer->origin.id = old_offer->origin.id;
+ new_offer->origin.version = old_offer->origin.version + 1;
+ pj_strdup(pool, &new_offer->origin.net_type, &old_offer->origin.net_type);
+ pj_strdup(pool, &new_offer->origin.addr_type,&old_offer->origin.addr_type);
+ pj_strdup(pool, &new_offer->origin.addr, &old_offer->origin.addr);
+
+ /* Generating the new offer, in the case media lines doesn't match the
+ * active SDP (e.g. current/active SDP's have m=audio and m=video lines,
+ * and the new offer only has m=audio line), the negotiator will fix
+ * the new offer by reordering and adding the missing media line with
+ * port number set to zero.
+ */
+ for (oi = 0; oi < old_offer->media_count; ++oi) {
+ pjmedia_sdp_media *om;
+ pjmedia_sdp_media *nm;
+ unsigned ni; /* new offer media index */
+ pj_bool_t found = PJ_FALSE;
+
+ om = old_offer->media[oi];
+ for (ni = oi; ni < new_offer->media_count; ++ni) {
+ nm = new_offer->media[ni];
+ if (pj_strcmp(&nm->desc.media, &om->desc.media) == 0) {
+ if (ni != oi) {
+ /* The same media found but the position unmatched to the
+ * old offer, so let's put this media in the right place,
+ * and keep the order of the rest.
+ */
+ pj_array_insert(new_offer->media, /* array */
+ sizeof(new_offer->media[0]), /* elmt size*/
+ ni, /* count */
+ oi, /* pos */
+ &nm); /* new elmt */
+ }
+ found = PJ_TRUE;
+ break;
+ }
+ }
+ if (!found) {
+ pjmedia_sdp_media *m;
+
+ m = sdp_media_clone_deactivate(pool, om, om, local);
+
+ pj_array_insert(new_offer->media, sizeof(new_offer->media[0]),
+ new_offer->media_count++, oi, &m);
+ }
+ }
+
+ /* New_offer fixed */
+ neg->initial_sdp = new_offer;
+ neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, new_offer);
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_sdp_neg_send_local_offer( pj_pool_t *pool,
+ pjmedia_sdp_neg *neg,
+ const pjmedia_sdp_session **offer)
+{
+ /* Check arguments are valid. */
+ PJ_ASSERT_RETURN(neg && offer, PJ_EINVAL);
+
+ *offer = NULL;
+
+ /* Can only do this in STATE_DONE or STATE_LOCAL_OFFER. */
+ PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE ||
+ neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER,
+ PJMEDIA_SDPNEG_EINSTATE);
+
+ if (neg->state == PJMEDIA_SDP_NEG_STATE_DONE) {
+ /* If in STATE_DONE, set the active SDP as the offer. */
+ PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE);
+
+ neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER;
+ neg->neg_local_sdp = pjmedia_sdp_session_clone(pool,
+ neg->active_local_sdp);
+ *offer = neg->active_local_sdp;
+
+ } else {
+ /* We assume that we're in STATE_LOCAL_OFFER.
+ * In this case set the neg_local_sdp as the offer.
+ */
+ *offer = neg->neg_local_sdp;
+ }
+
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_remote_answer( pj_pool_t *pool,
+ pjmedia_sdp_neg *neg,
+ const pjmedia_sdp_session *remote)
+{
+ /* Check arguments are valid. */
+ PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL);
+
+ /* Can only do this in STATE_LOCAL_OFFER.
+ * If we haven't provided local offer, then rx_remote_offer() should
+ * be called instead of this function.
+ */
+ PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER,
+ PJMEDIA_SDPNEG_EINSTATE);
+
+ /* We're ready to negotiate. */
+ neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO;
+ neg->has_remote_answer = PJ_TRUE;
+ neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote);
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_remote_offer( pj_pool_t *pool,
+ pjmedia_sdp_neg *neg,
+ const pjmedia_sdp_session *remote)
+{
+ /* Check arguments are valid. */
+ PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL);
+
+ /* Can only do this in STATE_DONE.
+ * If we already provide local offer, then rx_remote_answer() should
+ * be called instead of this function.
+ */
+ PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE,
+ PJMEDIA_SDPNEG_EINSTATE);
+
+ /* State now is STATE_REMOTE_OFFER. */
+ neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER;
+ neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote);
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_local_answer( pj_pool_t *pool,
+ pjmedia_sdp_neg *neg,
+ const pjmedia_sdp_session *local)
+{
+ /* Check arguments are valid. */
+ PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL);
+
+ /* Can only do this in STATE_REMOTE_OFFER.
+ * If we already provide local offer, then rx_remote_answer() should
+ * be called instead of this function.
+ */
+ PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER,
+ PJMEDIA_SDPNEG_EINSTATE);
+
+ /* State now is STATE_WAIT_NEGO. */
+ neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO;
+ if (local) {
+ neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local);
+ if (neg->initial_sdp) {
+ /* I don't think there is anything in RFC 3264 that mandates
+ * answerer to place the same origin (and increment version)
+ * in the answer, but probably it won't hurt either.
+ * Note that the version will be incremented in
+ * pjmedia_sdp_neg_negotiate()
+ */
+ neg->neg_local_sdp->origin.id = neg->initial_sdp->origin.id;
+ } else {
+ neg->initial_sdp = pjmedia_sdp_session_clone(pool, local);
+ }
+ } else {
+ PJ_ASSERT_RETURN(neg->initial_sdp, PJMEDIA_SDPNEG_ENOINITIAL);
+ neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp);
+ }
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_bool_t) pjmedia_sdp_neg_has_local_answer(pjmedia_sdp_neg *neg)
+{
+ pj_assert(neg && neg->state==PJMEDIA_SDP_NEG_STATE_WAIT_NEGO);
+ return !neg->has_remote_answer;
+}
+
+
+/* Swap string. */
+static void str_swap(pj_str_t *str1, pj_str_t *str2)
+{
+ pj_str_t tmp = *str1;
+ *str1 = *str2;
+ *str2 = tmp;
+}
+
+static void remove_all_media_directions(pjmedia_sdp_media *m)
+{
+ pjmedia_sdp_media_remove_all_attr(m, "inactive");
+ pjmedia_sdp_media_remove_all_attr(m, "sendrecv");
+ pjmedia_sdp_media_remove_all_attr(m, "sendonly");
+ pjmedia_sdp_media_remove_all_attr(m, "recvonly");
+}
+
+/* Update media direction based on peer's media direction */
+static void update_media_direction(pj_pool_t *pool,
+ const pjmedia_sdp_media *remote,
+ pjmedia_sdp_media *local)
+{
+ pjmedia_dir old_dir = PJMEDIA_DIR_ENCODING_DECODING,
+ new_dir;
+
+ /* Get the media direction of local SDP */
+ if (pjmedia_sdp_media_find_attr2(local, "sendonly", NULL))
+ old_dir = PJMEDIA_DIR_ENCODING;
+ else if (pjmedia_sdp_media_find_attr2(local, "recvonly", NULL))
+ old_dir = PJMEDIA_DIR_DECODING;
+ else if (pjmedia_sdp_media_find_attr2(local, "inactive", NULL))
+ old_dir = PJMEDIA_DIR_NONE;
+
+ new_dir = old_dir;
+
+ /* Adjust local media direction based on remote media direction */
+ if (pjmedia_sdp_media_find_attr2(remote, "inactive", NULL) != NULL) {
+ /* If remote has "a=inactive", then local is inactive too */
+
+ new_dir = PJMEDIA_DIR_NONE;
+
+ } else if(pjmedia_sdp_media_find_attr2(remote, "sendonly", NULL) != NULL) {
+ /* If remote has "a=sendonly", then set local to "recvonly" if
+ * it is currently "sendrecv". Otherwise if local is NOT "recvonly",
+ * then set local direction to "inactive".
+ */
+ switch (old_dir) {
+ case PJMEDIA_DIR_ENCODING_DECODING:
+ new_dir = PJMEDIA_DIR_DECODING;
+ break;
+ case PJMEDIA_DIR_DECODING:
+ /* No change */
+ break;
+ default:
+ new_dir = PJMEDIA_DIR_NONE;
+ break;
+ }
+
+ } else if(pjmedia_sdp_media_find_attr2(remote, "recvonly", NULL) != NULL) {
+ /* If remote has "a=recvonly", then set local to "sendonly" if
+ * it is currently "sendrecv". Otherwise if local is NOT "sendonly",
+ * then set local direction to "inactive"
+ */
+
+ switch (old_dir) {
+ case PJMEDIA_DIR_ENCODING_DECODING:
+ new_dir = PJMEDIA_DIR_ENCODING;
+ break;
+ case PJMEDIA_DIR_ENCODING:
+ /* No change */
+ break;
+ default:
+ new_dir = PJMEDIA_DIR_NONE;
+ break;
+ }
+
+ } else {
+ /* Remote indicates "sendrecv" capability. No change to local
+ * direction
+ */
+ }
+
+ if (new_dir != old_dir) {
+ pjmedia_sdp_attr *a = NULL;
+
+ remove_all_media_directions(local);
+
+ switch (new_dir) {
+ case PJMEDIA_DIR_NONE:
+ a = pjmedia_sdp_attr_create(pool, "inactive", NULL);
+ break;
+ case PJMEDIA_DIR_ENCODING:
+ a = pjmedia_sdp_attr_create(pool, "sendonly", NULL);
+ break;
+ case PJMEDIA_DIR_DECODING:
+ a = pjmedia_sdp_attr_create(pool, "recvonly", NULL);
+ break;
+ default:
+ /* sendrecv */
+ break;
+ }
+
+ if (a) {
+ pjmedia_sdp_media_add_attr(local, a);
+ }
+ }
+}
+
+
+/* Update single local media description to after receiving answer
+ * from remote.
+ */
+static pj_status_t process_m_answer( pj_pool_t *pool,
+ pjmedia_sdp_media *offer,
+ pjmedia_sdp_media *answer,
+ pj_bool_t allow_asym)
+{
+ unsigned i;
+
+ /* Check that the media type match our offer. */
+
+ if (pj_strcmp(&answer->desc.media, &offer->desc.media)!=0) {
+ /* The media type in the answer is different than the offer! */
+ return PJMEDIA_SDPNEG_EINVANSMEDIA;
+ }
+
+
+ /* Check that transport in the answer match our offer. */
+
+ /* At this point, transport type must be compatible,
+ * the transport instance will do more validation later.
+ */
+ if (pjmedia_sdp_transport_cmp(&answer->desc.transport,
+ &offer->desc.transport)
+ != PJ_SUCCESS)
+ {
+ return PJMEDIA_SDPNEG_EINVANSTP;
+ }
+
+
+ /* Check if remote has rejected our offer */
+ if (answer->desc.port == 0) {
+
+ /* Remote has rejected our offer.
+ * Deactivate our media too.
+ */
+ pjmedia_sdp_media_deactivate(pool, offer);
+
+ /* Don't need to proceed */
+ return PJ_SUCCESS;
+ }
+
+ /* Ticket #1148: check if remote answer does not set port to zero when
+ * offered with port zero. Let's just tolerate it.
+ */
+ if (offer->desc.port == 0) {
+ /* Don't need to proceed */
+ return PJ_SUCCESS;
+ }
+
+ /* Process direction attributes */
+ update_media_direction(pool, answer, offer);
+
+ /* If asymetric media is allowed, then just check that remote answer has
+ * codecs that are within the offer.
+ *
+ * Otherwise if asymetric media is not allowed, then we will choose only
+ * one codec in our initial offer to match the answer.
+ */
+ if (allow_asym) {
+ for (i=0; i<answer->desc.fmt_count; ++i) {
+ unsigned j;
+ pj_str_t *rem_fmt = &answer->desc.fmt[i];
+
+ for (j=0; j<offer->desc.fmt_count; ++j) {
+ if (pj_strcmp(rem_fmt, &answer->desc.fmt[j])==0)
+ break;
+ }
+
+ if (j != offer->desc.fmt_count) {
+ /* Found at least one common codec. */
+ break;
+ }
+ }
+
+ if (i == answer->desc.fmt_count) {
+ /* No common codec in the answer! */
+ return PJMEDIA_SDPNEG_EANSNOMEDIA;
+ }
+
+ PJ_TODO(CHECK_SDP_NEGOTIATION_WHEN_ASYMETRIC_MEDIA_IS_ALLOWED);
+
+ } else {
+ /* Offer format priority based on answer format index/priority */
+ unsigned offer_fmt_prior[PJMEDIA_MAX_SDP_FMT];
+
+ /* Remove all format in the offer that has no matching answer */
+ for (i=0; i<offer->desc.fmt_count;) {
+ unsigned pt;
+ pj_uint32_t j;
+ pj_str_t *fmt = &offer->desc.fmt[i];
+
+
+ /* Find matching answer */
+ pt = pj_strtoul(fmt);
+
+ if (pt < 96) {
+ for (j=0; j<answer->desc.fmt_count; ++j) {
+ if (pj_strcmp(fmt, &answer->desc.fmt[j])==0)
+ break;
+ }
+ } else {
+ /* This is dynamic payload type.
+ * For dynamic payload type, we must look the rtpmap and
+ * compare the encoding name.
+ */
+ const pjmedia_sdp_attr *a;
+ pjmedia_sdp_rtpmap or_;
+
+ /* Get the rtpmap for the payload type in the offer. */
+ a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", fmt);
+ if (!a) {
+ pj_assert(!"Bug! Offer should have been validated");
+ return PJ_EBUG;
+ }
+ pjmedia_sdp_attr_get_rtpmap(a, &or_);
+
+ /* Find paylaod in answer SDP with matching
+ * encoding name and clock rate.
+ */
+ for (j=0; j<answer->desc.fmt_count; ++j) {
+ a = pjmedia_sdp_media_find_attr2(answer, "rtpmap",
+ &answer->desc.fmt[j]);
+ if (a) {
+ pjmedia_sdp_rtpmap ar;
+ pjmedia_sdp_attr_get_rtpmap(a, &ar);
+
+ /* See if encoding name, clock rate, and channel
+ * count match
+ */
+ if (!pj_stricmp(&or_.enc_name, &ar.enc_name) &&
+ or_.clock_rate == ar.clock_rate &&
+ (pj_stricmp(&or_.param, &ar.param)==0 ||
+ (ar.param.slen==1 && *ar.param.ptr=='1')))
+ {
+ /* Call custom format matching callbacks */
+ if (custom_fmt_match(pool, &or_.enc_name,
+ offer, i, answer, j, 0) ==
+ PJ_SUCCESS)
+ {
+ /* Match! */
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (j == answer->desc.fmt_count) {
+ /* This format has no matching answer.
+ * Remove it from our offer.
+ */
+ pjmedia_sdp_attr *a;
+
+ /* Remove rtpmap associated with this format */
+ a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", fmt);
+ if (a)
+ pjmedia_sdp_media_remove_attr(offer, a);
+
+ /* Remove fmtp associated with this format */
+ a = pjmedia_sdp_media_find_attr2(offer, "fmtp", fmt);
+ if (a)
+ pjmedia_sdp_media_remove_attr(offer, a);
+
+ /* Remove this format from offer's array */
+ pj_array_erase(offer->desc.fmt, sizeof(offer->desc.fmt[0]),
+ offer->desc.fmt_count, i);
+ --offer->desc.fmt_count;
+
+ } else {
+ offer_fmt_prior[i] = j;
+ ++i;
+ }
+ }
+
+ if (0 == offer->desc.fmt_count) {
+ /* No common codec in the answer! */
+ return PJMEDIA_SDPNEG_EANSNOMEDIA;
+ }
+
+ /* Post process:
+ * - Resort offer formats so the order match to the answer.
+ * - Remove answer formats that unmatches to the offer.
+ */
+
+ /* Resort offer formats */
+ for (i=0; i<offer->desc.fmt_count; ++i) {
+ unsigned j;
+ for (j=i+1; j<offer->desc.fmt_count; ++j) {
+ if (offer_fmt_prior[i] > offer_fmt_prior[j]) {
+ unsigned tmp = offer_fmt_prior[i];
+ offer_fmt_prior[i] = offer_fmt_prior[j];
+ offer_fmt_prior[j] = tmp;
+ str_swap(&offer->desc.fmt[i], &offer->desc.fmt[j]);
+ }
+ }
+ }
+
+ /* Remove unmatched answer formats */
+ {
+ unsigned del_cnt = 0;
+ for (i=0; i<answer->desc.fmt_count;) {
+ /* The offer is ordered now, also the offer_fmt_prior */
+ if (i >= offer->desc.fmt_count ||
+ offer_fmt_prior[i]-del_cnt != i)
+ {
+ pj_str_t *fmt = &answer->desc.fmt[i];
+ pjmedia_sdp_attr *a;
+
+ /* Remove rtpmap associated with this format */
+ a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", fmt);
+ if (a)
+ pjmedia_sdp_media_remove_attr(answer, a);
+
+ /* Remove fmtp associated with this format */
+ a = pjmedia_sdp_media_find_attr2(answer, "fmtp", fmt);
+ if (a)
+ pjmedia_sdp_media_remove_attr(answer, a);
+
+ /* Remove this format from answer's array */
+ pj_array_erase(answer->desc.fmt,
+ sizeof(answer->desc.fmt[0]),
+ answer->desc.fmt_count, i);
+ --answer->desc.fmt_count;
+
+ ++del_cnt;
+ } else {
+ ++i;
+ }
+ }
+ }
+ }
+
+ /* Looks okay */
+ return PJ_SUCCESS;
+}
+
+
+/* Update local media session (offer) to create active local session
+ * after receiving remote answer.
+ */
+static pj_status_t process_answer(pj_pool_t *pool,
+ pjmedia_sdp_session *offer,
+ pjmedia_sdp_session *answer,
+ pj_bool_t allow_asym,
+ pjmedia_sdp_session **p_active)
+{
+ unsigned omi = 0; /* Offer media index */
+ unsigned ami = 0; /* Answer media index */
+ pj_bool_t has_active = PJ_FALSE;
+ pj_status_t status;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(pool && offer && answer && p_active, PJ_EINVAL);
+
+ /* Check that media count match between offer and answer */
+ // Ticket #527, different media count is allowed for more interoperability,
+ // however, the media order must be same between offer and answer.
+ // if (offer->media_count != answer->media_count)
+ // return PJMEDIA_SDPNEG_EMISMEDIA;
+
+ /* Now update each media line in the offer with the answer. */
+ for (; omi<offer->media_count; ++omi) {
+ if (ami == answer->media_count) {
+ /* The answer has less media than the offer */
+ pjmedia_sdp_media *am;
+
+ /* Generate matching-but-disabled-media for the answer */
+ am = sdp_media_clone_deactivate(pool, offer->media[omi],
+ offer->media[omi], offer);
+ answer->media[answer->media_count++] = am;
+ ++ami;
+
+ /* Deactivate our media offer too */
+ pjmedia_sdp_media_deactivate(pool, offer->media[omi]);
+
+ /* No answer media to be negotiated */
+ continue;
+ }
+
+ status = process_m_answer(pool, offer->media[omi], answer->media[ami],
+ allow_asym);
+
+ /* If media type is mismatched, just disable the media. */
+ if (status == PJMEDIA_SDPNEG_EINVANSMEDIA) {
+ pjmedia_sdp_media_deactivate(pool, offer->media[omi]);
+ continue;
+ }
+ /* No common format in the answer media. */
+ else if (status == PJMEDIA_SDPNEG_EANSNOMEDIA) {
+ pjmedia_sdp_media_deactivate(pool, offer->media[omi]);
+ pjmedia_sdp_media_deactivate(pool, answer->media[ami]);
+ }
+ /* Return the error code, for other errors. */
+ else if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ if (offer->media[omi]->desc.port != 0)
+ has_active = PJ_TRUE;
+
+ ++ami;
+ }
+
+ *p_active = offer;
+
+ return has_active ? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA;
+}
+
+
+/* Internal function to rewrite the format string in SDP attribute rtpmap
+ * and fmtp.
+ */
+PJ_INLINE(void) rewrite_pt(pj_pool_t *pool, pj_str_t *attr_val,
+ const pj_str_t *old_pt, const pj_str_t *new_pt)
+{
+ int len_diff = new_pt->slen - old_pt->slen;
+
+ /* Note that attribute value should be null-terminated. */
+ if (len_diff > 0) {
+ pj_str_t new_val;
+ new_val.ptr = (char*)pj_pool_alloc(pool, attr_val->slen+len_diff+1);
+ new_val.slen = attr_val->slen + len_diff;
+ pj_memcpy(new_val.ptr + len_diff, attr_val->ptr, attr_val->slen + 1);
+ *attr_val = new_val;
+ } else if (len_diff < 0) {
+ attr_val->slen += len_diff;
+ pj_memmove(attr_val->ptr, attr_val->ptr - len_diff,
+ attr_val->slen + 1);
+ }
+ pj_memcpy(attr_val->ptr, new_pt->ptr, new_pt->slen);
+}
+
+
+/* Internal function to apply symmetric PT for the local answer. */
+static void apply_answer_symmetric_pt(pj_pool_t *pool,
+ pjmedia_sdp_media *answer,
+ unsigned pt_cnt,
+ const pj_str_t pt_offer[],
+ const pj_str_t pt_answer[])
+{
+ pjmedia_sdp_attr *a_tmp[PJMEDIA_MAX_SDP_ATTR];
+ unsigned i, a_tmp_cnt = 0;
+
+ /* Rewrite the payload types in the answer if different to
+ * the ones in the offer.
+ */
+ for (i = 0; i < pt_cnt; ++i) {
+ pjmedia_sdp_attr *a;
+
+ /* Skip if the PTs are the same already, e.g: static PT. */
+ if (pj_strcmp(&pt_answer[i], &pt_offer[i]) == 0)
+ continue;
+
+ /* Rewrite payload type in the answer to match to the offer */
+ pj_strdup(pool, &answer->desc.fmt[i], &pt_offer[i]);
+
+ /* Also update payload type in rtpmap */
+ a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", &pt_answer[i]);
+ if (a) {
+ rewrite_pt(pool, &a->value, &pt_answer[i], &pt_offer[i]);
+ /* Temporarily remove the attribute in case the new payload
+ * type is being used by another format in the media.
+ */
+ pjmedia_sdp_media_remove_attr(answer, a);
+ a_tmp[a_tmp_cnt++] = a;
+ }
+
+ /* Also update payload type in fmtp */
+ a = pjmedia_sdp_media_find_attr2(answer, "fmtp", &pt_answer[i]);
+ if (a) {
+ rewrite_pt(pool, &a->value, &pt_answer[i], &pt_offer[i]);
+ /* Temporarily remove the attribute in case the new payload
+ * type is being used by another format in the media.
+ */
+ pjmedia_sdp_media_remove_attr(answer, a);
+ a_tmp[a_tmp_cnt++] = a;
+ }
+ }
+
+ /* Return back 'rtpmap' and 'fmtp' attributes */
+ for (i = 0; i < a_tmp_cnt; ++i)
+ pjmedia_sdp_media_add_attr(answer, a_tmp[i]);
+}
+
+
+/* Try to match offer with answer. */
+static pj_status_t match_offer(pj_pool_t *pool,
+ pj_bool_t prefer_remote_codec_order,
+ const pjmedia_sdp_media *offer,
+ const pjmedia_sdp_media *preanswer,
+ const pjmedia_sdp_session *preanswer_sdp,
+ pjmedia_sdp_media **p_answer)
+{
+ unsigned i;
+ pj_bool_t master_has_codec = 0,
+ master_has_telephone_event = 0,
+ master_has_other = 0,
+ found_matching_codec = 0,
+ found_matching_telephone_event = 0,
+ found_matching_other = 0;
+ unsigned pt_answer_count = 0;
+ pj_str_t pt_answer[PJMEDIA_MAX_SDP_FMT];
+ pj_str_t pt_offer[PJMEDIA_MAX_SDP_FMT];
+ pjmedia_sdp_media *answer;
+ const pjmedia_sdp_media *master, *slave;
+
+ /* If offer has zero port, just clone the offer */
+ if (offer->desc.port == 0) {
+ answer = sdp_media_clone_deactivate(pool, offer, preanswer,
+ preanswer_sdp);
+ *p_answer = answer;
+ return PJ_SUCCESS;
+ }
+
+ /* If the preanswer define zero port, this media is being rejected,
+ * just clone the preanswer.
+ */
+ if (preanswer->desc.port == 0) {
+ answer = pjmedia_sdp_media_clone(pool, preanswer);
+ *p_answer = answer;
+ return PJ_SUCCESS;
+ }
+
+ /* Set master/slave negotiator based on prefer_remote_codec_order. */
+ if (prefer_remote_codec_order) {
+ master = offer;
+ slave = preanswer;
+ } else {
+ master = preanswer;
+ slave = offer;
+ }
+
+ /* With the addition of telephone-event and dodgy MS RTC SDP,
+ * the answer generation algorithm looks really shitty...
+ */
+ for (i=0; i<master->desc.fmt_count; ++i) {
+ unsigned j;
+
+ if (pj_isdigit(*master->desc.fmt[i].ptr)) {
+ /* This is normal/standard payload type, where it's identified
+ * by payload number.
+ */
+ unsigned pt;
+
+ pt = pj_strtoul(&master->desc.fmt[i]);
+
+ if (pt < 96) {
+ /* For static payload type, it's enough to compare just
+ * the payload number.
+ */
+
+ master_has_codec = 1;
+
+ /* We just need to select one codec.
+ * Continue if we have selected matching codec for previous
+ * payload.
+ */
+ if (found_matching_codec)
+ continue;
+
+ /* Find matching codec in local descriptor. */
+ for (j=0; j<slave->desc.fmt_count; ++j) {
+ unsigned p;
+ p = pj_strtoul(&slave->desc.fmt[j]);
+ if (p == pt && pj_isdigit(*slave->desc.fmt[j].ptr)) {
+ found_matching_codec = 1;
+ pt_offer[pt_answer_count] = slave->desc.fmt[j];
+ pt_answer[pt_answer_count++] = slave->desc.fmt[j];
+ break;
+ }
+ }
+
+ } else {
+ /* This is dynamic payload type.
+ * For dynamic payload type, we must look the rtpmap and
+ * compare the encoding name.
+ */
+ const pjmedia_sdp_attr *a;
+ pjmedia_sdp_rtpmap or_;
+ pj_bool_t is_codec;
+
+ /* Get the rtpmap for the payload type in the master. */
+ a = pjmedia_sdp_media_find_attr2(master, "rtpmap",
+ &master->desc.fmt[i]);
+ if (!a) {
+ pj_assert(!"Bug! Offer should have been validated");
+ return PJMEDIA_SDP_EMISSINGRTPMAP;
+ }
+ pjmedia_sdp_attr_get_rtpmap(a, &or_);
+
+ if (!pj_stricmp2(&or_.enc_name, "telephone-event")) {
+ master_has_telephone_event = 1;
+ if (found_matching_telephone_event)
+ continue;
+ is_codec = 0;
+ } else {
+ master_has_codec = 1;
+ if (found_matching_codec)
+ continue;
+ is_codec = 1;
+ }
+
+ /* Find paylaod in our initial SDP with matching
+ * encoding name and clock rate.
+ */
+ for (j=0; j<slave->desc.fmt_count; ++j) {
+ a = pjmedia_sdp_media_find_attr2(slave, "rtpmap",
+ &slave->desc.fmt[j]);
+ if (a) {
+ pjmedia_sdp_rtpmap lr;
+ pjmedia_sdp_attr_get_rtpmap(a, &lr);
+
+ /* See if encoding name, clock rate, and
+ * channel count match
+ */
+ if (!pj_stricmp(&or_.enc_name, &lr.enc_name) &&
+ or_.clock_rate == lr.clock_rate &&
+ (pj_stricmp(&or_.param, &lr.param)==0 ||
+ (lr.param.slen==0 && or_.param.slen==1 &&
+ *or_.param.ptr=='1') ||
+ (or_.param.slen==0 && lr.param.slen==1 &&
+ *lr.param.ptr=='1')))
+ {
+ /* Match! */
+ if (is_codec) {
+ pjmedia_sdp_media *o, *a;
+ unsigned o_fmt_idx, a_fmt_idx;
+
+ o = (pjmedia_sdp_media*)offer;
+ a = (pjmedia_sdp_media*)preanswer;
+ o_fmt_idx = prefer_remote_codec_order? i:j;
+ a_fmt_idx = prefer_remote_codec_order? j:i;
+
+ /* Call custom format matching callbacks */
+ if (custom_fmt_match(pool, &or_.enc_name,
+ o, o_fmt_idx,
+ a, a_fmt_idx,
+ ALLOW_MODIFY_ANSWER) !=
+ PJ_SUCCESS)
+ {
+ continue;
+ }
+ found_matching_codec = 1;
+ } else {
+ found_matching_telephone_event = 1;
+ }
+
+ pt_offer[pt_answer_count] =
+ prefer_remote_codec_order?
+ offer->desc.fmt[i]:
+ offer->desc.fmt[j];
+ pt_answer[pt_answer_count++] =
+ prefer_remote_codec_order?
+ preanswer->desc.fmt[j]:
+ preanswer->desc.fmt[i];
+ break;
+ }
+ }
+ }
+ }
+
+ } else {
+ /* This is a non-standard, brain damaged SDP where the payload
+ * type is non-numeric. It exists e.g. in Microsoft RTC based
+ * UA, to indicate instant messaging capability.
+ * Example:
+ * - m=x-ms-message 5060 sip null
+ */
+ master_has_other = 1;
+ if (found_matching_other)
+ continue;
+
+ for (j=0; j<slave->desc.fmt_count; ++j) {
+ if (!pj_strcmp(&master->desc.fmt[i], &slave->desc.fmt[j])) {
+ /* Match */
+ found_matching_other = 1;
+ pt_offer[pt_answer_count] = prefer_remote_codec_order?
+ offer->desc.fmt[i]:
+ offer->desc.fmt[j];
+ pt_answer[pt_answer_count++] = prefer_remote_codec_order?
+ preanswer->desc.fmt[j]:
+ preanswer->desc.fmt[i];
+ break;
+ }
+ }
+ }
+ }
+
+ /* See if all types of master can be matched. */
+ if (master_has_codec && !found_matching_codec) {
+ return PJMEDIA_SDPNEG_NOANSCODEC;
+ }
+
+ /* If this comment is removed, negotiation will fail if remote has offered
+ telephone-event and local is not configured with telephone-event
+
+ if (offer_has_telephone_event && !found_matching_telephone_event) {
+ return PJMEDIA_SDPNEG_NOANSTELEVENT;
+ }
+ */
+
+ if (master_has_other && !found_matching_other) {
+ return PJMEDIA_SDPNEG_NOANSUNKNOWN;
+ }
+
+ /* Seems like everything is in order.
+ * Build the answer by cloning from preanswer, but rearrange the payload
+ * to suit the offer.
+ */
+ answer = pjmedia_sdp_media_clone(pool, preanswer);
+ for (i=0; i<pt_answer_count; ++i) {
+ unsigned j;
+ for (j=i; j<answer->desc.fmt_count; ++j) {
+ if (!pj_strcmp(&answer->desc.fmt[j], &pt_answer[i]))
+ break;
+ }
+ pj_assert(j != answer->desc.fmt_count);
+ str_swap(&answer->desc.fmt[i], &answer->desc.fmt[j]);
+ }
+
+ /* Remove unwanted local formats. */
+ for (i=pt_answer_count; i<answer->desc.fmt_count; ++i) {
+ pjmedia_sdp_attr *a;
+
+ /* Remove rtpmap for this format */
+ a = pjmedia_sdp_media_find_attr2(answer, "rtpmap",
+ &answer->desc.fmt[i]);
+ if (a) {
+ pjmedia_sdp_media_remove_attr(answer, a);
+ }
+
+ /* Remove fmtp for this format */
+ a = pjmedia_sdp_media_find_attr2(answer, "fmtp",
+ &answer->desc.fmt[i]);
+ if (a) {
+ pjmedia_sdp_media_remove_attr(answer, a);
+ }
+ }
+ answer->desc.fmt_count = pt_answer_count;
+
+#if PJMEDIA_SDP_NEG_ANSWER_SYMMETRIC_PT
+ apply_answer_symmetric_pt(pool, answer, pt_answer_count,
+ pt_offer, pt_answer);
+#endif
+
+ /* Update media direction. */
+ update_media_direction(pool, offer, answer);
+
+ *p_answer = answer;
+ return PJ_SUCCESS;
+}
+
+/* Create complete answer for remote's offer. */
+static pj_status_t create_answer( pj_pool_t *pool,
+ pj_bool_t prefer_remote_codec_order,
+ const pjmedia_sdp_session *initial,
+ const pjmedia_sdp_session *offer,
+ pjmedia_sdp_session **p_answer)
+{
+ pj_status_t status = PJMEDIA_SDPNEG_ENOMEDIA;
+ pj_bool_t has_active = PJ_FALSE;
+ pjmedia_sdp_session *answer;
+ char media_used[PJMEDIA_MAX_SDP_MEDIA];
+ unsigned i;
+
+ /* Validate remote offer.
+ * This should have been validated before.
+ */
+ PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(offer))==PJ_SUCCESS, status);
+
+ /* Create initial answer by duplicating initial SDP,
+ * but clear all media lines. The media lines will be filled up later.
+ */
+ answer = pjmedia_sdp_session_clone(pool, initial);
+ PJ_ASSERT_RETURN(answer != NULL, PJ_ENOMEM);
+
+ answer->media_count = 0;
+
+ pj_bzero(media_used, sizeof(media_used));
+
+ /* For each media line, create our answer based on our initial
+ * capability.
+ */
+ for (i=0; i<offer->media_count; ++i) {
+ const pjmedia_sdp_media *om; /* offer */
+ const pjmedia_sdp_media *im; /* initial media */
+ pjmedia_sdp_media *am = NULL; /* answer/result */
+ unsigned j;
+
+ om = offer->media[i];
+
+ /* Find media description in our initial capability that matches
+ * the media type and transport type of offer's media, has
+ * matching codec, and has not been used to answer other offer.
+ */
+ for (im=NULL, j=0; j<initial->media_count; ++j) {
+ im = initial->media[j];
+ if (pj_strcmp(&om->desc.media, &im->desc.media)==0 &&
+ pj_strcmp(&om->desc.transport, &im->desc.transport)==0 &&
+ media_used[j] == 0)
+ {
+ pj_status_t status2;
+
+ /* See if it has matching codec. */
+ status2 = match_offer(pool, prefer_remote_codec_order,
+ om, im, initial, &am);
+ if (status2 == PJ_SUCCESS) {
+ /* Mark media as used. */
+ media_used[j] = 1;
+ break;
+ } else {
+ status = status2;
+ }
+ }
+ }
+
+ if (j==initial->media_count) {
+ /* No matching media.
+ * Reject the offer by setting the port to zero in the answer.
+ */
+ /* For simplicity in the construction of the answer, we'll
+ * just clone the media from the offer. Anyway receiver will
+ * ignore anything in the media once it sees that the port
+ * number is zero.
+ */
+ am = sdp_media_clone_deactivate(pool, om, om, answer);
+ } else {
+ /* The answer is in am */
+ pj_assert(am != NULL);
+ }
+
+ /* Add the media answer */
+ answer->media[answer->media_count++] = am;
+
+ /* Check if this media is active.*/
+ if (am->desc.port != 0)
+ has_active = PJ_TRUE;
+ }
+
+ *p_answer = answer;
+
+ return has_active ? PJ_SUCCESS : status;
+}
+
+/* Cancel offer */
+PJ_DEF(pj_status_t) pjmedia_sdp_neg_cancel_offer(pjmedia_sdp_neg *neg)
+{
+ PJ_ASSERT_RETURN(neg, PJ_EINVAL);
+
+ /* Must be in LOCAL_OFFER state. */
+ PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER ||
+ neg->state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER,
+ PJMEDIA_SDPNEG_EINSTATE);
+
+ /* Reset state to done */
+ neg->state = PJMEDIA_SDP_NEG_STATE_DONE;
+
+ /* Clear temporary SDP */
+ neg->neg_local_sdp = neg->neg_remote_sdp = NULL;
+ neg->has_remote_answer = PJ_FALSE;
+
+ return PJ_SUCCESS;
+}
+
+
+/* The best bit: SDP negotiation function! */
+PJ_DEF(pj_status_t) pjmedia_sdp_neg_negotiate( pj_pool_t *pool,
+ pjmedia_sdp_neg *neg,
+ pj_bool_t allow_asym)
+{
+ pj_status_t status;
+
+ /* Check arguments are valid. */
+ PJ_ASSERT_RETURN(pool && neg, PJ_EINVAL);
+
+ /* Must be in STATE_WAIT_NEGO state. */
+ PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO,
+ PJMEDIA_SDPNEG_EINSTATE);
+
+ /* Must have remote offer. */
+ PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJ_EBUG);
+
+ if (neg->has_remote_answer) {
+ pjmedia_sdp_session *active;
+ status = process_answer(pool, neg->neg_local_sdp, neg->neg_remote_sdp,
+ allow_asym, &active);
+ if (status == PJ_SUCCESS) {
+ /* Only update active SDPs when negotiation is successfull */
+ neg->active_local_sdp = active;
+ neg->active_remote_sdp = neg->neg_remote_sdp;
+ }
+ } else {
+ pjmedia_sdp_session *answer = NULL;
+
+ status = create_answer(pool, neg->prefer_remote_codec_order,
+ neg->neg_local_sdp, neg->neg_remote_sdp,
+ &answer);
+ if (status == PJ_SUCCESS) {
+ pj_uint32_t active_ver;
+
+ if (neg->active_local_sdp)
+ active_ver = neg->active_local_sdp->origin.version;
+ else
+ active_ver = neg->initial_sdp->origin.version;
+
+ /* Only update active SDPs when negotiation is successfull */
+ neg->active_local_sdp = answer;
+ neg->active_remote_sdp = neg->neg_remote_sdp;
+
+ /* Increment SDP version */
+ neg->active_local_sdp->origin.version = ++active_ver;
+ }
+ }
+
+ /* State is DONE regardless */
+ neg->state = PJMEDIA_SDP_NEG_STATE_DONE;
+
+ /* Save state */
+ neg->answer_was_remote = neg->has_remote_answer;
+
+ /* Clear temporary SDP */
+ neg->neg_local_sdp = neg->neg_remote_sdp = NULL;
+ neg->has_remote_answer = PJ_FALSE;
+
+ return status;
+}
+
+
+static pj_status_t custom_fmt_match(pj_pool_t *pool,
+ const pj_str_t *fmt_name,
+ pjmedia_sdp_media *offer,
+ unsigned o_fmt_idx,
+ pjmedia_sdp_media *answer,
+ unsigned a_fmt_idx,
+ unsigned option)
+{
+ unsigned i;
+
+ for (i = 0; i < fmt_match_cb_cnt; ++i) {
+ if (pj_stricmp(fmt_name, &fmt_match_cb[i].fmt_name) == 0) {
+ pj_assert(fmt_match_cb[i].cb);
+ return (*fmt_match_cb[i].cb)(pool, offer, o_fmt_idx,
+ answer, a_fmt_idx,
+ option);
+ }
+ }
+
+ /* Not customized format matching found, should be matched */
+ return PJ_SUCCESS;
+}
+
+/* Register customized SDP format negotiation callback function. */
+PJ_DECL(pj_status_t) pjmedia_sdp_neg_register_fmt_match_cb(
+ const pj_str_t *fmt_name,
+ pjmedia_sdp_neg_fmt_match_cb cb)
+{
+ struct fmt_match_cb_t *f = NULL;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(fmt_name, PJ_EINVAL);
+
+ /* Check if the callback for the format name has been registered */
+ for (i = 0; i < fmt_match_cb_cnt; ++i) {
+ if (pj_stricmp(fmt_name, &fmt_match_cb[i].fmt_name) == 0)
+ break;
+ }
+
+ /* Unregistration */
+
+ if (cb == NULL) {
+ if (i == fmt_match_cb_cnt)
+ return PJ_ENOTFOUND;
+
+ pj_array_erase(fmt_match_cb, sizeof(fmt_match_cb[0]),
+ fmt_match_cb_cnt, i);
+ fmt_match_cb_cnt--;
+
+ return PJ_SUCCESS;
+ }
+
+ /* Registration */
+
+ if (i < fmt_match_cb_cnt) {
+ /* The same format name has been registered before */
+ if (cb != fmt_match_cb[i].cb)
+ return PJ_EEXISTS;
+ else
+ return PJ_SUCCESS;
+ }
+
+ if (fmt_match_cb_cnt >= PJ_ARRAY_SIZE(fmt_match_cb))
+ return PJ_ETOOMANY;
+
+ f = &fmt_match_cb[fmt_match_cb_cnt++];
+ f->fmt_name = *fmt_name;
+ f->cb = cb;
+
+ return PJ_SUCCESS;
+}
+
+
+/* Match format in the SDP media offer and answer. */
+PJ_DEF(pj_status_t) pjmedia_sdp_neg_fmt_match(pj_pool_t *pool,
+ pjmedia_sdp_media *offer,
+ unsigned o_fmt_idx,
+ pjmedia_sdp_media *answer,
+ unsigned a_fmt_idx,
+ unsigned option)
+{
+ const pjmedia_sdp_attr *attr;
+ pjmedia_sdp_rtpmap o_rtpmap, a_rtpmap;
+ unsigned o_pt;
+ unsigned a_pt;
+
+ o_pt = pj_strtoul(&offer->desc.fmt[o_fmt_idx]);
+ a_pt = pj_strtoul(&answer->desc.fmt[a_fmt_idx]);
+
+ if (o_pt < 96 || a_pt < 96) {
+ if (o_pt == a_pt)
+ return PJ_SUCCESS;
+ else
+ return PJMEDIA_SDP_EFORMATNOTEQUAL;
+ }
+
+ /* Get the format rtpmap from the offer. */
+ attr = pjmedia_sdp_media_find_attr2(offer, "rtpmap",
+ &offer->desc.fmt[o_fmt_idx]);
+ if (!attr) {
+ pj_assert(!"Bug! Offer haven't been validated");
+ return PJ_EBUG;
+ }
+ pjmedia_sdp_attr_get_rtpmap(attr, &o_rtpmap);
+
+ /* Get the format rtpmap from the answer. */
+ attr = pjmedia_sdp_media_find_attr2(answer, "rtpmap",
+ &answer->desc.fmt[a_fmt_idx]);
+ if (!attr) {
+ pj_assert(!"Bug! Answer haven't been validated");
+ return PJ_EBUG;
+ }
+ pjmedia_sdp_attr_get_rtpmap(attr, &a_rtpmap);
+
+ if (pj_stricmp(&o_rtpmap.enc_name, &a_rtpmap.enc_name) != 0 ||
+ o_rtpmap.clock_rate != a_rtpmap.clock_rate)
+ {
+ return PJMEDIA_SDP_EFORMATNOTEQUAL;
+ }
+
+ return custom_fmt_match(pool, &o_rtpmap.enc_name,
+ offer, o_fmt_idx, answer, a_fmt_idx, option);
+}
+
diff --git a/pjmedia/src/pjmedia/sdp_wrap.cpp b/pjmedia/src/pjmedia/sdp_wrap.cpp
new file mode 100644
index 0000000..fe816fd
--- /dev/null
+++ b/pjmedia/src/pjmedia/sdp_wrap.cpp
@@ -0,0 +1,24 @@
+/* $Id: sdp_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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
+ */
+
+/*
+ * This file is a C++ wrapper, see ticket #886 for details.
+ */
+
+#include "sdp.c"
diff --git a/pjmedia/src/pjmedia/session.c b/pjmedia/src/pjmedia/session.c
new file mode 100644
index 0000000..f98bafc
--- /dev/null
+++ b/pjmedia/src/pjmedia/session.c
@@ -0,0 +1,444 @@
+/* $Id: session.c 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/session.h>
+#include <pjmedia/errno.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+#include <pj/rand.h>
+
+
+struct pjmedia_session
+{
+ pj_pool_t *pool;
+ pjmedia_endpt *endpt;
+ unsigned stream_cnt;
+ pjmedia_stream_info stream_info[PJMEDIA_MAX_SDP_MEDIA];
+ pjmedia_stream *stream[PJMEDIA_MAX_SDP_MEDIA];
+ void *user_data;
+};
+
+#define THIS_FILE "session.c"
+
+#ifndef PJMEDIA_SESSION_SIZE
+# define PJMEDIA_SESSION_SIZE (10*1024)
+#endif
+
+#ifndef PJMEDIA_SESSION_INC
+# define PJMEDIA_SESSION_INC 1024
+#endif
+
+
+static const pj_str_t ID_AUDIO = { "audio", 5};
+static const pj_str_t ID_VIDEO = { "video", 5};
+static const pj_str_t ID_APPLICATION = { "application", 11};
+static const pj_str_t ID_IN = { "IN", 2 };
+static const pj_str_t ID_IP4 = { "IP4", 3};
+static const pj_str_t ID_IP6 = { "IP6", 3};
+static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 };
+static const pj_str_t ID_RTP_SAVP = { "RTP/SAVP", 8 };
+//static const pj_str_t ID_SDP_NAME = { "pjmedia", 7 };
+static const pj_str_t ID_RTPMAP = { "rtpmap", 6 };
+static const pj_str_t ID_TELEPHONE_EVENT = { "telephone-event", 15 };
+
+static const pj_str_t STR_INACTIVE = { "inactive", 8 };
+static const pj_str_t STR_SENDRECV = { "sendrecv", 8 };
+static const pj_str_t STR_SENDONLY = { "sendonly", 8 };
+static const pj_str_t STR_RECVONLY = { "recvonly", 8 };
+
+/*
+ * Initialize session info from SDP session descriptors.
+ */
+PJ_DEF(pj_status_t) pjmedia_session_info_from_sdp( pj_pool_t *pool,
+ pjmedia_endpt *endpt,
+ unsigned max_streams,
+ pjmedia_session_info *si,
+ const pjmedia_sdp_session *local,
+ const pjmedia_sdp_session *remote)
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(pool && endpt && si && local && remote, PJ_EINVAL);
+
+ si->stream_cnt = max_streams;
+ if (si->stream_cnt > local->media_count)
+ si->stream_cnt = local->media_count;
+
+ for (i=0; i<si->stream_cnt; ++i) {
+ pj_status_t status;
+
+ status = pjmedia_stream_info_from_sdp( &si->stream_info[i], pool,
+ endpt,
+ local, remote, i);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/**
+ * Create new session.
+ */
+PJ_DEF(pj_status_t) pjmedia_session_create( pjmedia_endpt *endpt,
+ const pjmedia_session_info *si,
+ pjmedia_transport *transports[],
+ void *user_data,
+ pjmedia_session **p_session )
+{
+ pj_pool_t *pool;
+ pjmedia_session *session;
+ int i; /* Must be signed */
+ pj_status_t status;
+
+ /* Verify arguments. */
+ PJ_ASSERT_RETURN(endpt && si && p_session, PJ_EINVAL);
+
+ /* Create pool for the session. */
+ pool = pjmedia_endpt_create_pool( endpt, "session",
+ PJMEDIA_SESSION_SIZE,
+ PJMEDIA_SESSION_INC);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ session = PJ_POOL_ZALLOC_T(pool, pjmedia_session);
+ session->pool = pool;
+ session->endpt = endpt;
+ session->stream_cnt = si->stream_cnt;
+ session->user_data = user_data;
+
+ /* Copy stream info (this simple memcpy may break sometime) */
+ pj_memcpy(session->stream_info, si->stream_info,
+ si->stream_cnt * sizeof(pjmedia_stream_info));
+
+ /*
+ * Now create and start the stream!
+ */
+ for (i=0; i<(int)si->stream_cnt; ++i) {
+
+ /* Create the stream */
+ status = pjmedia_stream_create(endpt, session->pool,
+ &session->stream_info[i],
+ (transports?transports[i]:NULL),
+ session,
+ &session->stream[i]);
+ if (status == PJ_SUCCESS)
+ status = pjmedia_stream_start(session->stream[i]);
+
+ if (status != PJ_SUCCESS) {
+
+ for ( --i; i>=0; ++i) {
+ pjmedia_stream_destroy(session->stream[i]);
+ }
+
+ pj_pool_release(session->pool);
+ return status;
+ }
+ }
+
+
+ /* Done. */
+
+ *p_session = session;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get session info.
+ */
+PJ_DEF(pj_status_t) pjmedia_session_get_info( pjmedia_session *session,
+ pjmedia_session_info *info )
+{
+ PJ_ASSERT_RETURN(session && info, PJ_EINVAL);
+
+ info->stream_cnt = session->stream_cnt;
+ pj_memcpy(info->stream_info, session->stream_info,
+ session->stream_cnt * sizeof(pjmedia_stream_info));
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get user data.
+ */
+PJ_DEF(void*) pjmedia_session_get_user_data( pjmedia_session *session)
+{
+ return (session? session->user_data : NULL);
+}
+
+/**
+ * Destroy media session.
+ */
+PJ_DEF(pj_status_t) pjmedia_session_destroy (pjmedia_session *session)
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(session, PJ_EINVAL);
+
+ for (i=0; i<session->stream_cnt; ++i) {
+
+ pjmedia_stream_destroy(session->stream[i]);
+
+ }
+
+ pj_pool_release (session->pool);
+
+ return PJ_SUCCESS;
+}
+
+
+/**
+ * Activate all stream in media session.
+ *
+ */
+PJ_DEF(pj_status_t) pjmedia_session_resume(pjmedia_session *session,
+ pjmedia_dir dir)
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(session, PJ_EINVAL);
+
+ for (i=0; i<session->stream_cnt; ++i) {
+ pjmedia_session_resume_stream(session, i, dir);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/**
+ * Suspend receipt and transmission of all stream in media session.
+ *
+ */
+PJ_DEF(pj_status_t) pjmedia_session_pause(pjmedia_session *session,
+ pjmedia_dir dir)
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(session, PJ_EINVAL);
+
+ for (i=0; i<session->stream_cnt; ++i) {
+ pjmedia_session_pause_stream(session, i, dir);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/**
+ * Suspend receipt and transmission of individual stream in media session.
+ */
+PJ_DEF(pj_status_t) pjmedia_session_pause_stream( pjmedia_session *session,
+ unsigned index,
+ pjmedia_dir dir)
+{
+ PJ_ASSERT_RETURN(session && index < session->stream_cnt, PJ_EINVAL);
+
+ return pjmedia_stream_pause(session->stream[index], dir);
+}
+
+
+/**
+ * Activate individual stream in media session.
+ *
+ */
+PJ_DEF(pj_status_t) pjmedia_session_resume_stream( pjmedia_session *session,
+ unsigned index,
+ pjmedia_dir dir)
+{
+ PJ_ASSERT_RETURN(session && index < session->stream_cnt, PJ_EINVAL);
+
+ return pjmedia_stream_resume(session->stream[index], dir);
+}
+
+/**
+ * Send RTCP SDES for the session.
+ */
+PJ_DEF(pj_status_t)
+pjmedia_session_send_rtcp_sdes( const pjmedia_session *session )
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(session, PJ_EINVAL);
+
+ for (i=0; i<session->stream_cnt; ++i) {
+ pjmedia_stream_send_rtcp_sdes(session->stream[i]);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/**
+ * Send RTCP BYE for the session.
+ */
+PJ_DEF(pj_status_t)
+pjmedia_session_send_rtcp_bye( const pjmedia_session *session )
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(session, PJ_EINVAL);
+
+ for (i=0; i<session->stream_cnt; ++i) {
+ pjmedia_stream_send_rtcp_bye(session->stream[i]);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/**
+ * Enumerate media stream in the session.
+ */
+PJ_DEF(pj_status_t) pjmedia_session_enum_streams(const pjmedia_session *session,
+ unsigned *count,
+ pjmedia_stream_info info[])
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(session && count && *count && info, PJ_EINVAL);
+
+ if (*count > session->stream_cnt)
+ *count = session->stream_cnt;
+
+ for (i=0; i<*count; ++i) {
+ pj_memcpy(&info[i], &session->stream_info[i],
+ sizeof(pjmedia_stream_info));
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the port interface.
+ */
+PJ_DEF(pj_status_t) pjmedia_session_get_port( pjmedia_session *session,
+ unsigned index,
+ pjmedia_port **p_port)
+{
+ return pjmedia_stream_get_port( session->stream[index], p_port);
+}
+
+/*
+ * Get statistics
+ */
+PJ_DEF(pj_status_t) pjmedia_session_get_stream_stat( pjmedia_session *session,
+ unsigned index,
+ pjmedia_rtcp_stat *stat)
+{
+ PJ_ASSERT_RETURN(session && stat && index < session->stream_cnt,
+ PJ_EINVAL);
+
+ return pjmedia_stream_get_stat(session->stream[index], stat);
+}
+
+
+/**
+ * Reset session statistics.
+ */
+PJ_DEF(pj_status_t) pjmedia_session_reset_stream_stat( pjmedia_session *session,
+ unsigned index)
+{
+ PJ_ASSERT_RETURN(session && index < session->stream_cnt, PJ_EINVAL);
+
+ return pjmedia_stream_reset_stat(session->stream[index]);
+}
+
+
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+/*
+ * Get extended statistics
+ */
+PJ_DEF(pj_status_t) pjmedia_session_get_stream_stat_xr(
+ pjmedia_session *session,
+ unsigned index,
+ pjmedia_rtcp_xr_stat *stat_xr)
+{
+ PJ_ASSERT_RETURN(session && stat_xr && index < session->stream_cnt,
+ PJ_EINVAL);
+
+ return pjmedia_stream_get_stat_xr(session->stream[index], stat_xr);
+}
+#endif
+
+PJ_DEF(pj_status_t) pjmedia_session_get_stream_stat_jbuf(
+ pjmedia_session *session,
+ unsigned index,
+ pjmedia_jb_state *state)
+{
+ PJ_ASSERT_RETURN(session && state && index < session->stream_cnt,
+ PJ_EINVAL);
+
+ return pjmedia_stream_get_stat_jbuf(session->stream[index], state);
+}
+
+/*
+ * Dial DTMF digit to the stream, using RFC 2833 mechanism.
+ */
+PJ_DEF(pj_status_t) pjmedia_session_dial_dtmf( pjmedia_session *session,
+ unsigned index,
+ const pj_str_t *ascii_digits )
+{
+ PJ_ASSERT_RETURN(session && ascii_digits, PJ_EINVAL);
+ return pjmedia_stream_dial_dtmf(session->stream[index], ascii_digits);
+}
+
+/*
+ * Check if the specified stream has received DTMF digits.
+ */
+PJ_DEF(pj_status_t) pjmedia_session_check_dtmf( pjmedia_session *session,
+ unsigned index )
+{
+ PJ_ASSERT_RETURN(session, PJ_EINVAL);
+ return pjmedia_stream_check_dtmf(session->stream[index]);
+}
+
+
+/*
+ * Retrieve DTMF digits from the specified stream.
+ */
+PJ_DEF(pj_status_t) pjmedia_session_get_dtmf( pjmedia_session *session,
+ unsigned index,
+ char *ascii_digits,
+ unsigned *size )
+{
+ PJ_ASSERT_RETURN(session && ascii_digits && size, PJ_EINVAL);
+ return pjmedia_stream_get_dtmf(session->stream[index], ascii_digits,
+ size);
+}
+
+/*
+ * Install DTMF callback.
+ */
+PJ_DEF(pj_status_t) pjmedia_session_set_dtmf_callback(pjmedia_session *session,
+ unsigned index,
+ void (*cb)(pjmedia_stream*,
+ void *user_data,
+ int digit),
+ void *user_data)
+{
+ PJ_ASSERT_RETURN(session && index < session->stream_cnt, PJ_EINVAL);
+ return pjmedia_stream_set_dtmf_callback(session->stream[index], cb,
+ user_data);
+}
+
diff --git a/pjmedia/src/pjmedia/silencedet.c b/pjmedia/src/pjmedia/silencedet.c
new file mode 100644
index 0000000..b6399ae
--- /dev/null
+++ b/pjmedia/src/pjmedia/silencedet.c
@@ -0,0 +1,333 @@
+/* $Id: silencedet.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/silencedet.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#define THIS_FILE "silencedet.c"
+
+#if 1
+# define TRACE_(x) PJ_LOG(5,x)
+#else
+# define TRACE_(x)
+#endif
+
+/**
+ * This enumeration specifies operation mode of silence detector
+ */
+typedef enum pjmedia_silence_det_mode {
+ VAD_MODE_NONE,
+ VAD_MODE_FIXED,
+ VAD_MODE_ADAPTIVE
+} pjmedia_silence_det_mode;
+
+/**
+ * Default settings
+ */
+#define DEF_RECALC_ON_VOICED 4000 /* Time to recalculate threshold
+ in voiced condition, in ms */
+#define DEF_RECALC_ON_SILENCE 2000 /* Time to recalculate threshold
+ in silence condition, in ms. */
+#define DEF_BEFORE_SILENCE 400 /* Silence time before really changing
+ state into SILENCE, in ms. */
+#define DEF_THRESHOLD 1000 /* Default threshold. */
+
+/**
+ * This enumeration specifies the states of the silence detector.
+ */
+enum pjmedia_silence_det_state {
+ STATE_SILENCE,
+ STATE_START_SILENCE,
+ STATE_VOICED
+};
+
+/**
+ * This structure holds the silence detector state.
+ */
+struct pjmedia_silence_det
+{
+ char objname[PJ_MAX_OBJ_NAME]; /**< VAD name. */
+
+ int mode; /**< VAD mode. */
+ unsigned ptime; /**< Frame time, in msec. */
+
+ unsigned threshold; /**< Current threshold level. */
+ unsigned sum_level; /**< Total sum of recent level. */
+ unsigned sum_cnt; /**< Number of level summed. */
+ unsigned silence_timer; /**< Silence condition timer. */
+ unsigned voiced_timer; /**< Voiced condition timer. */
+
+ enum pjmedia_silence_det_state state;/**< Silence detector state. */
+ unsigned recalc_on_voiced; /**< Setting of time to recalc
+ threshold in voiced condition. */
+ unsigned recalc_on_silence; /**< Setting of time to recalc
+ threshold in silence condition.*/
+ unsigned before_silence; /**< Setting of silence time before
+ really changing state into SILENCE,
+ in ms. */
+};
+
+
+
+PJ_DEF(pj_status_t) pjmedia_silence_det_create( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned samples_per_frame,
+ pjmedia_silence_det **p_sd)
+{
+ pjmedia_silence_det *sd;
+
+ PJ_ASSERT_RETURN(pool && p_sd, PJ_EINVAL);
+
+ sd = PJ_POOL_ZALLOC_T(pool, pjmedia_silence_det);
+
+ pj_ansi_snprintf(sd->objname, PJ_MAX_OBJ_NAME, "sd%p", sd);
+ sd->objname[PJ_MAX_OBJ_NAME-1] = '\0';
+
+ sd->ptime = samples_per_frame * 1000 / clock_rate;
+
+ /* Default settings */
+ pjmedia_silence_det_set_params(sd, -1, -1, -1);
+
+ /* Restart in adaptive, silent mode */
+ pjmedia_silence_det_set_adaptive( sd, -1 );
+
+ *p_sd = sd;
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_silence_det_set_name( pjmedia_silence_det *sd,
+ const char *name)
+{
+ PJ_ASSERT_RETURN(sd && name, PJ_EINVAL);
+
+ pj_ansi_snprintf(sd->objname, PJ_MAX_OBJ_NAME, name, sd);
+ sd->objname[PJ_MAX_OBJ_NAME-1] = '\0';
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_silence_det_set_adaptive(pjmedia_silence_det *sd,
+ int threshold)
+{
+ PJ_ASSERT_RETURN(sd, PJ_EINVAL);
+
+ if (threshold < 0)
+ threshold = DEF_THRESHOLD;
+
+ sd->mode = VAD_MODE_ADAPTIVE;
+ sd->threshold = threshold;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_silence_det_set_fixed( pjmedia_silence_det *sd,
+ int threshold )
+{
+ PJ_ASSERT_RETURN(sd, PJ_EINVAL);
+
+ if (threshold < 0)
+ threshold = DEF_THRESHOLD;
+
+ sd->mode = VAD_MODE_FIXED;
+ sd->threshold = threshold;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_silence_det_set_params( pjmedia_silence_det *sd,
+ int before_silence,
+ int recalc_time1,
+ int recalc_time2)
+{
+ PJ_ASSERT_RETURN(sd, PJ_EINVAL);
+
+ if (recalc_time1 < 0)
+ recalc_time1 = DEF_RECALC_ON_VOICED;
+ if (recalc_time2 < 0)
+ recalc_time2 = DEF_RECALC_ON_SILENCE;
+ if (before_silence < 0)
+ before_silence = DEF_BEFORE_SILENCE;
+
+ sd->recalc_on_voiced = recalc_time1;
+ sd->recalc_on_silence = recalc_time2;
+ sd->before_silence = before_silence;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_silence_det_disable( pjmedia_silence_det *sd )
+{
+ PJ_ASSERT_RETURN(sd, PJ_EINVAL);
+
+ sd->mode = VAD_MODE_NONE;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_int32_t) pjmedia_calc_avg_signal( const pj_int16_t samples[],
+ pj_size_t count)
+{
+ pj_uint32_t sum = 0;
+
+ const pj_int16_t * pcm = samples;
+ const pj_int16_t * end = samples + count;
+
+ if (count==0)
+ return 0;
+
+ while (pcm != end) {
+ if (*pcm < 0)
+ sum -= *pcm++;
+ else
+ sum += *pcm++;
+ }
+
+ return (pj_int32_t)(sum / count);
+}
+
+PJ_DEF(pj_bool_t) pjmedia_silence_det_apply( pjmedia_silence_det *sd,
+ pj_uint32_t level)
+{
+ int avg_recent_level;
+
+ if (sd->mode == VAD_MODE_NONE)
+ return PJ_FALSE;
+
+ if (sd->mode == VAD_MODE_FIXED)
+ return (level < sd->threshold);
+
+ /* Calculating recent level */
+ sd->sum_level += level;
+ ++sd->sum_cnt;
+ avg_recent_level = (sd->sum_level / sd->sum_cnt);
+
+ if (level > sd->threshold ||
+ level >= PJMEDIA_SILENCE_DET_MAX_THRESHOLD)
+ {
+ sd->silence_timer = 0;
+ sd->voiced_timer += sd->ptime;
+
+ switch(sd->state) {
+ case STATE_VOICED:
+ if (sd->voiced_timer > sd->recalc_on_voiced) {
+ /* Voiced for long time (>recalc_on_voiced), current
+ * threshold seems to be too low.
+ */
+ sd->threshold = (avg_recent_level + sd->threshold) >> 1;
+ TRACE_((THIS_FILE,"Re-adjust threshold (in talk burst)"
+ "to %d", sd->threshold));
+
+ sd->voiced_timer = 0;
+
+ /* Reset sig_level */
+ sd->sum_level = avg_recent_level;
+ sd->sum_cnt = 1;
+ }
+ break;
+
+ case STATE_SILENCE:
+ TRACE_((THIS_FILE,"Starting talk burst (level=%d threshold=%d)",
+ level, sd->threshold));
+
+ case STATE_START_SILENCE:
+ sd->state = STATE_VOICED;
+
+ /* Reset sig_level */
+ sd->sum_level = level;
+ sd->sum_cnt = 1;
+
+ break;
+
+ default:
+ pj_assert(0);
+ break;
+ }
+ } else {
+ sd->voiced_timer = 0;
+ sd->silence_timer += sd->ptime;
+
+ switch(sd->state) {
+ case STATE_SILENCE:
+ if (sd->silence_timer >= sd->recalc_on_silence) {
+ sd->threshold = avg_recent_level << 1;
+ TRACE_((THIS_FILE,"Re-adjust threshold (in silence)"
+ "to %d", sd->threshold));
+
+ sd->silence_timer = 0;
+
+ /* Reset sig_level */
+ sd->sum_level = avg_recent_level;
+ sd->sum_cnt = 1;
+ }
+ break;
+
+ case STATE_VOICED:
+ sd->state = STATE_START_SILENCE;
+
+ /* Reset sig_level */
+ sd->sum_level = level;
+ sd->sum_cnt = 1;
+
+ case STATE_START_SILENCE:
+ if (sd->silence_timer >= sd->before_silence) {
+ sd->state = STATE_SILENCE;
+ sd->threshold = avg_recent_level << 1;
+ TRACE_((THIS_FILE,"Starting silence (level=%d "
+ "threshold=%d)", level, sd->threshold));
+
+ /* Reset sig_level */
+ sd->sum_level = avg_recent_level;
+ sd->sum_cnt = 1;
+ }
+ break;
+
+ default:
+ pj_assert(0);
+ break;
+ }
+ }
+
+ return (sd->state == STATE_SILENCE);
+}
+
+
+PJ_DEF(pj_bool_t) pjmedia_silence_det_detect( pjmedia_silence_det *sd,
+ const pj_int16_t samples[],
+ pj_size_t count,
+ pj_int32_t *p_level)
+{
+ pj_uint32_t level;
+
+ /* Calculate average signal level. */
+ level = pjmedia_calc_avg_signal(samples, count);
+
+ /* Report to caller, if required. */
+ if (p_level)
+ *p_level = level;
+
+ return pjmedia_silence_det_apply(sd, level);
+}
+
diff --git a/pjmedia/src/pjmedia/sound_legacy.c b/pjmedia/src/pjmedia/sound_legacy.c
new file mode 100644
index 0000000..31215c0
--- /dev/null
+++ b/pjmedia/src/pjmedia/sound_legacy.c
@@ -0,0 +1,284 @@
+/* $Id: sound_legacy.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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
+ */
+
+/*
+ * This is implementation of legacy sound device API, for applications
+ * that still use the old/deprecated sound device API. This implementation
+ * uses the new Audio Device API.
+ *
+ * Please see http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more
+ * information.
+ */
+
+#include <pjmedia/sound.h>
+#include <pjmedia-audiodev/errno.h>
+#include <pj/assert.h>
+
+#if PJMEDIA_HAS_LEGACY_SOUND_API
+
+static struct legacy_subsys
+{
+ pjmedia_snd_dev_info info[4];
+ unsigned info_counter;
+ unsigned user_rec_latency;
+ unsigned user_play_latency;
+} g_sys;
+
+struct pjmedia_snd_stream
+{
+ pj_pool_t *pool;
+ pjmedia_aud_stream *aud_strm;
+ pjmedia_snd_rec_cb user_rec_cb;
+ pjmedia_snd_play_cb user_play_cb;
+ void *user_user_data;
+};
+
+PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory)
+{
+ return pjmedia_aud_subsys_init(factory);
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_deinit(void)
+{
+ return pjmedia_aud_subsys_shutdown();
+}
+
+PJ_DEF(int) pjmedia_snd_get_dev_count(void)
+{
+ return pjmedia_aud_dev_count();
+}
+
+PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index)
+{
+ pjmedia_snd_dev_info *oi = &g_sys.info[g_sys.info_counter];
+ pjmedia_aud_dev_info di;
+
+ g_sys.info_counter = (g_sys.info_counter+1) % PJ_ARRAY_SIZE(g_sys.info);
+
+ if (pjmedia_aud_dev_get_info(index, &di) != PJ_SUCCESS)
+ return NULL;
+
+ pj_bzero(oi, sizeof(*oi));
+ pj_ansi_strncpy(oi->name, di.name, sizeof(oi->name));
+ oi->name[sizeof(oi->name)-1] = '\0';
+ oi->input_count = di.input_count;
+ oi->output_count = di.output_count;
+ oi->default_samples_per_sec = di.default_samples_per_sec;
+
+ return oi;
+}
+
+
+static pj_status_t snd_play_cb(void *user_data,
+ pjmedia_frame *frame)
+{
+ pjmedia_snd_stream *strm = (pjmedia_snd_stream*)user_data;
+
+ frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ return strm->user_play_cb(strm->user_user_data,
+ frame->timestamp.u32.lo,
+ frame->buf,
+ frame->size);
+}
+
+static pj_status_t snd_rec_cb(void *user_data,
+ pjmedia_frame *frame)
+{
+ pjmedia_snd_stream *strm = (pjmedia_snd_stream*)user_data;
+ return strm->user_rec_cb(strm->user_user_data,
+ frame->timestamp.u32.lo,
+ frame->buf,
+ frame->size);
+}
+
+static pj_status_t open_stream( pjmedia_dir dir,
+ int rec_id,
+ int play_id,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ pjmedia_snd_rec_cb rec_cb,
+ pjmedia_snd_play_cb play_cb,
+ void *user_data,
+ pjmedia_snd_stream **p_snd_strm)
+{
+ pj_pool_t *pool;
+ pjmedia_snd_stream *snd_strm;
+ pjmedia_aud_param param;
+ pj_status_t status;
+
+ /* Initialize parameters */
+ if (dir & PJMEDIA_DIR_CAPTURE) {
+ status = pjmedia_aud_dev_default_param(rec_id, &param);
+ } else {
+ status = pjmedia_aud_dev_default_param(play_id, &param);
+ }
+ if (status != PJ_SUCCESS)
+ return status;
+
+ param.dir = dir;
+ param.rec_id = rec_id;
+ param.play_id = play_id;
+ param.clock_rate = clock_rate;
+ param.channel_count = channel_count;
+ param.samples_per_frame = samples_per_frame;
+ param.bits_per_sample = bits_per_sample;
+
+ /* Latencies setting */
+ if ((dir & PJMEDIA_DIR_CAPTURE) && g_sys.user_rec_latency) {
+ param.flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+ param.input_latency_ms = g_sys.user_rec_latency;
+ }
+ if ((dir & PJMEDIA_DIR_PLAYBACK) && g_sys.user_play_latency) {
+ param.flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+ param.output_latency_ms = g_sys.user_play_latency;
+ }
+
+ /* Create sound wrapper */
+ pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(),
+ "legacy-snd", 512, 512, NULL);
+ snd_strm = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_stream);
+ snd_strm->pool = pool;
+ snd_strm->user_rec_cb = rec_cb;
+ snd_strm->user_play_cb = play_cb;
+ snd_strm->user_user_data = user_data;
+
+ /* Create the stream */
+ status = pjmedia_aud_stream_create(&param, &snd_rec_cb,
+ &snd_play_cb, snd_strm,
+ &snd_strm->aud_strm);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(pool);
+ return status;
+ }
+
+ *p_snd_strm = snd_strm;
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ pjmedia_snd_rec_cb rec_cb,
+ void *user_data,
+ pjmedia_snd_stream **p_snd_strm)
+{
+ return open_stream(PJMEDIA_DIR_CAPTURE, index, PJMEDIA_AUD_INVALID_DEV,
+ clock_rate, channel_count, samples_per_frame,
+ bits_per_sample, rec_cb, NULL,
+ user_data, p_snd_strm);
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ pjmedia_snd_play_cb play_cb,
+ void *user_data,
+ pjmedia_snd_stream **p_snd_strm )
+{
+ return open_stream(PJMEDIA_DIR_PLAYBACK, PJMEDIA_AUD_INVALID_DEV, index,
+ clock_rate, channel_count, samples_per_frame,
+ bits_per_sample, NULL, play_cb,
+ user_data, p_snd_strm);
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id,
+ int play_id,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ pjmedia_snd_rec_cb rec_cb,
+ pjmedia_snd_play_cb play_cb,
+ void *user_data,
+ pjmedia_snd_stream **p_snd_strm)
+{
+ return open_stream(PJMEDIA_DIR_CAPTURE_PLAYBACK, rec_id, play_id,
+ clock_rate, channel_count, samples_per_frame,
+ bits_per_sample, rec_cb, play_cb,
+ user_data, p_snd_strm);
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream)
+{
+ return pjmedia_aud_stream_start(stream->aud_strm);
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream)
+{
+ return pjmedia_aud_stream_stop(stream->aud_strm);
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm,
+ pjmedia_snd_stream_info *pi)
+{
+ pjmedia_aud_param param;
+ pj_status_t status;
+
+ status = pjmedia_aud_stream_get_param(strm->aud_strm, &param);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_bzero(pi, sizeof(*pi));
+ pi->dir = param.dir;
+ pi->play_id = param.play_id;
+ pi->rec_id = param.rec_id;
+ pi->clock_rate = param.clock_rate;
+ pi->channel_count = param.channel_count;
+ pi->samples_per_frame = param.samples_per_frame;
+ pi->bits_per_sample = param.bits_per_sample;
+
+ if (param.flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) {
+ pi->rec_latency = param.input_latency_ms;
+ }
+ if (param.flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) {
+ pi->play_latency = param.output_latency_ms;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream)
+{
+ pj_status_t status;
+
+ status = pjmedia_aud_stream_destroy(stream->aud_strm);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_pool_release(stream->pool);
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency,
+ unsigned output_latency)
+{
+ g_sys.user_rec_latency = input_latency;
+ g_sys.user_play_latency = output_latency;
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_HAS_LEGACY_SOUND_API */
+
diff --git a/pjmedia/src/pjmedia/sound_port.c b/pjmedia/src/pjmedia/sound_port.c
new file mode 100644
index 0000000..4a475e7
--- /dev/null
+++ b/pjmedia/src/pjmedia/sound_port.c
@@ -0,0 +1,742 @@
+/* $Id: sound_port.c 4082 2012-04-24 13:09:14Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/sound_port.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pjmedia/delaybuf.h>
+#include <pjmedia/echo.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/rand.h>
+#include <pj/string.h> /* pj_memset() */
+
+#define AEC_TAIL 128 /* default AEC length in ms */
+#define AEC_SUSPEND_LIMIT 5 /* seconds of no activity */
+
+#define THIS_FILE "sound_port.c"
+
+//#define TEST_OVERFLOW_UNDERFLOW
+
+struct pjmedia_snd_port
+{
+ int rec_id;
+ int play_id;
+ pj_uint32_t aud_caps;
+ pjmedia_aud_param aud_param;
+ pjmedia_aud_stream *aud_stream;
+ pjmedia_dir dir;
+ pjmedia_port *port;
+
+ pjmedia_clock_src cap_clocksrc,
+ play_clocksrc;
+
+ unsigned clock_rate;
+ unsigned channel_count;
+ unsigned samples_per_frame;
+ unsigned bits_per_sample;
+ unsigned options;
+ unsigned prm_ec_options;
+
+ /* software ec */
+ pjmedia_echo_state *ec_state;
+ unsigned ec_options;
+ unsigned ec_tail_len;
+ pj_bool_t ec_suspended;
+ unsigned ec_suspend_count;
+ unsigned ec_suspend_limit;
+};
+
+/*
+ * The callback called by sound player when it needs more samples to be
+ * played.
+ */
+static pj_status_t play_cb(void *user_data, pjmedia_frame *frame)
+{
+ pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data;
+ pjmedia_port *port;
+ const unsigned required_size = frame->size;
+ pj_status_t status;
+
+ pjmedia_clock_src_update(&snd_port->play_clocksrc, &frame->timestamp);
+
+ port = snd_port->port;
+ if (port == NULL)
+ goto no_frame;
+
+ status = pjmedia_port_get_frame(port, frame);
+ if (status != PJ_SUCCESS)
+ goto no_frame;
+
+ if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO)
+ goto no_frame;
+
+ /* Must supply the required samples */
+ pj_assert(frame->size == required_size);
+
+ if (snd_port->ec_state) {
+ if (snd_port->ec_suspended) {
+ snd_port->ec_suspended = PJ_FALSE;
+ //pjmedia_echo_state_reset(snd_port->ec_state);
+ PJ_LOG(4,(THIS_FILE, "EC activated"));
+ }
+ snd_port->ec_suspend_count = 0;
+ pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)frame->buf);
+ }
+
+
+ return PJ_SUCCESS;
+
+no_frame:
+ frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame->size = required_size;
+ pj_bzero(frame->buf, frame->size);
+
+ if (snd_port->ec_state && !snd_port->ec_suspended) {
+ ++snd_port->ec_suspend_count;
+ if (snd_port->ec_suspend_count > snd_port->ec_suspend_limit) {
+ snd_port->ec_suspended = PJ_TRUE;
+ PJ_LOG(4,(THIS_FILE, "EC suspended because of inactivity"));
+ }
+ if (snd_port->ec_state) {
+ /* To maintain correct delay in EC */
+ pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)frame->buf);
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * The callback called by sound recorder when it has finished capturing a
+ * frame.
+ */
+static pj_status_t rec_cb(void *user_data, pjmedia_frame *frame)
+{
+ pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data;
+ pjmedia_port *port;
+
+ pjmedia_clock_src_update(&snd_port->cap_clocksrc, &frame->timestamp);
+
+ port = snd_port->port;
+ if (port == NULL)
+ return PJ_SUCCESS;
+
+ /* Cancel echo */
+ if (snd_port->ec_state && !snd_port->ec_suspended) {
+ pjmedia_echo_capture(snd_port->ec_state, (pj_int16_t*) frame->buf, 0);
+ }
+
+ pjmedia_port_put_frame(port, frame);
+
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * The callback called by sound player when it needs more samples to be
+ * played. This version is for non-PCM data.
+ */
+static pj_status_t play_cb_ext(void *user_data, pjmedia_frame *frame)
+{
+ pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data;
+ pjmedia_port *port = snd_port->port;
+
+ if (port == NULL) {
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+ return PJ_SUCCESS;
+ }
+
+ pjmedia_port_get_frame(port, frame);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * The callback called by sound recorder when it has finished capturing a
+ * frame. This version is for non-PCM data.
+ */
+static pj_status_t rec_cb_ext(void *user_data, pjmedia_frame *frame)
+{
+ pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data;
+ pjmedia_port *port;
+
+ port = snd_port->port;
+ if (port == NULL)
+ return PJ_SUCCESS;
+
+ pjmedia_port_put_frame(port, frame);
+
+ return PJ_SUCCESS;
+}
+
+/* Initialize with default values (zero) */
+PJ_DEF(void) pjmedia_snd_port_param_default(pjmedia_snd_port_param *prm)
+{
+ pj_bzero(prm, sizeof(*prm));
+}
+
+/*
+ * Start the sound stream.
+ * This may be called even when the sound stream has already been started.
+ */
+static pj_status_t start_sound_device( pj_pool_t *pool,
+ pjmedia_snd_port *snd_port )
+{
+ pjmedia_aud_rec_cb snd_rec_cb;
+ pjmedia_aud_play_cb snd_play_cb;
+ pjmedia_aud_param param_copy;
+ pj_status_t status;
+
+ /* Check if sound has been started. */
+ if (snd_port->aud_stream != NULL)
+ return PJ_SUCCESS;
+
+ PJ_ASSERT_RETURN(snd_port->dir == PJMEDIA_DIR_CAPTURE ||
+ snd_port->dir == PJMEDIA_DIR_PLAYBACK ||
+ snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK,
+ PJ_EBUG);
+
+ /* Get device caps */
+ if (snd_port->aud_param.dir & PJMEDIA_DIR_CAPTURE) {
+ pjmedia_aud_dev_info dev_info;
+
+ status = pjmedia_aud_dev_get_info(snd_port->aud_param.rec_id,
+ &dev_info);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ snd_port->aud_caps = dev_info.caps;
+ } else {
+ snd_port->aud_caps = 0;
+ }
+
+ /* Process EC settings */
+ pj_memcpy(&param_copy, &snd_port->aud_param, sizeof(param_copy));
+ if (param_copy.flags & PJMEDIA_AUD_DEV_CAP_EC) {
+ /* EC is wanted */
+ if ((snd_port->prm_ec_options & PJMEDIA_ECHO_USE_SW_ECHO) == 0 &&
+ snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC)
+ {
+ /* Device supports EC */
+ /* Nothing to do */
+ } else {
+ /* Application wants to use software EC or device
+ * doesn't support EC, remove EC settings from
+ * device parameters
+ */
+ param_copy.flags &= ~(PJMEDIA_AUD_DEV_CAP_EC |
+ PJMEDIA_AUD_DEV_CAP_EC_TAIL);
+ }
+ }
+
+ /* Use different callback if format is not PCM */
+ if (snd_port->aud_param.ext_fmt.id == PJMEDIA_FORMAT_L16) {
+ snd_rec_cb = &rec_cb;
+ snd_play_cb = &play_cb;
+ } else {
+ snd_rec_cb = &rec_cb_ext;
+ snd_play_cb = &play_cb_ext;
+ }
+
+ /* Open the device */
+ status = pjmedia_aud_stream_create(&param_copy,
+ snd_rec_cb,
+ snd_play_cb,
+ snd_port,
+ &snd_port->aud_stream);
+
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Inactivity limit before EC is suspended. */
+ snd_port->ec_suspend_limit = AEC_SUSPEND_LIMIT *
+ (snd_port->clock_rate /
+ snd_port->samples_per_frame);
+
+ /* Create software EC if parameter specifies EC and
+ * (app specifically requests software EC or device
+ * doesn't support EC). Only do this if the format is PCM!
+ */
+ if ((snd_port->aud_param.flags & PJMEDIA_AUD_DEV_CAP_EC) &&
+ ((snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC)==0 ||
+ (snd_port->prm_ec_options & PJMEDIA_ECHO_USE_SW_ECHO) != 0) &&
+ param_copy.ext_fmt.id == PJMEDIA_FORMAT_PCM)
+ {
+ if ((snd_port->aud_param.flags & PJMEDIA_AUD_DEV_CAP_EC_TAIL)==0) {
+ snd_port->aud_param.flags |= PJMEDIA_AUD_DEV_CAP_EC_TAIL;
+ snd_port->aud_param.ec_tail_ms = AEC_TAIL;
+ PJ_LOG(4,(THIS_FILE, "AEC tail is set to default %u ms",
+ snd_port->aud_param.ec_tail_ms));
+ }
+
+ status = pjmedia_snd_port_set_ec(snd_port, pool,
+ snd_port->aud_param.ec_tail_ms,
+ snd_port->prm_ec_options);
+ if (status != PJ_SUCCESS) {
+ pjmedia_aud_stream_destroy(snd_port->aud_stream);
+ snd_port->aud_stream = NULL;
+ return status;
+ }
+ }
+
+ /* Start sound stream. */
+ if (!(snd_port->options & PJMEDIA_SND_PORT_NO_AUTO_START)) {
+ status = pjmedia_aud_stream_start(snd_port->aud_stream);
+ }
+ if (status != PJ_SUCCESS) {
+ pjmedia_aud_stream_destroy(snd_port->aud_stream);
+ snd_port->aud_stream = NULL;
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Stop the sound device.
+ * This may be called even when there's no sound device in the port.
+ */
+static pj_status_t stop_sound_device( pjmedia_snd_port *snd_port )
+{
+ /* Check if we have sound stream device. */
+ if (snd_port->aud_stream) {
+ pjmedia_aud_stream_stop(snd_port->aud_stream);
+ pjmedia_aud_stream_destroy(snd_port->aud_stream);
+ snd_port->aud_stream = NULL;
+ }
+
+ /* Destroy AEC */
+ if (snd_port->ec_state) {
+ pjmedia_echo_destroy(snd_port->ec_state);
+ snd_port->ec_state = NULL;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create bidirectional port.
+ */
+PJ_DEF(pj_status_t) pjmedia_snd_port_create( pj_pool_t *pool,
+ int rec_id,
+ int play_id,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ unsigned options,
+ pjmedia_snd_port **p_port)
+{
+ pjmedia_snd_port_param param;
+ pj_status_t status;
+
+ pjmedia_snd_port_param_default(&param);
+
+ status = pjmedia_aud_dev_default_param(rec_id, &param.base);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ param.base.dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param.base.rec_id = rec_id;
+ param.base.play_id = play_id;
+ param.base.clock_rate = clock_rate;
+ param.base.channel_count = channel_count;
+ param.base.samples_per_frame = samples_per_frame;
+ param.base.bits_per_sample = bits_per_sample;
+ param.options = options;
+ param.ec_options = 0;
+
+ return pjmedia_snd_port_create2(pool, &param, p_port);
+}
+
+/*
+ * Create sound recorder AEC.
+ */
+PJ_DEF(pj_status_t) pjmedia_snd_port_create_rec( pj_pool_t *pool,
+ int dev_id,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ unsigned options,
+ pjmedia_snd_port **p_port)
+{
+ pjmedia_snd_port_param param;
+ pj_status_t status;
+
+ pjmedia_snd_port_param_default(&param);
+
+ status = pjmedia_aud_dev_default_param(dev_id, &param.base);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ param.base.dir = PJMEDIA_DIR_CAPTURE;
+ param.base.rec_id = dev_id;
+ param.base.clock_rate = clock_rate;
+ param.base.channel_count = channel_count;
+ param.base.samples_per_frame = samples_per_frame;
+ param.base.bits_per_sample = bits_per_sample;
+ param.options = options;
+ param.ec_options = 0;
+
+ return pjmedia_snd_port_create2(pool, &param, p_port);
+}
+
+
+/*
+ * Create sound player port.
+ */
+PJ_DEF(pj_status_t) pjmedia_snd_port_create_player( pj_pool_t *pool,
+ int dev_id,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ unsigned options,
+ pjmedia_snd_port **p_port)
+{
+ pjmedia_snd_port_param param;
+ pj_status_t status;
+
+ pjmedia_snd_port_param_default(&param);
+
+ status = pjmedia_aud_dev_default_param(dev_id, &param.base);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ param.base.dir = PJMEDIA_DIR_PLAYBACK;
+ param.base.play_id = dev_id;
+ param.base.clock_rate = clock_rate;
+ param.base.channel_count = channel_count;
+ param.base.samples_per_frame = samples_per_frame;
+ param.base.bits_per_sample = bits_per_sample;
+ param.options = options;
+ param.ec_options = 0;
+
+ return pjmedia_snd_port_create2(pool, &param, p_port);
+}
+
+
+/*
+ * Create sound port.
+ */
+PJ_DEF(pj_status_t) pjmedia_snd_port_create2(pj_pool_t *pool,
+ const pjmedia_snd_port_param *prm,
+ pjmedia_snd_port **p_port)
+{
+ pjmedia_snd_port *snd_port;
+ pj_status_t status;
+ unsigned ptime_usec;
+
+ PJ_ASSERT_RETURN(pool && prm && p_port, PJ_EINVAL);
+
+ snd_port = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_port);
+ PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM);
+
+ snd_port->dir = prm->base.dir;
+ snd_port->rec_id = prm->base.rec_id;
+ snd_port->play_id = prm->base.play_id;
+ snd_port->clock_rate = prm->base.clock_rate;
+ snd_port->channel_count = prm->base.channel_count;
+ snd_port->samples_per_frame = prm->base.samples_per_frame;
+ snd_port->bits_per_sample = prm->base.bits_per_sample;
+ pj_memcpy(&snd_port->aud_param, &prm->base, sizeof(snd_port->aud_param));
+ snd_port->options = prm->options;
+ snd_port->prm_ec_options = prm->ec_options;
+
+ ptime_usec = prm->base.samples_per_frame * 1000 / prm->base.channel_count /
+ prm->base.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.
+ * If there's no port connected, the sound callback will return
+ * empty signal.
+ */
+ status = start_sound_device( pool, snd_port );
+ if (status != PJ_SUCCESS) {
+ pjmedia_snd_port_destroy(snd_port);
+ return status;
+ }
+
+ *p_port = snd_port;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy port (also destroys the sound device).
+ */
+PJ_DEF(pj_status_t) pjmedia_snd_port_destroy(pjmedia_snd_port *snd_port)
+{
+ PJ_ASSERT_RETURN(snd_port, PJ_EINVAL);
+
+ return stop_sound_device(snd_port);
+}
+
+
+/*
+ * Retrieve the sound stream associated by this sound device port.
+ */
+PJ_DEF(pjmedia_aud_stream*) pjmedia_snd_port_get_snd_stream(
+ pjmedia_snd_port *snd_port)
+{
+ PJ_ASSERT_RETURN(snd_port, NULL);
+ return snd_port->aud_stream;
+}
+
+
+/*
+ * Change EC settings.
+ */
+PJ_DEF(pj_status_t) pjmedia_snd_port_set_ec( pjmedia_snd_port *snd_port,
+ pj_pool_t *pool,
+ unsigned tail_ms,
+ unsigned options)
+{
+ pjmedia_aud_param prm;
+ pj_status_t status;
+
+ /* Sound must be opened in full-duplex mode */
+ PJ_ASSERT_RETURN(snd_port &&
+ snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK,
+ PJ_EINVALIDOP);
+
+ /* Determine whether we use device or software EC */
+ if ((snd_port->prm_ec_options & PJMEDIA_ECHO_USE_SW_ECHO) == 0 &&
+ snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC)
+ {
+ /* We use device EC */
+ pj_bool_t ec_enabled;
+
+ /* Query EC status */
+ status = pjmedia_aud_stream_get_cap(snd_port->aud_stream,
+ PJMEDIA_AUD_DEV_CAP_EC,
+ &ec_enabled);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (tail_ms != 0) {
+ /* Change EC setting */
+
+ if (!ec_enabled) {
+ /* Enable EC first */
+ pj_bool_t value = PJ_TRUE;
+ status = pjmedia_aud_stream_set_cap(snd_port->aud_stream,
+ PJMEDIA_AUD_DEV_CAP_EC,
+ &value);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ if ((snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC_TAIL)==0) {
+ /* Device does not support setting EC tail */
+ return PJMEDIA_EAUD_INVCAP;
+ }
+
+ return pjmedia_aud_stream_set_cap(snd_port->aud_stream,
+ PJMEDIA_AUD_DEV_CAP_EC_TAIL,
+ &tail_ms);
+
+ } else if (ec_enabled) {
+ /* Disable EC */
+ pj_bool_t value = PJ_FALSE;
+ return pjmedia_aud_stream_set_cap(snd_port->aud_stream,
+ PJMEDIA_AUD_DEV_CAP_EC,
+ &value);
+ } else {
+ /* Request to disable EC but EC has been disabled */
+ /* Do nothing */
+ return PJ_SUCCESS;
+ }
+
+ } else {
+ /* We use software EC */
+
+ /* Check if there is change in parameters */
+ if (tail_ms==snd_port->ec_tail_len && options==snd_port->ec_options) {
+ PJ_LOG(5,(THIS_FILE, "pjmedia_snd_port_set_ec() ignored, no "
+ "change in settings"));
+ return PJ_SUCCESS;
+ }
+
+ status = pjmedia_aud_stream_get_param(snd_port->aud_stream, &prm);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Audio stream must be in PCM format */
+ PJ_ASSERT_RETURN(prm.ext_fmt.id == PJMEDIA_FORMAT_PCM,
+ PJ_EINVALIDOP);
+
+ /* Destroy AEC */
+ if (snd_port->ec_state) {
+ pjmedia_echo_destroy(snd_port->ec_state);
+ snd_port->ec_state = NULL;
+ }
+
+ if (tail_ms != 0) {
+ unsigned delay_ms;
+
+ //No need to add input latency in the latency calculation,
+ //since actual input latency should be zero.
+ //delay_ms = (si.rec_latency + si.play_latency) * 1000 /
+ // snd_port->clock_rate;
+ /* Set EC latency to 3/4 of output latency to reduce the
+ * possibility of missing/late reference frame.
+ */
+ delay_ms = prm.output_latency_ms * 3/4;
+ status = pjmedia_echo_create2(pool, snd_port->clock_rate,
+ snd_port->channel_count,
+ snd_port->samples_per_frame,
+ tail_ms, delay_ms,
+ options, &snd_port->ec_state);
+ if (status != PJ_SUCCESS)
+ snd_port->ec_state = NULL;
+ else
+ snd_port->ec_suspended = PJ_FALSE;
+ } else {
+ PJ_LOG(4,(THIS_FILE, "Echo canceller is now disabled in the "
+ "sound port"));
+ status = PJ_SUCCESS;
+ }
+
+ snd_port->ec_options = options;
+ snd_port->ec_tail_len = tail_ms;
+ }
+
+ return status;
+}
+
+
+/* Get AEC tail length */
+PJ_DEF(pj_status_t) pjmedia_snd_port_get_ec_tail( pjmedia_snd_port *snd_port,
+ unsigned *p_length)
+{
+ PJ_ASSERT_RETURN(snd_port && p_length, PJ_EINVAL);
+
+ /* Determine whether we use device or software EC */
+ if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC) {
+ /* We use device EC */
+ pj_bool_t ec_enabled;
+ pj_status_t status;
+
+ /* Query EC status */
+ status = pjmedia_aud_stream_get_cap(snd_port->aud_stream,
+ PJMEDIA_AUD_DEV_CAP_EC,
+ &ec_enabled);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (!ec_enabled) {
+ *p_length = 0;
+ } else if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC_TAIL) {
+ /* Get device EC tail */
+ status = pjmedia_aud_stream_get_cap(snd_port->aud_stream,
+ PJMEDIA_AUD_DEV_CAP_EC_TAIL,
+ p_length);
+ if (status != PJ_SUCCESS)
+ return status;
+ } else {
+ /* Just use default */
+ *p_length = AEC_TAIL;
+ }
+
+ } else {
+ /* We use software EC */
+ *p_length = snd_port->ec_state ? snd_port->ec_tail_len : 0;
+ }
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * 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.
+ */
+PJ_DEF(pj_status_t) pjmedia_snd_port_connect( pjmedia_snd_port *snd_port,
+ pjmedia_port *port)
+{
+ pjmedia_audio_format_detail *afd;
+
+ PJ_ASSERT_RETURN(snd_port && port, PJ_EINVAL);
+
+ afd = pjmedia_format_get_audio_format_detail(&port->info.fmt, PJ_TRUE);
+
+ /* Check that port has the same configuration as the sound device
+ * port.
+ */
+ if (afd->clock_rate != snd_port->clock_rate)
+ return PJMEDIA_ENCCLOCKRATE;
+
+ if (PJMEDIA_AFD_SPF(afd) != snd_port->samples_per_frame)
+ return PJMEDIA_ENCSAMPLESPFRAME;
+
+ if (afd->channel_count != snd_port->channel_count)
+ return PJMEDIA_ENCCHANNEL;
+
+ if (afd->bits_per_sample != snd_port->bits_per_sample)
+ return PJMEDIA_ENCBITS;
+
+ /* Port is okay. */
+ snd_port->port = port;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the connected port.
+ */
+PJ_DEF(pjmedia_port*) pjmedia_snd_port_get_port(pjmedia_snd_port *snd_port)
+{
+ PJ_ASSERT_RETURN(snd_port, NULL);
+ return snd_port->port;
+}
+
+
+/*
+ * Disconnect port.
+ */
+PJ_DEF(pj_status_t) pjmedia_snd_port_disconnect(pjmedia_snd_port *snd_port)
+{
+ PJ_ASSERT_RETURN(snd_port, PJ_EINVAL);
+
+ snd_port->port = NULL;
+
+ return PJ_SUCCESS;
+}
+
+
diff --git a/pjmedia/src/pjmedia/splitcomb.c b/pjmedia/src/pjmedia/splitcomb.c
new file mode 100644
index 0000000..dd2a944
--- /dev/null
+++ b/pjmedia/src/pjmedia/splitcomb.c
@@ -0,0 +1,807 @@
+/* $Id: splitcomb.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/splitcomb.h>
+#include <pjmedia/delaybuf.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+
+
+#define SIGNATURE PJMEDIA_SIG_PORT_SPLIT_COMB
+#define SIGNATURE_PORT PJMEDIA_SIG_PORT_SPLIT_COMB_P
+#define THIS_FILE "splitcomb.c"
+#define TMP_SAMP_TYPE pj_int16_t
+
+/* Maximum number of channels. */
+#define MAX_CHANNELS 16
+
+/* Maximum number of buffers to be accommodated by delaybuf */
+#define MAX_BUF_CNT PJMEDIA_SOUND_BUFFER_COUNT
+
+/* Maximum number of burst before we pause the media flow */
+#define MAX_BURST (buf_cnt + 6)
+
+/* Maximum number of NULL frames received before we pause the
+ * media flow.
+ */
+#define MAX_NULL_FRAMES (rport->max_burst)
+
+
+/* Operations */
+#define OP_PUT (1)
+#define OP_GET (-1)
+
+
+/*
+ * Media flow directions:
+ *
+ * put_frame() +-----+
+ * UPSTREAM ------------>|split|<--> DOWNSTREAM
+ * <------------|comb |
+ * get_frame() +-----+
+ *
+ */
+enum sc_dir
+{
+ /* This is the media direction from the splitcomb to the
+ * downstream port(s), which happens when:
+ * - put_frame() is called to the splitcomb
+ * - get_frame() is called to the reverse channel port.
+ */
+ DIR_DOWNSTREAM,
+
+ /* This is the media direction from the downstream port to
+ * the splitcomb, which happens when:
+ * - get_frame() is called to the splitcomb
+ * - put_frame() is called to the reverse channel port.
+ */
+ DIR_UPSTREAM
+};
+
+
+
+/*
+ * This structure describes the splitter/combiner.
+ */
+struct splitcomb
+{
+ pjmedia_port base;
+
+ unsigned options;
+
+ /* Array of ports, one for each channel */
+ struct {
+ pjmedia_port *port;
+ pj_bool_t reversed;
+ } port_desc[MAX_CHANNELS];
+
+ /* Temporary buffers needed to extract mono frame from
+ * multichannel frame. We could use stack for this, but this
+ * way it should be safer for devices with small stack size.
+ */
+ TMP_SAMP_TYPE *get_buf;
+ TMP_SAMP_TYPE *put_buf;
+};
+
+
+/*
+ * This structure describes reverse port.
+ */
+struct reverse_port
+{
+ pjmedia_port base;
+ struct splitcomb*parent;
+ unsigned ch_num;
+
+ /* Maximum burst before media flow is suspended.
+ * With reverse port, it's possible that either end of the
+ * port doesn't actually process the media flow (meaning, it
+ * stops calling get_frame()/put_frame()). When this happens,
+ * the other end will encounter excessive underflow or overflow,
+ * depending on which direction is not actively processed by
+ * the stopping end.
+ *
+ * To avoid excessive underflow/overflow, the media flow will
+ * be suspended once underflow/overflow goes over this max_burst
+ * limit.
+ */
+ int max_burst;
+
+ /* When the media interface port of the splitcomb or the reverse
+ * channel port is registered to conference bridge, the bridge
+ * will transmit NULL frames to the media port when the media
+ * port is not receiving any audio from other slots (for example,
+ * when no other slots are connected to the media port).
+ *
+ * When this happens, we will generate zero frame to our buffer,
+ * to avoid underflow/overflow. But after too many NULL frames
+ * are received, we will pause the media flow instead, to save
+ * some processing.
+ *
+ * This value controls how many NULL frames can be received
+ * before we suspend media flow for a particular direction.
+ */
+ unsigned max_null_frames;
+
+ /* A reverse port need a temporary buffer to store frames
+ * (because of the different phase, see splitcomb.h for details).
+ * Since we can not expect get_frame() and put_frame() to be
+ * called evenly one after another, we use delay buffers to
+ * accomodate the burst.
+ *
+ * We maintain state for each direction, hence the array. The
+ * array is indexed by direction (sc_dir).
+ */
+ struct {
+
+ /* The delay buffer where frames will be stored */
+ pjmedia_delay_buf *dbuf;
+
+ /* Flag to indicate that audio flow on this direction
+ * is currently being suspended (perhaps because nothing
+ * is processing the frame on the other end).
+ */
+ pj_bool_t paused;
+
+ /* Operation level. When the level exceeds a maximum value,
+ * the media flow on this direction will be paused.
+ */
+ int level;
+
+ /* Timestamp. */
+ pj_timestamp ts;
+
+ /* Number of NULL frames transmitted to this port so far.
+ * NULL frame indicate that nothing is transmitted, and
+ * once we get too many of this, we should pause the media
+ * flow to reduce processing.
+ */
+ unsigned null_cnt;
+
+ } buf[2];
+
+ /* Must have temporary put buffer for the delay buf,
+ * unfortunately.
+ */
+ pj_int16_t *tmp_up_buf;
+};
+
+
+/*
+ * Prototypes.
+ */
+static pj_status_t put_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t on_destroy(pjmedia_port *this_port);
+
+static pj_status_t rport_put_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t rport_get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t rport_on_destroy(pjmedia_port *this_port);
+
+
+/*
+ * Create the splitter/combiner.
+ */
+PJ_DEF(pj_status_t) pjmedia_splitcomb_create( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ unsigned options,
+ pjmedia_port **p_splitcomb)
+{
+ const pj_str_t name = pj_str("splitcomb");
+ struct splitcomb *sc;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(pool && clock_rate && channel_count &&
+ samples_per_frame && bits_per_sample &&
+ p_splitcomb, PJ_EINVAL);
+
+ /* Only supports 16 bits per sample */
+ PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL);
+
+ *p_splitcomb = NULL;
+
+ /* Create the splitter/combiner structure */
+ sc = PJ_POOL_ZALLOC_T(pool, struct splitcomb);
+ PJ_ASSERT_RETURN(sc != NULL, PJ_ENOMEM);
+
+ /* Create temporary buffers */
+ sc->get_buf = (TMP_SAMP_TYPE*)
+ pj_pool_alloc(pool, samples_per_frame *
+ sizeof(TMP_SAMP_TYPE) /
+ channel_count);
+ PJ_ASSERT_RETURN(sc->get_buf, PJ_ENOMEM);
+
+ sc->put_buf = (TMP_SAMP_TYPE*)
+ pj_pool_alloc(pool, samples_per_frame *
+ sizeof(TMP_SAMP_TYPE) /
+ channel_count);
+ PJ_ASSERT_RETURN(sc->put_buf, PJ_ENOMEM);
+
+
+ /* Save options */
+ sc->options = options;
+
+ /* Initialize port */
+ pjmedia_port_info_init(&sc->base.info, &name, SIGNATURE, clock_rate,
+ channel_count, bits_per_sample, samples_per_frame);
+
+ sc->base.put_frame = &put_frame;
+ sc->base.get_frame = &get_frame;
+ sc->base.on_destroy = &on_destroy;
+
+ /* Init ports array */
+ /*
+ sc->port_desc = pj_pool_zalloc(pool, channel_count*sizeof(*sc->port_desc));
+ */
+ pj_bzero(sc->port_desc, sizeof(sc->port_desc));
+
+ /* Done for now */
+ *p_splitcomb = &sc->base;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Attach media port with the same phase as the splitter/combiner.
+ */
+PJ_DEF(pj_status_t) pjmedia_splitcomb_set_channel( pjmedia_port *splitcomb,
+ unsigned ch_num,
+ unsigned options,
+ pjmedia_port *port)
+{
+ struct splitcomb *sc = (struct splitcomb*) splitcomb;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(splitcomb && port, 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->port_desc[ch_num].port = port;
+ sc->port_desc[ch_num].reversed = PJ_FALSE;
+
+ 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;
+}
+
+
+/*
+ * Extract one mono frame from a multichannel frame.
+ */
+static void extract_mono_frame( const pj_int16_t *in,
+ pj_int16_t *out,
+ unsigned ch,
+ unsigned ch_cnt,
+ unsigned samples_count)
+{
+ unsigned i;
+
+ in += ch;
+ for (i=0; i<samples_count; ++i) {
+ *out++ = *in;
+ in += ch_cnt;
+ }
+}
+
+
+/*
+ * Put one mono frame into a multichannel frame
+ */
+static void store_mono_frame( const pj_int16_t *in,
+ pj_int16_t *out,
+ unsigned ch,
+ unsigned ch_cnt,
+ unsigned samples_count)
+{
+ unsigned i;
+
+ out += ch;
+ for (i=0; i<samples_count; ++i) {
+ *out = *in++;
+ out += ch_cnt;
+ }
+}
+
+/* Update operation on the specified direction */
+static void op_update(struct reverse_port *rport, int dir, int op)
+{
+ char *dir_name[2] = {"downstream", "upstream"};
+
+ rport->buf[dir].level += op;
+
+ if (op == OP_PUT) {
+ rport->buf[dir].ts.u64 += PJMEDIA_PIA_SPF(&rport->base.info);
+ }
+
+ if (rport->buf[dir].paused) {
+ if (rport->buf[dir].level < -rport->max_burst) {
+ /* Prevent the level from overflowing and resets back to zero */
+ rport->buf[dir].level = -rport->max_burst;
+ } else if (rport->buf[dir].level > rport->max_burst) {
+ /* Prevent the level from overflowing and resets back to zero */
+ rport->buf[dir].level = rport->max_burst;
+ } else {
+ /* Level has fallen below max level, we can resume
+ * media flow.
+ */
+ PJ_LOG(5,(rport->base.info.name.ptr,
+ "Resuming media flow on %s direction (level=%d)",
+ dir_name[dir], rport->buf[dir].level));
+ rport->buf[dir].level = 0;
+ rport->buf[dir].paused = PJ_FALSE;
+
+ //This will cause disruption in audio, and it seems to be
+ //working fine without this anyway, so we disable it for now.
+ //pjmedia_delay_buf_learn(rport->buf[dir].dbuf);
+
+ }
+ } else {
+ if (rport->buf[dir].level >= rport->max_burst ||
+ rport->buf[dir].level <= -rport->max_burst)
+ {
+ /* Level has reached maximum level, the other side of
+ * rport is not sending/retrieving frames. Pause the
+ * rport on this direction.
+ */
+ PJ_LOG(5,(rport->base.info.name.ptr,
+ "Pausing media flow on %s direction (level=%d)",
+ dir_name[dir], rport->buf[dir].level));
+ rport->buf[dir].paused = PJ_TRUE;
+ }
+ }
+}
+
+
+/*
+ * "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(&this_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),
+ 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;
+}
+
+
+/*
+ * 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;
+}
+
+
+static pj_status_t on_destroy(pjmedia_port *this_port)
+{
+ /* Nothing to do for the splitcomb
+ * Reverse ports must be destroyed separately.
+ */
+ PJ_UNUSED_ARG(this_port);
+
+ 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 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);
+}
+
+
+static pj_status_t rport_on_destroy(pjmedia_port *this_port)
+{
+ struct reverse_port *rport = (struct reverse_port*) this_port;
+
+ pjmedia_delay_buf_destroy(rport->buf[DIR_DOWNSTREAM].dbuf);
+ pjmedia_delay_buf_destroy(rport->buf[DIR_UPSTREAM].dbuf);
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjmedia/src/pjmedia/stereo_port.c b/pjmedia/src/pjmedia/stereo_port.c
new file mode 100644
index 0000000..0dd2fb7
--- /dev/null
+++ b/pjmedia/src/pjmedia/stereo_port.c
@@ -0,0 +1,227 @@
+/* $Id: stereo_port.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/stereo.h>
+#include <pj/assert.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#define SIGNATURE PJMEDIA_SIG_PORT_STEREO
+
+
+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,
+ 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(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;
+}
+
+static pj_status_t stereo_put_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;
+
+ /* Return if we don't have downstream port. */
+ if (sport->dn_port == NULL) {
+ 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);
+
+ if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) {
+ tmp_frame.buf = sport->put_buf;
+ if (dn_afd->channel_count == 1) {
+ pjmedia_convert_channel_nto1((pj_int16_t*)tmp_frame.buf,
+ (const pj_int16_t*)frame->buf,
+ s_afd->channel_count,
+ PJMEDIA_AFD_SPF(s_afd),
+ (sport->options & PJMEDIA_STEREO_MIX),
+ 0);
+ } else {
+ pjmedia_convert_channel_1ton((pj_int16_t*)tmp_frame.buf,
+ (const pj_int16_t*)frame->buf,
+ dn_afd->channel_count,
+ PJMEDIA_AFD_SPF(s_afd),
+ sport->options);
+ }
+ tmp_frame.size = PJMEDIA_AFD_AVG_FSZ(dn_afd);
+ } 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;
+ 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 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/pjmedia/src/pjmedia/stream.c b/pjmedia/src/pjmedia/stream.c
new file mode 100644
index 0000000..5c7c873
--- /dev/null
+++ b/pjmedia/src/pjmedia/stream.c
@@ -0,0 +1,2803 @@
+/* $Id: stream.c 4120 2012-05-12 07:18:09Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/stream.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/rtp.h>
+#include <pjmedia/rtcp.h>
+#include <pjmedia/jbuf.h>
+#include <pjmedia/stream_common.h>
+#include <pj/array.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+#include <pj/compat/socket.h>
+#include <pj/errno.h>
+#include <pj/ioqueue.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/rand.h>
+#include <pj/sock_select.h>
+#include <pj/string.h> /* memcpy() */
+
+
+#define THIS_FILE "stream.c"
+#define ERRLEVEL 1
+#define LOGERR_(expr) stream_perror expr
+#define TRC_(expr) PJ_LOG(5,expr)
+
+#define BYTES_PER_SAMPLE 2
+
+/* Limit the number of synthetic audio samples that are generated by PLC.
+ * Normally PLC should have it's own means to limit the number of
+ * synthetic frames, so we need to set this to a reasonably large value
+ * just as precaution
+ */
+#define MAX_PLC_MSEC PJMEDIA_MAX_PLC_DURATION_MSEC
+
+
+/* Tracing jitter buffer operations in a stream session to a CSV file.
+ * The trace will contain JB operation timestamp, frame info, RTP info, and
+ * the JB state right after the operation.
+ */
+#define TRACE_JB 0 /* Enable/disable trace. */
+#define TRACE_JB_PATH_PREFIX "" /* Optional path/prefix
+ for the CSV filename. */
+#if TRACE_JB
+# include <pj/file_io.h>
+# define TRACE_JB_INVALID_FD ((pj_oshandle_t)-1)
+# define TRACE_JB_OPENED(s) (s->trace_jb_fd != TRACE_JB_INVALID_FD)
+#endif
+
+#ifndef PJMEDIA_STREAM_SIZE
+# define PJMEDIA_STREAM_SIZE 1000
+#endif
+
+#ifndef PJMEDIA_STREAM_INC
+# define PJMEDIA_STREAM_INC 1000
+#endif
+
+
+/**
+ * Media channel.
+ */
+struct pjmedia_channel
+{
+ pjmedia_stream *stream; /**< Parent stream. */
+ pjmedia_dir dir; /**< Channel direction. */
+ unsigned pt; /**< Payload type. */
+ pj_bool_t paused; /**< Paused?. */
+ unsigned out_pkt_size; /**< Size of output buffer. */
+ void *out_pkt; /**< Output buffer. */
+ unsigned out_pkt_len; /**< Length of data in buffer. */
+ pjmedia_rtp_session rtp; /**< RTP session. */
+};
+
+
+struct dtmf
+{
+ int event;
+ pj_uint32_t duration;
+};
+
+/**
+ * This structure describes media stream.
+ * A media stream is bidirectional media transmission between two endpoints.
+ * It consists of two channels, i.e. encoding and decoding channels.
+ * A media stream corresponds to a single "m=" line in a SDP session
+ * description.
+ */
+struct pjmedia_stream
+{
+ pjmedia_endpt *endpt; /**< Media endpoint. */
+ pjmedia_codec_mgr *codec_mgr; /**< Codec manager instance. */
+ pjmedia_stream_info si; /**< Creation parameter. */
+ pjmedia_port port; /**< Port interface. */
+ pjmedia_channel *enc; /**< Encoding channel. */
+ pjmedia_channel *dec; /**< Decoding channel. */
+
+ pj_pool_t *own_pool; /**< Only created if not given */
+
+ pjmedia_dir dir; /**< Stream direction. */
+ void *user_data; /**< User data. */
+ pj_str_t cname; /**< SDES CNAME */
+
+ pjmedia_transport *transport; /**< Stream transport. */
+
+ pjmedia_codec *codec; /**< Codec instance being used. */
+ pjmedia_codec_param codec_param; /**< Codec param. */
+ pj_int16_t *enc_buf; /**< Encoding buffer, when enc's
+ ptime is different than dec.
+ Otherwise it's NULL. */
+
+ unsigned enc_samples_per_pkt;
+ unsigned enc_buf_size; /**< Encoding buffer size, in
+ samples. */
+ unsigned enc_buf_pos; /**< First position in buf. */
+ unsigned enc_buf_count; /**< Number of samples in the
+ encoding buffer. */
+
+ unsigned plc_cnt; /**< # of consecutive PLC frames*/
+ unsigned max_plc_cnt; /**< Max # of PLC frames */
+
+ unsigned vad_enabled; /**< VAD enabled in param. */
+ unsigned frame_size; /**< Size of encoded base frame.*/
+ pj_bool_t is_streaming; /**< Currently streaming?. This
+ is used to put RTP marker
+ bit. */
+ pj_uint32_t ts_vad_disabled;/**< TS when VAD was disabled. */
+ pj_uint32_t tx_duration; /**< TX duration in timestamp. */
+
+ pj_mutex_t *jb_mutex;
+ pjmedia_jbuf *jb; /**< Jitter buffer. */
+ char jb_last_frm; /**< Last frame type from jb */
+ unsigned jb_last_frm_cnt;/**< Last JB frame type counter*/
+
+ pjmedia_rtcp_session rtcp; /**< RTCP for incoming RTP. */
+
+ pj_uint32_t rtcp_last_tx; /**< RTCP tx time in timestamp */
+ pj_uint32_t rtcp_interval; /**< Interval, in timestamp. */
+ pj_bool_t initial_rr; /**< Initial RTCP RR sent */
+ pj_bool_t rtcp_sdes_bye_disabled;/**< Send RTCP SDES/BYE?*/
+ void *out_rtcp_pkt; /**< Outgoing RTCP packet. */
+ unsigned out_rtcp_pkt_size;
+ /**< Outgoing RTCP packet size. */
+
+ /* RFC 2833 DTMF transmission queue: */
+ int tx_event_pt; /**< Outgoing pt for dtmf. */
+ int tx_dtmf_count; /**< # of digits in tx dtmf buf.*/
+ struct dtmf tx_dtmf_buf[32];/**< Outgoing dtmf queue. */
+
+ /* Incoming DTMF: */
+ int rx_event_pt; /**< Incoming pt for dtmf. */
+ int last_dtmf; /**< Current digit, or -1. */
+ pj_uint32_t last_dtmf_dur; /**< Start ts for cur digit. */
+ unsigned rx_dtmf_count; /**< # of digits in dtmf rx buf.*/
+ char rx_dtmf_buf[32];/**< Incoming DTMF buffer. */
+
+ /* DTMF callback */
+ void (*dtmf_cb)(pjmedia_stream*, void*, int);
+ void *dtmf_cb_user_data;
+
+#if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG!=0)
+ /* Enable support to handle codecs with inconsistent clock rate
+ * between clock rate in SDP/RTP & the clock rate that is actually used.
+ * This happens for example with G.722 and MPEG audio codecs.
+ */
+ pj_bool_t has_g722_mpeg_bug;
+ /**< Flag to specify whether
+ normalization process
+ is needed */
+ unsigned rtp_tx_ts_len_per_pkt;
+ /**< Normalized ts length per packet
+ transmitted according to
+ 'erroneous' definition */
+ unsigned rtp_rx_ts_len_per_frame;
+ /**< Normalized ts length per frame
+ received according to
+ 'erroneous' definition */
+ unsigned rtp_rx_last_cnt;/**< Nb of frames in last pkt */
+ unsigned rtp_rx_check_cnt;
+ /**< Counter of remote timestamp
+ checking */
+#endif
+
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+ pj_uint32_t rtcp_xr_last_tx; /**< RTCP XR tx time
+ in timestamp. */
+ pj_uint32_t rtcp_xr_interval; /**< Interval, in timestamp. */
+ pj_sockaddr rtcp_xr_dest; /**< Additional remote RTCP XR
+ dest. If sin_family is
+ zero, it will be ignored*/
+ unsigned rtcp_xr_dest_len; /**< Length of RTCP XR dest
+ address */
+#endif
+
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
+ pj_bool_t use_ka; /**< Stream keep-alive with non-
+ codec-VAD mechanism is
+ enabled? */
+ pj_timestamp last_frm_ts_sent; /**< Timestamp of last sending
+ packet */
+#endif
+
+#if TRACE_JB
+ pj_oshandle_t trace_jb_fd; /**< Jitter tracing file handle.*/
+ char *trace_jb_buf; /**< Jitter tracing buffer. */
+#endif
+
+ pj_uint32_t rtp_rx_last_ts; /**< Last received RTP timestamp*/
+};
+
+
+/* RFC 2833 digit */
+static const char digitmap[16] = { '0', '1', '2', '3',
+ '4', '5', '6', '7',
+ '8', '9', '*', '#',
+ 'A', 'B', 'C', 'D'};
+
+/* Zero audio frame samples */
+static pj_int16_t zero_frame[2 * 30 * 16000 / 1000];
+
+/*
+ * Print error.
+ */
+static void stream_perror(const char *sender, const char *title,
+ pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(sender, "%s: %s [err:%d]", title, errmsg, status));
+}
+
+
+static pj_status_t send_rtcp(pjmedia_stream *stream,
+ pj_bool_t with_sdes,
+ pj_bool_t with_bye,
+ pj_bool_t with_xr);
+
+
+#if TRACE_JB
+
+PJ_INLINE(int) trace_jb_print_timestamp(char **buf, pj_ssize_t len)
+{
+ pj_time_val now;
+ pj_parsed_time ptime;
+ char *p = *buf;
+
+ if (len < 14)
+ return -1;
+
+ pj_gettimeofday(&now);
+ pj_time_decode(&now, &ptime);
+ p += pj_utoa_pad(ptime.hour, p, 2, '0');
+ *p++ = ':';
+ p += pj_utoa_pad(ptime.min, p, 2, '0');
+ *p++ = ':';
+ p += pj_utoa_pad(ptime.sec, p, 2, '0');
+ *p++ = '.';
+ p += pj_utoa_pad(ptime.msec, p, 3, '0');
+ *p++ = ',';
+
+ *buf = p;
+
+ return 0;
+}
+
+PJ_INLINE(int) trace_jb_print_state(pjmedia_stream *stream,
+ char **buf, pj_ssize_t len)
+{
+ char *p = *buf;
+ char *endp = *buf + len;
+ pjmedia_jb_state state;
+
+ pjmedia_jbuf_get_state(stream->jb, &state);
+
+ len = pj_ansi_snprintf(p, endp-p, "%d, %d, %d",
+ state.size, state.burst, state.prefetch);
+ if ((len < 0) || (len >= endp-p))
+ return -1;
+
+ p += len;
+ *buf = p;
+ return 0;
+}
+
+static void trace_jb_get(pjmedia_stream *stream, pjmedia_jb_frame_type ft,
+ pj_size_t fsize)
+{
+ char *p = stream->trace_jb_buf;
+ char *endp = stream->trace_jb_buf + PJ_LOG_MAX_SIZE;
+ pj_ssize_t len = 0;
+ const char* ft_st;
+
+ if (!TRACE_JB_OPENED(stream))
+ return;
+
+ /* Print timestamp. */
+ if (trace_jb_print_timestamp(&p, endp-p))
+ goto on_insuff_buffer;
+
+ /* Print frame type and size */
+ switch(ft) {
+ case PJMEDIA_JB_MISSING_FRAME:
+ ft_st = "missing";
+ break;
+ case PJMEDIA_JB_NORMAL_FRAME:
+ ft_st = "normal";
+ break;
+ case PJMEDIA_JB_ZERO_PREFETCH_FRAME:
+ ft_st = "prefetch";
+ break;
+ case PJMEDIA_JB_ZERO_EMPTY_FRAME:
+ ft_st = "empty";
+ break;
+ default:
+ ft_st = "unknown";
+ break;
+ }
+
+ /* Print operation, size, frame count, frame type */
+ len = pj_ansi_snprintf(p, endp-p, "GET,%d,1,%s,,,,", fsize, ft_st);
+ if ((len < 0) || (len >= endp-p))
+ goto on_insuff_buffer;
+ p += len;
+
+ /* Print JB state */
+ if (trace_jb_print_state(stream, &p, endp-p))
+ goto on_insuff_buffer;
+
+ /* Print end of line */
+ if (endp-p < 2)
+ goto on_insuff_buffer;
+ *p++ = '\n';
+
+ /* Write and flush */
+ len = p - stream->trace_jb_buf;
+ pj_file_write(stream->trace_jb_fd, stream->trace_jb_buf, &len);
+ pj_file_flush(stream->trace_jb_fd);
+ return;
+
+on_insuff_buffer:
+ pj_assert(!"Trace buffer too small, check PJ_LOG_MAX_SIZE!");
+}
+
+static void trace_jb_put(pjmedia_stream *stream, const pjmedia_rtp_hdr *hdr,
+ unsigned payloadlen, unsigned frame_cnt)
+{
+ char *p = stream->trace_jb_buf;
+ char *endp = stream->trace_jb_buf + PJ_LOG_MAX_SIZE;
+ pj_ssize_t len = 0;
+
+ if (!TRACE_JB_OPENED(stream))
+ return;
+
+ /* Print timestamp. */
+ if (trace_jb_print_timestamp(&p, endp-p))
+ goto on_insuff_buffer;
+
+ /* Print operation, size, frame count, RTP info */
+ len = pj_ansi_snprintf(p, endp-p,
+ "PUT,%d,%d,,%d,%d,%d,",
+ payloadlen, frame_cnt,
+ pj_ntohs(hdr->seq), pj_ntohl(hdr->ts), hdr->m);
+ if ((len < 0) || (len >= endp-p))
+ goto on_insuff_buffer;
+ p += len;
+
+ /* Print JB state */
+ if (trace_jb_print_state(stream, &p, endp-p))
+ goto on_insuff_buffer;
+
+ /* Print end of line */
+ if (endp-p < 2)
+ goto on_insuff_buffer;
+ *p++ = '\n';
+
+ /* Write and flush */
+ len = p - stream->trace_jb_buf;
+ pj_file_write(stream->trace_jb_fd, stream->trace_jb_buf, &len);
+ pj_file_flush(stream->trace_jb_fd);
+ return;
+
+on_insuff_buffer:
+ pj_assert(!"Trace buffer too small, check PJ_LOG_MAX_SIZE!");
+}
+
+#endif /* TRACE_JB */
+
+
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA != 0
+/*
+ * Send keep-alive packet using non-codec frame.
+ */
+static void send_keep_alive_packet(pjmedia_stream *stream)
+{
+#if PJMEDIA_STREAM_ENABLE_KA == PJMEDIA_STREAM_KA_EMPTY_RTP
+
+ /* Keep-alive packet is empty RTP */
+ pj_status_t status;
+ void *pkt;
+ int pkt_len;
+
+ TRC_((stream->port.info.name.ptr,
+ "Sending keep-alive (RTCP and empty RTP)"));
+
+ /* Send RTP */
+ status = pjmedia_rtp_encode_rtp( &stream->enc->rtp,
+ stream->enc->pt, 0,
+ 1,
+ 0,
+ (const void**)&pkt,
+ &pkt_len);
+ pj_assert(status == PJ_SUCCESS);
+
+ pj_memcpy(stream->enc->out_pkt, pkt, pkt_len);
+ pjmedia_transport_send_rtp(stream->transport, stream->enc->out_pkt,
+ pkt_len);
+
+ /* Send RTCP */
+ send_rtcp(stream, PJ_TRUE, PJ_FALSE, PJ_FALSE);
+
+#elif PJMEDIA_STREAM_ENABLE_KA == PJMEDIA_STREAM_KA_USER
+
+ /* Keep-alive packet is defined in PJMEDIA_STREAM_KA_USER_PKT */
+ int pkt_len;
+ const pj_str_t str_ka = PJMEDIA_STREAM_KA_USER_PKT;
+
+ TRC_((stream->port.info.name.ptr,
+ "Sending keep-alive (custom RTP/RTCP packets)"));
+
+ /* Send to RTP port */
+ pj_memcpy(stream->enc->out_pkt, str_ka.ptr, str_ka.slen);
+ pkt_len = str_ka.slen;
+ pjmedia_transport_send_rtp(stream->transport, stream->enc->out_pkt,
+ pkt_len);
+
+ /* Send to RTCP port */
+ pjmedia_transport_send_rtcp(stream->transport, stream->enc->out_pkt,
+ pkt_len);
+
+#else
+
+ PJ_UNUSED_ARG(stream);
+
+#endif
+}
+#endif /* defined(PJMEDIA_STREAM_ENABLE_KA) */
+
+/*
+ * play_callback()
+ *
+ * This callback is called by sound device's player thread when it
+ * needs to feed the player with some frames.
+ */
+static pj_status_t get_frame( pjmedia_port *port, pjmedia_frame *frame)
+{
+ pjmedia_stream *stream = (pjmedia_stream*) port->port_data.pdata;
+ pjmedia_channel *channel = stream->dec;
+ unsigned samples_count, samples_per_frame, samples_required;
+ pj_int16_t *p_out_samp;
+ pj_status_t status;
+
+
+ /* Return no frame is channel is paused */
+ if (channel->paused) {
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+ return PJ_SUCCESS;
+ }
+
+ /* Repeat get frame from the jitter buffer and decode the frame
+ * until we have enough frames according to codec's ptime.
+ */
+
+ /* Lock jitter buffer mutex first */
+ pj_mutex_lock( stream->jb_mutex );
+
+ samples_required = PJMEDIA_PIA_SPF(&stream->port.info);
+ samples_per_frame = stream->codec_param.info.frm_ptime *
+ stream->codec_param.info.clock_rate *
+ stream->codec_param.info.channel_cnt /
+ 1000;
+ p_out_samp = (pj_int16_t*) frame->buf;
+
+ for (samples_count=0; samples_count < samples_required;
+ samples_count += samples_per_frame)
+ {
+ char frame_type;
+ pj_size_t frame_size;
+ pj_uint32_t bit_info;
+
+ /* Get frame from jitter buffer. */
+ pjmedia_jbuf_get_frame2(stream->jb, channel->out_pkt, &frame_size,
+ &frame_type, &bit_info);
+
+#if TRACE_JB
+ trace_jb_get(stream, frame_type, frame_size);
+#endif
+
+ if (frame_type == PJMEDIA_JB_MISSING_FRAME) {
+
+ /* Activate PLC */
+ if (stream->codec->op->recover &&
+ stream->codec_param.setting.plc &&
+ stream->plc_cnt < stream->max_plc_cnt)
+ {
+ pjmedia_frame frame_out;
+
+ frame_out.buf = p_out_samp + samples_count;
+ frame_out.size = frame->size - samples_count*2;
+ status = pjmedia_codec_recover(stream->codec,
+ frame_out.size,
+ &frame_out);
+
+ ++stream->plc_cnt;
+
+ } else {
+ status = -1;
+ }
+
+ if (status != PJ_SUCCESS) {
+ /* Either PLC failed or PLC not supported/enabled */
+ pjmedia_zero_samples(p_out_samp + samples_count,
+ samples_required - samples_count);
+ }
+
+ if (frame_type != stream->jb_last_frm) {
+ /* Report changing frame type event */
+ PJ_LOG(5,(stream->port.info.name.ptr, "Frame lost%s!",
+ (status == PJ_SUCCESS? ", recovered":"")));
+
+ stream->jb_last_frm = frame_type;
+ stream->jb_last_frm_cnt = 1;
+ } else {
+ stream->jb_last_frm_cnt++;
+ }
+
+ } else if (frame_type == PJMEDIA_JB_ZERO_EMPTY_FRAME) {
+
+ const char *with_plc = "";
+
+ /* Jitter buffer is empty. If this is the first "empty" state,
+ * activate PLC to smoothen the fade-out, otherwise zero
+ * the frame.
+ */
+ //Using this "if" will only invoke PLC for the first packet
+ //lost and not the subsequent ones.
+ //if (frame_type != stream->jb_last_frm) {
+ if (1) {
+ /* Activate PLC to smoothen the missing frame */
+ if (stream->codec->op->recover &&
+ stream->codec_param.setting.plc &&
+ stream->plc_cnt < stream->max_plc_cnt)
+ {
+ pjmedia_frame frame_out;
+
+ do {
+ frame_out.buf = p_out_samp + samples_count;
+ frame_out.size = frame->size - samples_count*2;
+ status = pjmedia_codec_recover(stream->codec,
+ frame_out.size,
+ &frame_out);
+ if (status != PJ_SUCCESS)
+ break;
+
+ samples_count += samples_per_frame;
+ ++stream->plc_cnt;
+
+ } while (samples_count < samples_required &&
+ stream->plc_cnt < stream->max_plc_cnt);
+
+ with_plc = ", plc invoked";
+ }
+ }
+
+ if (samples_count < samples_required) {
+ pjmedia_zero_samples(p_out_samp + samples_count,
+ samples_required - samples_count);
+ samples_count = samples_required;
+ }
+
+ if (stream->jb_last_frm != frame_type) {
+ pjmedia_jb_state jb_state;
+
+ /* Report changing frame type event */
+ pjmedia_jbuf_get_state(stream->jb, &jb_state);
+ PJ_LOG(5,(stream->port.info.name.ptr,
+ "Jitter buffer empty (prefetch=%d)%s",
+ jb_state.prefetch, with_plc));
+
+ stream->jb_last_frm = frame_type;
+ stream->jb_last_frm_cnt = 1;
+ } else {
+ stream->jb_last_frm_cnt++;
+ }
+ break;
+
+ } else if (frame_type != PJMEDIA_JB_NORMAL_FRAME) {
+
+ const char *with_plc = "";
+
+ /* It can only be PJMEDIA_JB_ZERO_PREFETCH frame */
+ pj_assert(frame_type == PJMEDIA_JB_ZERO_PREFETCH_FRAME);
+
+ /* Always activate PLC when it's available.. */
+ if (stream->codec->op->recover &&
+ stream->codec_param.setting.plc &&
+ stream->plc_cnt < stream->max_plc_cnt)
+ {
+ pjmedia_frame frame_out;
+
+ do {
+ frame_out.buf = p_out_samp + samples_count;
+ frame_out.size = frame->size - samples_count*2;
+ status = pjmedia_codec_recover(stream->codec,
+ frame_out.size,
+ &frame_out);
+ if (status != PJ_SUCCESS)
+ break;
+ samples_count += samples_per_frame;
+
+ ++stream->plc_cnt;
+
+ } while (samples_count < samples_required &&
+ stream->plc_cnt < stream->max_plc_cnt);
+
+ with_plc = ", plc invoked";
+ }
+
+ if (samples_count < samples_required) {
+ pjmedia_zero_samples(p_out_samp + samples_count,
+ samples_required - samples_count);
+ samples_count = samples_required;
+ }
+
+ if (stream->jb_last_frm != frame_type) {
+ pjmedia_jb_state jb_state;
+
+ /* Report changing frame type event */
+ pjmedia_jbuf_get_state(stream->jb, &jb_state);
+ PJ_LOG(5,(stream->port.info.name.ptr,
+ "Jitter buffer is bufferring (prefetch=%d)%s",
+ jb_state.prefetch, with_plc));
+
+ stream->jb_last_frm = frame_type;
+ stream->jb_last_frm_cnt = 1;
+ } else {
+ stream->jb_last_frm_cnt++;
+ }
+ break;
+
+ } else {
+ /* Got "NORMAL" frame from jitter buffer */
+ pjmedia_frame frame_in, frame_out;
+
+ stream->plc_cnt = 0;
+
+ /* Decode */
+ frame_in.buf = channel->out_pkt;
+ frame_in.size = frame_size;
+ frame_in.bit_info = bit_info;
+ frame_in.type = PJMEDIA_FRAME_TYPE_AUDIO; /* ignored */
+
+ frame_out.buf = p_out_samp + samples_count;
+ frame_out.size = frame->size - samples_count*BYTES_PER_SAMPLE;
+ status = pjmedia_codec_decode( stream->codec, &frame_in,
+ frame_out.size, &frame_out);
+ if (status != 0) {
+ LOGERR_((port->info.name.ptr, "codec decode() error",
+ status));
+
+ pjmedia_zero_samples(p_out_samp + samples_count,
+ samples_per_frame);
+ }
+
+ if (stream->jb_last_frm != frame_type) {
+ /* Report changing frame type event */
+ PJ_LOG(5,(stream->port.info.name.ptr,
+ "Jitter buffer starts returning normal frames "
+ "(after %d empty/lost)",
+ stream->jb_last_frm_cnt, stream->jb_last_frm));
+
+ stream->jb_last_frm = frame_type;
+ stream->jb_last_frm_cnt = 1;
+ } else {
+ stream->jb_last_frm_cnt++;
+ }
+ }
+ }
+
+
+ /* Unlock jitter buffer mutex. */
+ pj_mutex_unlock( stream->jb_mutex );
+
+ /* Return PJMEDIA_FRAME_TYPE_NONE if we have no frames at all
+ * (it can happen when jitter buffer returns PJMEDIA_JB_ZERO_EMPTY_FRAME).
+ */
+ if (samples_count == 0) {
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+ frame->size = 0;
+ } else {
+ frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame->size = samples_count * BYTES_PER_SAMPLE;
+ frame->timestamp.u64 = 0;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* The other version of get_frame callback used when stream port format
+ * is non linear PCM.
+ */
+static pj_status_t get_frame_ext( pjmedia_port *port, pjmedia_frame *frame)
+{
+ pjmedia_stream *stream = (pjmedia_stream*) port->port_data.pdata;
+ pjmedia_channel *channel = stream->dec;
+ pjmedia_frame_ext *f = (pjmedia_frame_ext*)frame;
+ unsigned samples_per_frame, samples_required;
+ pj_status_t status;
+
+ /* Return no frame if channel is paused */
+ if (channel->paused) {
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+ return PJ_SUCCESS;
+ }
+
+ /* Repeat get frame from the jitter buffer and decode the frame
+ * until we have enough frames according to codec's ptime.
+ */
+
+ samples_required = PJMEDIA_PIA_SPF(&stream->port.info);
+ samples_per_frame = stream->codec_param.info.frm_ptime *
+ stream->codec_param.info.clock_rate *
+ stream->codec_param.info.channel_cnt /
+ 1000;
+
+ pj_bzero(f, sizeof(pjmedia_frame_ext));
+ f->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
+
+ while (f->samples_cnt < samples_required) {
+ char frame_type;
+ pj_size_t frame_size;
+ pj_uint32_t bit_info;
+
+ /* Lock jitter buffer mutex first */
+ pj_mutex_lock( stream->jb_mutex );
+
+ /* Get frame from jitter buffer. */
+ pjmedia_jbuf_get_frame2(stream->jb, channel->out_pkt, &frame_size,
+ &frame_type, &bit_info);
+
+#if TRACE_JB
+ trace_jb_get(stream, frame_type, frame_size);
+#endif
+
+ /* Unlock jitter buffer mutex. */
+ pj_mutex_unlock( stream->jb_mutex );
+
+ if (frame_type == PJMEDIA_JB_NORMAL_FRAME) {
+ /* Got "NORMAL" frame from jitter buffer */
+ pjmedia_frame frame_in;
+
+ /* Decode */
+ frame_in.buf = channel->out_pkt;
+ frame_in.size = frame_size;
+ frame_in.bit_info = bit_info;
+ frame_in.type = PJMEDIA_FRAME_TYPE_AUDIO;
+
+ status = pjmedia_codec_decode( stream->codec, &frame_in,
+ 0, frame);
+ if (status != PJ_SUCCESS) {
+ LOGERR_((port->info.name.ptr, "codec decode() error",
+ status));
+ pjmedia_frame_ext_append_subframe(f, NULL, 0,
+ (pj_uint16_t)samples_per_frame);
+ }
+
+ if (stream->jb_last_frm != frame_type) {
+ /* Report changing frame type event */
+ PJ_LOG(5,(stream->port.info.name.ptr,
+ "Jitter buffer starts returning normal frames "
+ "(after %d empty/lost)",
+ stream->jb_last_frm_cnt, stream->jb_last_frm));
+
+ stream->jb_last_frm = frame_type;
+ stream->jb_last_frm_cnt = 1;
+ } else {
+ stream->jb_last_frm_cnt++;
+ }
+
+ } else {
+
+ /* Try to generate frame by invoking PLC (when any) */
+ status = PJ_SUCCESS;
+ if (stream->codec->op->recover) {
+ status = pjmedia_codec_recover(stream->codec, 0, frame);
+ }
+
+ /* No PLC or PLC failed */
+ if (!stream->codec->op->recover || status != PJ_SUCCESS) {
+ pjmedia_frame_ext_append_subframe(f, NULL, 0,
+ (pj_uint16_t)samples_per_frame);
+ }
+
+ if (frame_type == PJMEDIA_JB_MISSING_FRAME) {
+ if (frame_type != stream->jb_last_frm) {
+ /* Report changing frame type event */
+ PJ_LOG(5,(stream->port.info.name.ptr, "Frame lost!"));
+
+ stream->jb_last_frm = frame_type;
+ stream->jb_last_frm_cnt = 1;
+ } else {
+ stream->jb_last_frm_cnt++;
+ }
+ } else if (frame_type == PJMEDIA_JB_ZERO_EMPTY_FRAME) {
+ if (frame_type != stream->jb_last_frm) {
+ pjmedia_jb_state jb_state;
+
+ /* Report changing frame type event */
+ pjmedia_jbuf_get_state(stream->jb, &jb_state);
+ PJ_LOG(5,(stream->port.info.name.ptr,
+ "Jitter buffer empty (prefetch=%d)",
+ jb_state.prefetch));
+
+ stream->jb_last_frm = frame_type;
+ stream->jb_last_frm_cnt = 1;
+ } else {
+ stream->jb_last_frm_cnt++;
+ }
+ } else {
+
+ /* It can only be PJMEDIA_JB_ZERO_PREFETCH frame */
+ pj_assert(frame_type == PJMEDIA_JB_ZERO_PREFETCH_FRAME);
+
+ if (stream->jb_last_frm != frame_type) {
+ pjmedia_jb_state jb_state;
+
+ /* Report changing frame type event */
+ pjmedia_jbuf_get_state(stream->jb, &jb_state);
+ PJ_LOG(5,(stream->port.info.name.ptr,
+ "Jitter buffer is bufferring (prefetch=%d)",
+ jb_state.prefetch));
+
+ stream->jb_last_frm = frame_type;
+ stream->jb_last_frm_cnt = 1;
+ } else {
+ stream->jb_last_frm_cnt++;
+ }
+ }
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Transmit DTMF
+ */
+static void create_dtmf_payload(pjmedia_stream *stream,
+ struct pjmedia_frame *frame_out,
+ int *first, int *last)
+{
+ pjmedia_rtp_dtmf_event *event;
+ struct dtmf *digit = &stream->tx_dtmf_buf[0];
+ pj_uint32_t cur_ts;
+
+ pj_assert(sizeof(pjmedia_rtp_dtmf_event) == 4);
+
+ *first = *last = 0;
+
+ event = (pjmedia_rtp_dtmf_event*) frame_out->buf;
+ cur_ts = pj_ntohl(stream->enc->rtp.out_hdr.ts);
+
+ if (digit->duration == 0) {
+ PJ_LOG(5,(stream->port.info.name.ptr, "Sending DTMF digit id %c",
+ digitmap[digit->event]));
+ *first = 1;
+ }
+
+ digit->duration += PJMEDIA_PIA_SPF(&stream->port.info);
+
+ event->event = (pj_uint8_t)digit->event;
+ event->e_vol = 10;
+ event->duration = pj_htons((pj_uint16_t)digit->duration);
+
+
+ if (digit->duration >= PJMEDIA_DTMF_DURATION) {
+
+ event->e_vol |= 0x80;
+ *last = 1;
+
+ /* Prepare next digit. */
+ pj_mutex_lock(stream->jb_mutex);
+
+ pj_array_erase(stream->tx_dtmf_buf, sizeof(stream->tx_dtmf_buf[0]),
+ stream->tx_dtmf_count, 0);
+ --stream->tx_dtmf_count;
+
+ pj_mutex_unlock(stream->jb_mutex);
+ }
+
+ frame_out->size = 4;
+}
+
+
+static pj_status_t send_rtcp(pjmedia_stream *stream,
+ pj_bool_t with_sdes,
+ pj_bool_t with_bye,
+ pj_bool_t with_xr)
+{
+ void *sr_rr_pkt;
+ pj_uint8_t *pkt;
+ int len, max_len;
+ pj_status_t status;
+
+ /* Build RTCP RR/SR packet */
+ pjmedia_rtcp_build_rtcp(&stream->rtcp, &sr_rr_pkt, &len);
+
+#if !defined(PJMEDIA_HAS_RTCP_XR) || (PJMEDIA_HAS_RTCP_XR == 0)
+ with_xr = PJ_FALSE;
+#endif
+
+ if (with_sdes || with_bye || with_xr) {
+ pkt = (pj_uint8_t*) stream->out_rtcp_pkt;
+ pj_memcpy(pkt, sr_rr_pkt, len);
+ max_len = stream->out_rtcp_pkt_size;
+ } else {
+ pkt = (pj_uint8_t*)sr_rr_pkt;
+ max_len = len;
+ }
+
+ /* Build RTCP SDES packet */
+ if (with_sdes) {
+ pjmedia_rtcp_sdes sdes;
+ pj_size_t sdes_len;
+
+ pj_bzero(&sdes, sizeof(sdes));
+ sdes.cname = stream->cname;
+ sdes_len = max_len - len;
+ status = pjmedia_rtcp_build_rtcp_sdes(&stream->rtcp, pkt+len,
+ &sdes_len, &sdes);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4,(stream->port.info.name.ptr, status,
+ "Error generating RTCP SDES"));
+ } else {
+ len += (int)sdes_len;
+ }
+ }
+
+ /* Build RTCP XR packet */
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+ if (with_xr) {
+ int i;
+ pjmedia_jb_state jb_state;
+ void *xr_pkt;
+ int xr_len;
+
+ /* Update RTCP XR with current JB states */
+ pjmedia_jbuf_get_state(stream->jb, &jb_state);
+
+ i = jb_state.avg_delay;
+ status = pjmedia_rtcp_xr_update_info(&stream->rtcp.xr_session,
+ PJMEDIA_RTCP_XR_INFO_JB_NOM, i);
+ pj_assert(status == PJ_SUCCESS);
+
+ i = jb_state.max_delay;
+ status = pjmedia_rtcp_xr_update_info(&stream->rtcp.xr_session,
+ PJMEDIA_RTCP_XR_INFO_JB_MAX, i);
+ pj_assert(status == PJ_SUCCESS);
+
+ pjmedia_rtcp_build_rtcp_xr(&stream->rtcp.xr_session, 0,
+ &xr_pkt, &xr_len);
+
+ if (xr_len + len <= max_len) {
+ pj_memcpy(pkt+len, xr_pkt, xr_len);
+ len += xr_len;
+
+ /* Send the RTCP XR to third-party destination if specified */
+ if (stream->rtcp_xr_dest_len) {
+ pjmedia_transport_send_rtcp2(stream->transport,
+ &stream->rtcp_xr_dest,
+ stream->rtcp_xr_dest_len,
+ xr_pkt, xr_len);
+ }
+
+ } else {
+ PJ_PERROR(4,(stream->port.info.name.ptr, PJ_ETOOBIG,
+ "Error generating RTCP-XR"));
+ }
+ }
+#endif
+
+ /* Build RTCP BYE packet */
+ if (with_bye) {
+ pj_size_t bye_len;
+
+ bye_len = max_len - len;
+ status = pjmedia_rtcp_build_rtcp_bye(&stream->rtcp, pkt+len,
+ &bye_len, NULL);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4,(stream->port.info.name.ptr, status,
+ "Error generating RTCP BYE"));
+ } else {
+ len += (int)bye_len;
+ }
+ }
+
+ /* Send! */
+ status = pjmedia_transport_send_rtcp(stream->transport, pkt, len);
+
+ return status;
+}
+
+/**
+ * check_tx_rtcp()
+ *
+ * This function is can be called by either put_frame() or get_frame(),
+ * to transmit periodic RTCP SR/RR report.
+ */
+static void check_tx_rtcp(pjmedia_stream *stream, pj_uint32_t timestamp)
+{
+ /* Note that timestamp may represent local or remote timestamp,
+ * depending on whether this function is called from put_frame()
+ * or get_frame().
+ */
+
+ if (stream->rtcp_last_tx == 0) {
+
+ stream->rtcp_last_tx = timestamp;
+
+ } else if (timestamp - stream->rtcp_last_tx >= stream->rtcp_interval) {
+ pj_bool_t with_xr = PJ_FALSE;
+ pj_status_t status;
+
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+ if (stream->rtcp.xr_enabled) {
+ if (stream->rtcp_xr_last_tx == 0) {
+ stream->rtcp_xr_last_tx = timestamp;
+ } else if (timestamp - stream->rtcp_xr_last_tx >=
+ stream->rtcp_xr_interval)
+ {
+ with_xr = PJ_TRUE;
+
+ /* Update last tx RTCP XR */
+ stream->rtcp_xr_last_tx = timestamp;
+ }
+ }
+#endif
+
+ status = send_rtcp(stream, !stream->rtcp_sdes_bye_disabled, PJ_FALSE,
+ with_xr);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4,(stream->port.info.name.ptr, status,
+ "Error sending RTCP"));
+ }
+
+ stream->rtcp_last_tx = timestamp;
+ }
+}
+
+
+/**
+ * Rebuffer the frame when encoder and decoder has different ptime
+ * (such as when different iLBC modes are used by local and remote)
+ */
+static void rebuffer(pjmedia_stream *stream,
+ pjmedia_frame *frame)
+{
+ /* How many samples are needed */
+ unsigned count;
+
+ /* Normalize frame */
+ if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO)
+ frame->size = 0;
+
+ /* Remove used frame from the buffer. */
+ if (stream->enc_buf_pos) {
+ if (stream->enc_buf_count) {
+ pj_memmove(stream->enc_buf,
+ stream->enc_buf + stream->enc_buf_pos,
+ (stream->enc_buf_count << 1));
+ }
+ stream->enc_buf_pos = 0;
+ }
+
+ /* Make sure we have space to store the new frame */
+ pj_assert(stream->enc_buf_count + (frame->size >> 1) <
+ stream->enc_buf_size);
+
+ /* Append new frame to the buffer */
+ if (frame->size) {
+ /* Handle case when there is no port transmitting to this port */
+ if (frame->buf) {
+ pj_memcpy(stream->enc_buf + stream->enc_buf_count,
+ frame->buf, frame->size);
+ } else {
+ pj_bzero(stream->enc_buf + stream->enc_buf_count, frame->size);
+ }
+ stream->enc_buf_count += (frame->size >> 1);
+ }
+
+ /* How many samples are needed */
+ count = stream->codec_param.info.enc_ptime *
+ PJMEDIA_PIA_SRATE(&stream->port.info) / 1000;
+
+ /* See if we have enough samples */
+ if (stream->enc_buf_count >= count) {
+
+ frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame->buf = stream->enc_buf;
+ frame->size = (count << 1);
+
+ stream->enc_buf_pos = count;
+ stream->enc_buf_count -= count;
+
+ } else {
+ /* We don't have enough samples */
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+ }
+}
+
+
+/**
+ * put_frame_imp()
+ */
+static pj_status_t put_frame_imp( pjmedia_port *port,
+ pjmedia_frame *frame )
+{
+ pjmedia_stream *stream = (pjmedia_stream*) port->port_data.pdata;
+ pjmedia_channel *channel = stream->enc;
+ pj_status_t status = 0;
+ pjmedia_frame frame_out;
+ unsigned ts_len, rtp_ts_len, samples_per_frame;
+ void *rtphdr;
+ int rtphdrlen;
+ int inc_timestamp = 0;
+
+
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA != 0
+ /* If the interval since last sending packet is greater than
+ * PJMEDIA_STREAM_KA_INTERVAL, send keep-alive packet.
+ */
+ if (stream->use_ka)
+ {
+ pj_uint32_t dtx_duration;
+
+ dtx_duration = pj_timestamp_diff32(&stream->last_frm_ts_sent,
+ &frame->timestamp);
+ if (dtx_duration >
+ PJMEDIA_STREAM_KA_INTERVAL * PJMEDIA_PIA_SRATE(&stream->port.info))
+ {
+ send_keep_alive_packet(stream);
+ stream->last_frm_ts_sent = frame->timestamp;
+ }
+ }
+#endif
+
+ /* Don't do anything if stream is paused */
+ if (channel->paused) {
+ stream->enc_buf_pos = stream->enc_buf_count = 0;
+ return PJ_SUCCESS;
+ }
+
+ /* Number of samples in the frame */
+ if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO)
+ ts_len = (frame->size >> 1) / stream->codec_param.info.channel_cnt;
+ else if (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED)
+ ts_len = PJMEDIA_PIA_SPF(&stream->port.info) /
+ PJMEDIA_PIA_CCNT(&stream->port.info);
+ else
+ ts_len = 0;
+
+ /* Increment transmit duration */
+ stream->tx_duration += ts_len;
+
+#if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG!=0)
+ /* Handle special case for audio codec with RTP timestamp inconsistence
+ * e.g: G722, MPEG audio.
+ */
+ if (stream->has_g722_mpeg_bug)
+ rtp_ts_len = stream->rtp_tx_ts_len_per_pkt;
+ else
+ rtp_ts_len = ts_len;
+#else
+ rtp_ts_len = ts_len;
+#endif
+
+ /* Init frame_out buffer. */
+ frame_out.buf = ((char*)channel->out_pkt) + sizeof(pjmedia_rtp_hdr);
+ frame_out.size = 0;
+
+ /* Calculate number of samples per frame */
+ samples_per_frame = stream->enc_samples_per_pkt;
+
+
+ /* If we have DTMF digits in the queue, transmit the digits.
+ * Otherwise encode the PCM buffer.
+ */
+ if (stream->tx_dtmf_count) {
+ int first=0, last=0;
+
+ create_dtmf_payload(stream, &frame_out, &first, &last);
+
+ /* Encapsulate into RTP packet. Note that:
+ * - RTP marker should be set on the beginning of a new event
+ * - RTP timestamp is constant for the same packet.
+ */
+ status = pjmedia_rtp_encode_rtp( &channel->rtp,
+ stream->tx_event_pt, first,
+ frame_out.size,
+ (first ? rtp_ts_len : 0),
+ (const void**)&rtphdr,
+ &rtphdrlen);
+
+ if (last) {
+ /* This is the last packet for the event.
+ * Increment the RTP timestamp of the RTP session, for next
+ * RTP packets.
+ */
+ inc_timestamp = PJMEDIA_DTMF_DURATION - rtp_ts_len;
+ }
+
+
+ /*
+ * Special treatment for FRAME_TYPE_AUDIO but with frame->buf==NULL.
+ * This happens when stream input is disconnected from the bridge.
+ * In this case we periodically transmit RTP frame to keep NAT binding
+ * open, by giving zero PCM frame to the codec.
+ *
+ * This was originally done in http://trac.pjsip.org/repos/ticket/56,
+ * but then disabled in http://trac.pjsip.org/repos/ticket/439, but
+ * now it's enabled again.
+ */
+ } else if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO &&
+ frame->buf == NULL &&
+ stream->port.info.fmt.id == PJMEDIA_FORMAT_L16 &&
+ (stream->dir & PJMEDIA_DIR_ENCODING) &&
+ stream->codec_param.info.frm_ptime *
+ stream->codec_param.info.channel_cnt *
+ stream->codec_param.info.clock_rate/1000 <
+ PJ_ARRAY_SIZE(zero_frame))
+ {
+ pjmedia_frame silence_frame;
+
+ pj_bzero(&silence_frame, sizeof(silence_frame));
+ silence_frame.buf = zero_frame;
+ silence_frame.size = stream->codec_param.info.frm_ptime * 2 *
+ stream->codec_param.info.channel_cnt *
+ stream->codec_param.info.clock_rate / 1000;
+ silence_frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ silence_frame.timestamp.u32.lo = pj_ntohl(stream->enc->rtp.out_hdr.ts);
+
+ /* Encode! */
+ status = pjmedia_codec_encode( stream->codec, &silence_frame,
+ channel->out_pkt_size -
+ sizeof(pjmedia_rtp_hdr),
+ &frame_out);
+ if (status != PJ_SUCCESS) {
+ LOGERR_((stream->port.info.name.ptr,
+ "Codec encode() error", status));
+ return status;
+ }
+
+ /* Encapsulate. */
+ status = pjmedia_rtp_encode_rtp( &channel->rtp,
+ channel->pt, 0,
+ frame_out.size, rtp_ts_len,
+ (const void**)&rtphdr,
+ &rtphdrlen);
+
+
+ /* Encode audio frame */
+ } else if ((frame->type == PJMEDIA_FRAME_TYPE_AUDIO &&
+ frame->buf != NULL) ||
+ (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED))
+ {
+ /* Encode! */
+ status = pjmedia_codec_encode( stream->codec, frame,
+ channel->out_pkt_size -
+ sizeof(pjmedia_rtp_hdr),
+ &frame_out);
+ if (status != PJ_SUCCESS) {
+ LOGERR_((stream->port.info.name.ptr,
+ "Codec encode() error", status));
+ return status;
+ }
+
+ /* Encapsulate. */
+ status = pjmedia_rtp_encode_rtp( &channel->rtp,
+ channel->pt, 0,
+ frame_out.size, rtp_ts_len,
+ (const void**)&rtphdr,
+ &rtphdrlen);
+
+ } else {
+
+ /* Just update RTP session's timestamp. */
+ status = pjmedia_rtp_encode_rtp( &channel->rtp,
+ 0, 0,
+ 0, rtp_ts_len,
+ (const void**)&rtphdr,
+ &rtphdrlen);
+
+ }
+
+ if (status != PJ_SUCCESS) {
+ LOGERR_((stream->port.info.name.ptr,
+ "RTP encode_rtp() error", status));
+ return status;
+ }
+
+ /* Check if now is the time to transmit RTCP SR/RR report.
+ * We only do this when stream direction is not "decoding only", because
+ * when it is, check_tx_rtcp() will be handled by get_frame().
+ */
+ if (stream->dir != PJMEDIA_DIR_DECODING) {
+ check_tx_rtcp(stream, pj_ntohl(channel->rtp.out_hdr.ts));
+ }
+
+ /* Do nothing if we have nothing to transmit */
+ if (frame_out.size == 0) {
+ if (stream->is_streaming) {
+ PJ_LOG(5,(stream->port.info.name.ptr,"Starting silence"));
+ stream->is_streaming = PJ_FALSE;
+ }
+
+ return PJ_SUCCESS;
+ }
+
+
+ /* Copy RTP header to the beginning of packet */
+ pj_memcpy(channel->out_pkt, rtphdr, sizeof(pjmedia_rtp_hdr));
+
+ /* Special case for DTMF: timestamp remains constant for
+ * the same event, and is only updated after a complete event
+ * has been transmitted.
+ */
+ if (inc_timestamp) {
+ pjmedia_rtp_encode_rtp( &channel->rtp, stream->tx_event_pt, 0,
+ 0, inc_timestamp, NULL, NULL);
+ }
+
+ /* Set RTP marker bit if currently not streaming */
+ if (stream->is_streaming == PJ_FALSE) {
+ pjmedia_rtp_hdr *rtp = (pjmedia_rtp_hdr*) channel->out_pkt;
+
+ rtp->m = 1;
+ PJ_LOG(5,(stream->port.info.name.ptr,"Start talksprut.."));
+ }
+
+ stream->is_streaming = PJ_TRUE;
+
+ /* Send the RTP packet to the transport. */
+ status = pjmedia_transport_send_rtp(stream->transport, channel->out_pkt,
+ frame_out.size +
+ sizeof(pjmedia_rtp_hdr));
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4,(stream->port.info.name.ptr, status,
+ "Error sending RTP"));
+ }
+
+ /* Update stat */
+ pjmedia_rtcp_tx_rtp(&stream->rtcp, frame_out.size);
+ stream->rtcp.stat.rtp_tx_last_ts = pj_ntohl(stream->enc->rtp.out_hdr.ts);
+ stream->rtcp.stat.rtp_tx_last_seq = pj_ntohs(stream->enc->rtp.out_hdr.seq);
+
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
+ /* Update timestamp of last sending packet. */
+ stream->last_frm_ts_sent = frame->timestamp;
+#endif
+
+ return PJ_SUCCESS;
+}
+
+
+/**
+ * put_frame()
+ *
+ * This callback is called by upstream component when it has PCM frame
+ * to transmit. This function encodes the PCM frame, pack it into
+ * RTP packet, and transmit to peer.
+ */
+static pj_status_t put_frame( pjmedia_port *port,
+ pjmedia_frame *frame )
+{
+ pjmedia_stream *stream = (pjmedia_stream*) port->port_data.pdata;
+ pjmedia_frame tmp_zero_frame;
+ unsigned samples_per_frame;
+
+ samples_per_frame = stream->enc_samples_per_pkt;
+
+ /* http://www.pjsip.org/trac/ticket/56:
+ * when input is PJMEDIA_FRAME_TYPE_NONE, feed zero PCM frame
+ * instead so that encoder can decide whether or not to transmit
+ * silence frame.
+ */
+ if (frame->type == PJMEDIA_FRAME_TYPE_NONE) {
+ pj_memcpy(&tmp_zero_frame, frame, sizeof(pjmedia_frame));
+ frame = &tmp_zero_frame;
+
+ tmp_zero_frame.buf = NULL;
+ tmp_zero_frame.size = samples_per_frame * 2;
+ tmp_zero_frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ }
+
+#if 0
+ // This is no longer needed because each TYPE_NONE frame will
+ // be converted into zero frame above
+
+ /* If VAD is temporarily disabled during creation, feed zero PCM frame
+ * to the codec.
+ */
+ if (stream->vad_enabled != stream->codec_param.setting.vad &&
+ stream->vad_enabled != 0 &&
+ frame->type == PJMEDIA_FRAME_TYPE_NONE &&
+ samples_per_frame <= ZERO_PCM_MAX_SIZE)
+ {
+ pj_memcpy(&tmp_in_frame, frame, sizeof(pjmedia_frame));
+ frame = &tmp_in_frame;
+
+ tmp_in_frame.buf = NULL;
+ tmp_in_frame.size = samples_per_frame * 2;
+ tmp_in_frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ }
+#endif
+
+ /* If VAD is temporarily disabled during creation, enable it
+ * after transmitting for VAD_SUSPEND_SEC seconds.
+ */
+ if (stream->vad_enabled != stream->codec_param.setting.vad &&
+ (stream->tx_duration - stream->ts_vad_disabled) >
+ PJMEDIA_PIA_SRATE(&stream->port.info) *
+ PJMEDIA_STREAM_VAD_SUSPEND_MSEC / 1000)
+ {
+ stream->codec_param.setting.vad = stream->vad_enabled;
+ pjmedia_codec_modify(stream->codec, &stream->codec_param);
+ PJ_LOG(4,(stream->port.info.name.ptr,"VAD re-enabled"));
+ }
+
+
+ /* If encoder has different ptime than decoder, then the frame must
+ * be passed through the encoding buffer via rebuffer() function.
+ */
+ if (stream->enc_buf != NULL) {
+ pjmedia_frame tmp_rebuffer_frame;
+ pj_status_t status = PJ_SUCCESS;
+
+ /* Copy original frame to temporary frame since we need
+ * to modify it.
+ */
+ pj_memcpy(&tmp_rebuffer_frame, frame, sizeof(pjmedia_frame));
+
+ /* Loop while we have full frame in enc_buffer */
+ for (;;) {
+ pj_status_t st;
+
+ /* Run rebuffer() */
+ rebuffer(stream, &tmp_rebuffer_frame);
+
+ /* Process this frame */
+ st = put_frame_imp(port, &tmp_rebuffer_frame);
+ if (st != PJ_SUCCESS)
+ status = st;
+
+ /* If we still have full frame in the buffer, re-run
+ * rebuffer() with NULL frame.
+ */
+ if (stream->enc_buf_count >= stream->enc_samples_per_pkt) {
+
+ tmp_rebuffer_frame.type = PJMEDIA_FRAME_TYPE_NONE;
+
+ } else {
+
+ /* Otherwise break */
+ break;
+ }
+ }
+
+ return status;
+
+ } else {
+ return put_frame_imp(port, frame);
+ }
+}
+
+
+#if 0
+static void dump_bin(const char *buf, unsigned len)
+{
+ unsigned i;
+
+ PJ_LOG(3,(THIS_FILE, "begin dump"));
+ for (i=0; i<len; ++i) {
+ int j;
+ char bits[9];
+ unsigned val = buf[i] & 0xFF;
+
+ bits[8] = '\0';
+ for (j=0; j<8; ++j) {
+ if (val & (1 << (7-j)))
+ bits[j] = '1';
+ else
+ bits[j] = '0';
+ }
+
+ PJ_LOG(3,(THIS_FILE, "%2d %s [%d]", i, bits, val));
+ }
+ PJ_LOG(3,(THIS_FILE, "end dump"));
+}
+#endif
+
+/*
+ * Handle incoming DTMF digits.
+ */
+static void handle_incoming_dtmf( pjmedia_stream *stream,
+ const void *payload, unsigned payloadlen)
+{
+ pjmedia_rtp_dtmf_event *event = (pjmedia_rtp_dtmf_event*) payload;
+
+ /* Check compiler packing. */
+ pj_assert(sizeof(pjmedia_rtp_dtmf_event)==4);
+
+ /* Must have sufficient length before we proceed. */
+ if (payloadlen < sizeof(pjmedia_rtp_dtmf_event))
+ return;
+
+ //dump_bin(payload, payloadlen);
+
+ /* Check if this is the same/current digit of the last packet. */
+ if (stream->last_dtmf != -1 &&
+ event->event == stream->last_dtmf &&
+ pj_ntohs(event->duration) >= stream->last_dtmf_dur)
+ {
+ /* Yes, this is the same event. */
+ stream->last_dtmf_dur = pj_ntohs(event->duration);
+ return;
+ }
+
+ /* Ignore unknown event. */
+ if (event->event > 15) {
+ PJ_LOG(5,(stream->port.info.name.ptr,
+ "Ignored RTP pkt with bad DTMF event %d",
+ event->event));
+ return;
+ }
+
+ /* New event! */
+ PJ_LOG(5,(stream->port.info.name.ptr, "Received DTMF digit %c, vol=%d",
+ digitmap[event->event],
+ (event->e_vol & 0x3F)));
+
+ stream->last_dtmf = event->event;
+ stream->last_dtmf_dur = pj_ntohs(event->duration);
+
+ /* If DTMF callback is installed, call the callback, otherwise keep
+ * the DTMF digits in the buffer.
+ */
+ if (stream->dtmf_cb) {
+
+ stream->dtmf_cb(stream, stream->dtmf_cb_user_data,
+ digitmap[event->event]);
+
+ } else {
+ /* By convention, we use jitter buffer's mutex to access shared
+ * DTMF variables.
+ */
+ pj_mutex_lock(stream->jb_mutex);
+ if (stream->rx_dtmf_count >= PJ_ARRAY_SIZE(stream->rx_dtmf_buf)) {
+ /* DTMF digits overflow. Discard the oldest digit. */
+ pj_array_erase(stream->rx_dtmf_buf,
+ sizeof(stream->rx_dtmf_buf[0]),
+ stream->rx_dtmf_count, 0);
+ --stream->rx_dtmf_count;
+ }
+ stream->rx_dtmf_buf[stream->rx_dtmf_count++] = digitmap[event->event];
+ pj_mutex_unlock(stream->jb_mutex);
+ }
+}
+
+
+/*
+ * This callback is called by stream transport on receipt of packets
+ * in the RTP socket.
+ */
+static void on_rx_rtp( void *data,
+ void *pkt,
+ pj_ssize_t bytes_read)
+
+{
+ pjmedia_stream *stream = (pjmedia_stream*) data;
+ pjmedia_channel *channel = stream->dec;
+ const pjmedia_rtp_hdr *hdr;
+ const void *payload;
+ unsigned payloadlen;
+ pjmedia_rtp_status seq_st;
+ pj_status_t status;
+ pj_bool_t pkt_discarded = PJ_FALSE;
+
+ /* Check for errors */
+ if (bytes_read < 0) {
+ LOGERR_((stream->port.info.name.ptr, "RTP recv() error", -bytes_read));
+ return;
+ }
+
+ /* Ignore keep-alive packets */
+ if (bytes_read < (pj_ssize_t) sizeof(pjmedia_rtp_hdr))
+ return;
+
+ /* Update RTP and RTCP session. */
+ status = pjmedia_rtp_decode_rtp(&channel->rtp, pkt, bytes_read,
+ &hdr, &payload, &payloadlen);
+ if (status != PJ_SUCCESS) {
+ LOGERR_((stream->port.info.name.ptr, "RTP decode error", status));
+ stream->rtcp.stat.rx.discard++;
+ return;
+ }
+
+ /* Ignore the packet if decoder is paused */
+ if (channel->paused)
+ goto on_return;
+
+ /* Update RTP session (also checks if RTP session can accept
+ * the incoming packet.
+ */
+ pjmedia_rtp_session_update2(&channel->rtp, hdr, &seq_st,
+ hdr->pt != stream->rx_event_pt);
+ if (seq_st.status.value) {
+ TRC_ ((stream->port.info.name.ptr,
+ "RTP status: badpt=%d, badssrc=%d, dup=%d, "
+ "outorder=%d, probation=%d, restart=%d",
+ seq_st.status.flag.badpt,
+ seq_st.status.flag.badssrc,
+ seq_st.status.flag.dup,
+ seq_st.status.flag.outorder,
+ seq_st.status.flag.probation,
+ seq_st.status.flag.restart));
+
+ if (seq_st.status.flag.badpt) {
+ PJ_LOG(4,(stream->port.info.name.ptr,
+ "Bad RTP pt %d (expecting %d)",
+ hdr->pt, channel->rtp.out_pt));
+ }
+
+ if (seq_st.status.flag.badssrc) {
+ PJ_LOG(4,(stream->port.info.name.ptr,
+ "Changed RTP peer SSRC %d (previously %d)",
+ channel->rtp.peer_ssrc, stream->rtcp.peer_ssrc));
+ stream->rtcp.peer_ssrc = channel->rtp.peer_ssrc;
+ }
+
+
+ }
+
+ /* Skip bad RTP packet */
+ if (seq_st.status.flag.bad) {
+ pkt_discarded = PJ_TRUE;
+ goto on_return;
+ }
+
+ /* Ignore if payloadlen is zero */
+ if (payloadlen == 0) {
+ pkt_discarded = PJ_TRUE;
+ goto on_return;
+ }
+
+ /* Handle incoming DTMF. */
+ if (hdr->pt == stream->rx_event_pt) {
+ /* Ignore out-of-order packet as it will be detected as new
+ * digit. Also ignore duplicate packet as it serves no use.
+ */
+ if (seq_st.status.flag.outorder || seq_st.status.flag.dup) {
+ goto on_return;
+ }
+
+ handle_incoming_dtmf(stream, payload, payloadlen);
+ goto on_return;
+ }
+
+ /* Put "good" packet to jitter buffer, or reset the jitter buffer
+ * when RTP session is restarted.
+ */
+ pj_mutex_lock( stream->jb_mutex );
+ if (seq_st.status.flag.restart) {
+ status = pjmedia_jbuf_reset(stream->jb);
+ PJ_LOG(4,(stream->port.info.name.ptr, "Jitter buffer reset"));
+ } else {
+ /*
+ * Packets may contain more than one frames, while the jitter
+ * buffer can only take one frame per "put" operation. So we need
+ * to ask the codec to "parse" the payload into multiple frames.
+ */
+ enum { MAX = 16 };
+ pj_timestamp ts;
+ unsigned i, count = MAX;
+ unsigned ts_span;
+ pjmedia_frame frames[MAX];
+
+ /* Get the timestamp of the first sample */
+ ts.u64 = pj_ntohl(hdr->ts);
+
+ /* Parse the payload. */
+ status = pjmedia_codec_parse(stream->codec, (void*)payload,
+ payloadlen, &ts, &count, frames);
+ if (status != PJ_SUCCESS) {
+ LOGERR_((stream->port.info.name.ptr,
+ "Codec parse() error",
+ status));
+ count = 0;
+ }
+
+#if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG!=0)
+ /* This code is used to learn the samples per frame value that is put
+ * by remote endpoint, for codecs with inconsistent clock rate such
+ * as G.722 or MPEG audio. We need to learn the samples per frame
+ * value as it is used as divider when inserting frames into the
+ * jitter buffer.
+ */
+ if (stream->has_g722_mpeg_bug) {
+ if (stream->rtp_rx_check_cnt) {
+ /* Make sure the detection performed only on two consecutive
+ * packets with valid RTP sequence and no wrapped timestamp.
+ */
+ if (seq_st.diff == 1 && stream->rtp_rx_last_ts &&
+ ts.u64 > stream->rtp_rx_last_ts &&
+ stream->rtp_rx_last_cnt > 0)
+ {
+ unsigned peer_frm_ts_diff;
+ unsigned frm_ts_span;
+
+ /* Calculate actual frame timestamp span */
+ frm_ts_span = PJMEDIA_PIA_SPF(&stream->port.info) /
+ stream->codec_param.setting.frm_per_pkt/
+ PJMEDIA_PIA_CCNT(&stream->port.info);
+
+ /* Get remote frame timestamp span */
+ peer_frm_ts_diff =
+ ((pj_uint32_t)ts.u64-stream->rtp_rx_last_ts) /
+ stream->rtp_rx_last_cnt;
+
+ /* Possibilities remote's samples per frame for G.722
+ * are only (frm_ts_span) and (frm_ts_span/2), this
+ * validation is needed to avoid wrong decision because
+ * of silence frames.
+ */
+ if (stream->codec_param.info.pt == PJMEDIA_RTP_PT_G722 &&
+ (peer_frm_ts_diff == frm_ts_span ||
+ peer_frm_ts_diff == (frm_ts_span>>1)))
+ {
+ if (peer_frm_ts_diff < stream->rtp_rx_ts_len_per_frame)
+ stream->rtp_rx_ts_len_per_frame = peer_frm_ts_diff;
+
+ if (--stream->rtp_rx_check_cnt == 0) {
+ PJ_LOG(4, (THIS_FILE, "G722 codec used, remote"
+ " samples per frame detected = %d",
+ stream->rtp_rx_ts_len_per_frame));
+
+ /* Reset jitter buffer once detection done */
+ pjmedia_jbuf_reset(stream->jb);
+ }
+ }
+ }
+
+ stream->rtp_rx_last_ts = (pj_uint32_t)ts.u64;
+ stream->rtp_rx_last_cnt = count;
+ }
+
+ ts_span = stream->rtp_rx_ts_len_per_frame;
+
+ /* Adjust the timestamp of the parsed frames */
+ for (i=0; i<count; ++i) {
+ frames[i].timestamp.u64 = ts.u64 + ts_span * i;
+ }
+
+ } else {
+ ts_span = stream->codec_param.info.frm_ptime *
+ stream->codec_param.info.clock_rate /
+ 1000;
+ }
+#else
+ ts_span = stream->codec_param.info.frm_ptime *
+ stream->codec_param.info.clock_rate /
+ 1000;
+#endif
+
+ /* Put each frame to jitter buffer. */
+ for (i=0; i<count; ++i) {
+ unsigned ext_seq;
+ pj_bool_t discarded;
+
+ ext_seq = (unsigned)(frames[i].timestamp.u64 / ts_span);
+ pjmedia_jbuf_put_frame2(stream->jb, frames[i].buf, frames[i].size,
+ frames[i].bit_info, ext_seq, &discarded);
+ if (discarded)
+ pkt_discarded = PJ_TRUE;
+ }
+
+#if TRACE_JB
+ trace_jb_put(stream, hdr, payloadlen, count);
+#endif
+
+ }
+ pj_mutex_unlock( stream->jb_mutex );
+
+
+ /* Check if now is the time to transmit RTCP SR/RR report.
+ * We only do this when stream direction is "decoding only",
+ * because otherwise check_tx_rtcp() will be handled by put_frame()
+ */
+ if (stream->dir == PJMEDIA_DIR_DECODING) {
+ check_tx_rtcp(stream, pj_ntohl(hdr->ts));
+ }
+
+ if (status != 0) {
+ LOGERR_((stream->port.info.name.ptr, "Jitter buffer put() error",
+ status));
+ pkt_discarded = PJ_TRUE;
+ goto on_return;
+ }
+
+on_return:
+ /* Update RTCP session */
+ if (stream->rtcp.peer_ssrc == 0)
+ stream->rtcp.peer_ssrc = channel->rtp.peer_ssrc;
+
+ pjmedia_rtcp_rx_rtp2(&stream->rtcp, pj_ntohs(hdr->seq),
+ pj_ntohl(hdr->ts), payloadlen, pkt_discarded);
+
+ /* Send RTCP RR and SDES after we receive some RTP packets */
+ if (stream->rtcp.received >= 10 && !stream->initial_rr) {
+ status = send_rtcp(stream, !stream->rtcp_sdes_bye_disabled,
+ PJ_FALSE, PJ_FALSE);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4,(stream->port.info.name.ptr, status,
+ "Error sending initial RTCP RR"));
+ } else {
+ stream->initial_rr = PJ_TRUE;
+ }
+ }
+}
+
+
+/*
+ * This callback is called by stream transport on receipt of packets
+ * in the RTCP socket.
+ */
+static void on_rx_rtcp( void *data,
+ void *pkt,
+ pj_ssize_t bytes_read)
+{
+ pjmedia_stream *stream = (pjmedia_stream*) data;
+
+ /* Check for errors */
+ if (bytes_read < 0) {
+ LOGERR_((stream->port.info.name.ptr, "RTCP recv() error",
+ -bytes_read));
+ return;
+ }
+
+ pjmedia_rtcp_rx_rtcp(&stream->rtcp, pkt, bytes_read);
+}
+
+
+/*
+ * Create media channel.
+ */
+static pj_status_t create_channel( pj_pool_t *pool,
+ pjmedia_stream *stream,
+ pjmedia_dir dir,
+ unsigned pt,
+ const pjmedia_stream_info *param,
+ pjmedia_channel **p_channel)
+{
+ pjmedia_channel *channel;
+ pj_status_t status;
+
+ /* Allocate memory for channel descriptor */
+
+ channel = PJ_POOL_ZALLOC_T(pool, pjmedia_channel);
+ PJ_ASSERT_RETURN(channel != NULL, PJ_ENOMEM);
+
+ /* Init channel info. */
+
+ channel->stream = stream;
+ channel->dir = dir;
+ channel->paused = 1;
+ channel->pt = pt;
+
+
+ /* Allocate buffer for outgoing packet. */
+
+ if (param->type == PJMEDIA_TYPE_AUDIO) {
+ channel->out_pkt_size = sizeof(pjmedia_rtp_hdr) +
+ stream->codec_param.info.max_bps *
+ PJMEDIA_MAX_FRAME_DURATION_MS /
+ 8 / 1000;
+ if (channel->out_pkt_size > PJMEDIA_MAX_MTU -
+ PJMEDIA_STREAM_RESV_PAYLOAD_LEN)
+ {
+ channel->out_pkt_size = PJMEDIA_MAX_MTU -
+ PJMEDIA_STREAM_RESV_PAYLOAD_LEN;
+ }
+ } else {
+ return PJ_ENOTSUP;
+ }
+
+ channel->out_pkt = pj_pool_alloc(pool, channel->out_pkt_size);
+ PJ_ASSERT_RETURN(channel->out_pkt != NULL, PJ_ENOMEM);
+
+
+
+ /* Create RTP and RTCP sessions: */
+
+ if (param->rtp_seq_ts_set == 0) {
+ status = pjmedia_rtp_session_init(&channel->rtp, pt, param->ssrc);
+ } else {
+ pjmedia_rtp_session_setting settings;
+
+ settings.flags = (pj_uint8_t)((param->rtp_seq_ts_set << 2) | 3);
+ settings.default_pt = pt;
+ settings.sender_ssrc = param->ssrc;
+ settings.seq = param->rtp_seq;
+ settings.ts = param->rtp_ts;
+ status = pjmedia_rtp_session_init2(&channel->rtp, settings);
+ }
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Done. */
+ *p_channel = channel;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create media stream.
+ */
+PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt,
+ pj_pool_t *pool,
+ const pjmedia_stream_info *info,
+ pjmedia_transport *tp,
+ void *user_data,
+ pjmedia_stream **p_stream)
+
+{
+ enum { M = 32 };
+ pjmedia_stream *stream;
+ pj_str_t name;
+ unsigned jb_init, jb_max, jb_min_pre, jb_max_pre;
+ pjmedia_audio_format_detail *afd;
+ pj_pool_t *own_pool = NULL;
+ char *p;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(endpt && info && p_stream, PJ_EINVAL);
+
+ if (pool == NULL) {
+ own_pool = pjmedia_endpt_create_pool( endpt, "strm%p",
+ PJMEDIA_STREAM_SIZE,
+ PJMEDIA_STREAM_INC);
+ PJ_ASSERT_RETURN(own_pool != NULL, PJ_ENOMEM);
+ pool = own_pool;
+ }
+
+ /* Allocate the media stream: */
+
+ stream = PJ_POOL_ZALLOC_T(pool, pjmedia_stream);
+ PJ_ASSERT_RETURN(stream != NULL, PJ_ENOMEM);
+ stream->own_pool = own_pool;
+ pj_memcpy(&stream->si, info, sizeof(*info));
+
+ /* Init stream/port name */
+ name.ptr = (char*) pj_pool_alloc(pool, M);
+ name.slen = pj_ansi_snprintf(name.ptr, M, "strm%p", stream);
+
+ /* Init some port-info. Some parts of the info will be set later
+ * once we have more info about the codec.
+ */
+ pjmedia_port_info_init(&stream->port.info, &name,
+ PJMEDIA_SIG_PORT_STREAM,
+ info->fmt.clock_rate, info->fmt.channel_cnt,
+ 16, 80);
+ afd = pjmedia_format_get_audio_format_detail(&stream->port.info.fmt, 1);
+
+ /* Init port. */
+
+ //No longer there in 2.0
+ //pj_strdup(pool, &stream->port.info.encoding_name, &info->fmt.encoding_name);
+ afd->clock_rate = info->fmt.clock_rate;
+ afd->channel_count = info->fmt.channel_cnt;
+ stream->port.port_data.pdata = stream;
+
+ /* Init stream: */
+ stream->endpt = endpt;
+ stream->codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
+ stream->dir = info->dir;
+ stream->user_data = user_data;
+ stream->rtcp_interval = (PJMEDIA_RTCP_INTERVAL-500 + (pj_rand()%1000)) *
+ info->fmt.clock_rate / 1000;
+ stream->rtcp_sdes_bye_disabled = info->rtcp_sdes_bye_disabled;
+
+ stream->tx_event_pt = info->tx_event_pt ? info->tx_event_pt : -1;
+ stream->rx_event_pt = info->rx_event_pt ? info->rx_event_pt : -1;
+ stream->last_dtmf = -1;
+ stream->jb_last_frm = PJMEDIA_JB_NORMAL_FRAME;
+
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
+ stream->use_ka = info->use_ka;
+#endif
+
+ /* Build random RTCP CNAME. CNAME has user@host format */
+ stream->cname.ptr = p = (char*) pj_pool_alloc(pool, 20);
+ pj_create_random_string(p, 5);
+ p += 5;
+ *p++ = '@'; *p++ = 'p'; *p++ = 'j';
+ pj_create_random_string(p, 6);
+ p += 6;
+ *p++ = '.'; *p++ = 'o'; *p++ = 'r'; *p++ = 'g';
+ stream->cname.slen = p - stream->cname.ptr;
+
+
+ /* Create mutex to protect jitter buffer: */
+
+ status = pj_mutex_create_simple(pool, NULL, &stream->jb_mutex);
+ if (status != PJ_SUCCESS)
+ goto err_cleanup;
+
+
+ /* Create and initialize codec: */
+
+ status = pjmedia_codec_mgr_alloc_codec( stream->codec_mgr,
+ &info->fmt, &stream->codec);
+ if (status != PJ_SUCCESS)
+ goto err_cleanup;
+
+
+ /* Get codec param: */
+ if (info->param)
+ stream->codec_param = *info->param;
+ else {
+ status = pjmedia_codec_mgr_get_default_param(stream->codec_mgr,
+ &info->fmt,
+ &stream->codec_param);
+ if (status != PJ_SUCCESS)
+ goto err_cleanup;
+ }
+
+ /* Check for invalid max_bps. */
+ if (stream->codec_param.info.max_bps < stream->codec_param.info.avg_bps)
+ stream->codec_param.info.max_bps = stream->codec_param.info.avg_bps;
+
+ /* Check for invalid frame per packet. */
+ if (stream->codec_param.setting.frm_per_pkt < 1)
+ stream->codec_param.setting.frm_per_pkt = 1;
+
+ /* Init the codec. */
+ status = pjmedia_codec_init(stream->codec, pool);
+ if (status != PJ_SUCCESS)
+ goto err_cleanup;
+
+ /* Open the codec. */
+ status = pjmedia_codec_open(stream->codec, &stream->codec_param);
+ if (status != PJ_SUCCESS)
+ goto err_cleanup;
+
+ /* Set additional info and callbacks. */
+ afd->bits_per_sample = 16;
+ afd->frame_time_usec = stream->codec_param.info.frm_ptime *
+ stream->codec_param.setting.frm_per_pkt * 1000;
+ stream->port.info.fmt.id = stream->codec_param.info.fmt_id;
+ if (stream->codec_param.info.fmt_id == PJMEDIA_FORMAT_L16) {
+ /* Raw format */
+ afd->avg_bps = afd->max_bps = afd->clock_rate * afd->channel_count *
+ afd->bits_per_sample;
+
+ stream->port.put_frame = &put_frame;
+ stream->port.get_frame = &get_frame;
+ } else {
+ /* Encoded format */
+ afd->avg_bps = stream->codec_param.info.avg_bps;
+ afd->max_bps = stream->codec_param.info.max_bps;
+
+ /* Not applicable for 2.0
+ if ((stream->codec_param.info.max_bps *
+ stream->codec_param.info.frm_ptime *
+ stream->codec_param.setting.frm_per_pkt) % 8000 != 0)
+ {
+ ++stream->port.info.bytes_per_frame;
+ }
+ stream->port.info.format.bitrate = stream->codec_param.info.avg_bps;
+ stream->port.info.format.vad = (stream->codec_param.setting.vad != 0);
+ */
+
+ stream->port.put_frame = &put_frame;
+ stream->port.get_frame = &get_frame_ext;
+ }
+
+ /* If encoder and decoder's ptime are asymmetric, then we need to
+ * create buffer on the encoder side. This could happen for example
+ * with iLBC
+ */
+ if (stream->codec_param.info.enc_ptime!=0 &&
+ stream->codec_param.info.enc_ptime!=stream->codec_param.info.frm_ptime)
+ {
+ unsigned ptime;
+
+ stream->enc_samples_per_pkt = stream->codec_param.info.enc_ptime *
+ stream->codec_param.info.channel_cnt *
+ afd->clock_rate / 1000;
+
+ /* Set buffer size as twice the largest ptime value between
+ * stream's ptime, encoder ptime, or decoder ptime.
+ */
+
+ ptime = afd->frame_time_usec / 1000;
+
+ if (stream->codec_param.info.enc_ptime > ptime)
+ ptime = stream->codec_param.info.enc_ptime;
+
+ if (stream->codec_param.info.frm_ptime > ptime)
+ ptime = stream->codec_param.info.frm_ptime;
+
+ ptime <<= 1;
+
+ /* Allocate buffer */
+ stream->enc_buf_size = afd->clock_rate * ptime / 1000;
+ stream->enc_buf = (pj_int16_t*)
+ pj_pool_alloc(pool, stream->enc_buf_size * 2);
+
+ } else {
+ stream->enc_samples_per_pkt = PJMEDIA_AFD_SPF(afd);
+ }
+
+
+ /* Initially disable the VAD in the stream, to help traverse NAT better */
+ stream->vad_enabled = stream->codec_param.setting.vad;
+ if (PJMEDIA_STREAM_VAD_SUSPEND_MSEC > 0 && stream->vad_enabled) {
+ stream->codec_param.setting.vad = 0;
+ stream->ts_vad_disabled = 0;
+ pjmedia_codec_modify(stream->codec, &stream->codec_param);
+ PJ_LOG(4,(stream->port.info.name.ptr,"VAD temporarily disabled"));
+ }
+
+ /* Get the frame size */
+ stream->frame_size = stream->codec_param.info.max_bps *
+ stream->codec_param.info.frm_ptime / 8 / 1000;
+ if ((stream->codec_param.info.max_bps * stream->codec_param.info.frm_ptime)
+ % 8000 != 0)
+ {
+ ++stream->frame_size;
+ }
+
+ /* How many consecutive PLC frames can be generated */
+ stream->max_plc_cnt = (MAX_PLC_MSEC+stream->codec_param.info.frm_ptime-1)/
+ stream->codec_param.info.frm_ptime;
+
+#if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG!=0)
+ stream->rtp_rx_check_cnt = 5;
+ stream->has_g722_mpeg_bug = PJ_FALSE;
+ stream->rtp_rx_last_ts = 0;
+ stream->rtp_rx_last_cnt = 0;
+ stream->rtp_tx_ts_len_per_pkt = stream->enc_samples_per_pkt /
+ stream->codec_param.info.channel_cnt;
+ stream->rtp_rx_ts_len_per_frame = PJMEDIA_AFD_SPF(afd) /
+ stream->codec_param.setting.frm_per_pkt /
+ stream->codec_param.info.channel_cnt;
+
+ if (info->fmt.pt == PJMEDIA_RTP_PT_G722) {
+ stream->has_g722_mpeg_bug = PJ_TRUE;
+ /* RTP clock rate = 1/2 real clock rate */
+ stream->rtp_tx_ts_len_per_pkt >>= 1;
+ }
+#endif
+
+ /* Init jitter buffer parameters: */
+ if (info->jb_max >= stream->codec_param.info.frm_ptime)
+ jb_max = (info->jb_max + stream->codec_param.info.frm_ptime - 1) /
+ stream->codec_param.info.frm_ptime;
+ else
+ jb_max = 500 / stream->codec_param.info.frm_ptime;
+
+ if (info->jb_min_pre >= stream->codec_param.info.frm_ptime)
+ jb_min_pre = info->jb_min_pre / stream->codec_param.info.frm_ptime;
+ else
+ //jb_min_pre = 60 / stream->codec_param.info.frm_ptime;
+ jb_min_pre = 1;
+
+ if (info->jb_max_pre >= stream->codec_param.info.frm_ptime)
+ jb_max_pre = info->jb_max_pre / stream->codec_param.info.frm_ptime;
+ else
+ //jb_max_pre = 240 / stream->codec_param.info.frm_ptime;
+ jb_max_pre = jb_max * 4 / 5;
+
+ if (info->jb_init >= stream->codec_param.info.frm_ptime)
+ jb_init = info->jb_init / stream->codec_param.info.frm_ptime;
+ else
+ //jb_init = (jb_min_pre + jb_max_pre) / 2;
+ jb_init = 0;
+
+ /* Create jitter buffer */
+ status = pjmedia_jbuf_create(pool, &stream->port.info.name,
+ stream->frame_size,
+ stream->codec_param.info.frm_ptime,
+ jb_max, &stream->jb);
+ if (status != PJ_SUCCESS)
+ goto err_cleanup;
+
+
+ /* Set up jitter buffer */
+ pjmedia_jbuf_set_adaptive( stream->jb, jb_init, jb_min_pre, jb_max_pre);
+
+ /* Create decoder channel: */
+
+ status = create_channel( pool, stream, PJMEDIA_DIR_DECODING,
+ info->rx_pt, info, &stream->dec);
+ if (status != PJ_SUCCESS)
+ goto err_cleanup;
+
+
+ /* Create encoder channel: */
+
+ status = create_channel( pool, stream, PJMEDIA_DIR_ENCODING,
+ info->tx_pt, info, &stream->enc);
+ if (status != PJ_SUCCESS)
+ goto err_cleanup;
+
+
+ /* Init RTCP session: */
+
+ {
+ pjmedia_rtcp_session_setting rtcp_setting;
+
+ pjmedia_rtcp_session_setting_default(&rtcp_setting);
+ rtcp_setting.name = stream->port.info.name.ptr;
+ rtcp_setting.ssrc = info->ssrc;
+ rtcp_setting.rtp_ts_base = pj_ntohl(stream->enc->rtp.out_hdr.ts);
+ rtcp_setting.clock_rate = info->fmt.clock_rate;
+ rtcp_setting.samples_per_frame = PJMEDIA_AFD_SPF(afd);
+
+#if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG!=0)
+ /* Special case for G.722 */
+ if (info->fmt.pt == PJMEDIA_RTP_PT_G722) {
+ rtcp_setting.clock_rate = 8000;
+ rtcp_setting.samples_per_frame = 160;
+ }
+#endif
+
+ pjmedia_rtcp_init2(&stream->rtcp, &rtcp_setting);
+
+ if (info->rtp_seq_ts_set) {
+ stream->rtcp.stat.rtp_tx_last_seq = info->rtp_seq;
+ stream->rtcp.stat.rtp_tx_last_ts = info->rtp_ts;
+ }
+ }
+
+ /* Allocate outgoing RTCP buffer, should be enough to hold SR/RR, SDES,
+ * BYE, and XR.
+ */
+ stream->out_rtcp_pkt_size = sizeof(pjmedia_rtcp_sr_pkt) +
+ sizeof(pjmedia_rtcp_common) +
+ (4 + stream->cname.slen) +
+ 32;
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+ if (info->rtcp_xr_enabled) {
+ stream->out_rtcp_pkt_size += sizeof(pjmedia_rtcp_xr_pkt);
+ }
+#endif
+
+ if (stream->out_rtcp_pkt_size > PJMEDIA_MAX_MTU)
+ stream->out_rtcp_pkt_size = PJMEDIA_MAX_MTU;
+
+ stream->out_rtcp_pkt = pj_pool_alloc(pool, stream->out_rtcp_pkt_size);
+
+ /* Only attach transport when stream is ready. */
+ status = pjmedia_transport_attach(tp, stream, &info->rem_addr,
+ &info->rem_rtcp,
+ pj_sockaddr_get_len(&info->rem_addr),
+ &on_rx_rtp, &on_rx_rtcp);
+ if (status != PJ_SUCCESS)
+ goto err_cleanup;
+
+ stream->transport = tp;
+
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+ /* Enable RTCP XR and update stream info/config to RTCP XR */
+ if (info->rtcp_xr_enabled) {
+ int i;
+
+ pjmedia_rtcp_enable_xr(&stream->rtcp, PJ_TRUE);
+
+ /* Set RTCP XR TX interval */
+ if (info->rtcp_xr_interval != 0)
+ stream->rtcp_xr_interval = info->rtcp_xr_interval;
+ else
+ stream->rtcp_xr_interval = (PJMEDIA_RTCP_INTERVAL +
+ (pj_rand() % 8000)) *
+ info->fmt.clock_rate / 1000;
+
+ /* Additional third-party RTCP XR destination */
+ if (info->rtcp_xr_dest.addr.sa_family != 0) {
+ stream->rtcp_xr_dest_len = pj_sockaddr_get_len(&info->rtcp_xr_dest);
+ pj_memcpy(&stream->rtcp_xr_dest, &info->rtcp_xr_dest,
+ stream->rtcp_xr_dest_len);
+ }
+
+ /* jitter buffer adaptive info */
+ i = PJMEDIA_RTCP_XR_JB_ADAPTIVE;
+ pjmedia_rtcp_xr_update_info(&stream->rtcp.xr_session,
+ PJMEDIA_RTCP_XR_INFO_CONF_JBA,
+ i);
+
+ /* Jitter buffer aggressiveness info (estimated) */
+ i = 7;
+ pjmedia_rtcp_xr_update_info(&stream->rtcp.xr_session,
+ PJMEDIA_RTCP_XR_INFO_CONF_JBR,
+ i);
+
+ /* Jitter buffer absolute maximum delay */
+ i = jb_max * stream->codec_param.info.frm_ptime;
+ pjmedia_rtcp_xr_update_info(&stream->rtcp.xr_session,
+ PJMEDIA_RTCP_XR_INFO_JB_ABS_MAX,
+ i);
+
+ /* PLC info */
+ if (stream->codec_param.setting.plc == 0)
+ i = PJMEDIA_RTCP_XR_PLC_DIS;
+ else
+#if PJMEDIA_WSOLA_IMP==PJMEDIA_WSOLA_IMP_WSOLA
+ i = PJMEDIA_RTCP_XR_PLC_ENH;
+#else
+ i = PJMEDIA_RTCP_XR_PLC_DIS;
+#endif
+ pjmedia_rtcp_xr_update_info(&stream->rtcp.xr_session,
+ PJMEDIA_RTCP_XR_INFO_CONF_PLC,
+ i);
+ }
+#endif
+
+ /* Send RTCP SDES */
+ if (!stream->rtcp_sdes_bye_disabled) {
+ pjmedia_stream_send_rtcp_sdes(stream);
+ }
+
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
+ /* NAT hole punching by sending KA packet via RTP transport. */
+ if (stream->use_ka)
+ send_keep_alive_packet(stream);
+#endif
+
+#if TRACE_JB
+ {
+ char trace_name[PJ_MAXPATH];
+ pj_ssize_t len;
+
+ pj_ansi_snprintf(trace_name, sizeof(trace_name),
+ TRACE_JB_PATH_PREFIX "%s.csv",
+ stream->port.info.name.ptr);
+ status = pj_file_open(pool, trace_name, PJ_O_WRONLY, &stream->trace_jb_fd);
+ if (status != PJ_SUCCESS) {
+ stream->trace_jb_fd = TRACE_JB_INVALID_FD;
+ PJ_LOG(3,(THIS_FILE, "Failed creating RTP trace file '%s'",
+ trace_name));
+ } else {
+ stream->trace_jb_buf = (char*)pj_pool_alloc(pool, PJ_LOG_MAX_SIZE);
+
+ /* Print column header */
+ len = pj_ansi_snprintf(stream->trace_jb_buf, PJ_LOG_MAX_SIZE,
+ "Time, Operation, Size, Frame Count, "
+ "Frame type, RTP Seq, RTP TS, RTP M, "
+ "JB size, JB burst level, JB prefetch\n");
+ pj_file_write(stream->trace_jb_fd, stream->trace_jb_buf, &len);
+ pj_file_flush(stream->trace_jb_fd);
+ }
+ }
+#endif
+
+ /* Success! */
+ *p_stream = stream;
+
+ PJ_LOG(5,(THIS_FILE, "Stream %s created", stream->port.info.name.ptr));
+
+ return PJ_SUCCESS;
+
+
+err_cleanup:
+ pjmedia_stream_destroy(stream);
+ return status;
+}
+
+
+/*
+ * Destroy stream.
+ */
+PJ_DEF(pj_status_t) pjmedia_stream_destroy( pjmedia_stream *stream )
+{
+ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+ /* Send RTCP BYE (also SDES & XR) */
+ if (!stream->rtcp_sdes_bye_disabled) {
+ send_rtcp(stream, PJ_TRUE, PJ_TRUE, PJ_TRUE);
+ }
+
+ /* Detach from transport
+ * MUST NOT hold stream mutex while detaching from transport, as
+ * it may cause deadlock. See ticket #460 for the details.
+ */
+ if (stream->transport) {
+ pjmedia_transport_detach(stream->transport, stream);
+ stream->transport = NULL;
+ }
+
+ /* This function may be called when stream is partly initialized. */
+ if (stream->jb_mutex)
+ pj_mutex_lock(stream->jb_mutex);
+
+
+ /* Free codec. */
+
+ if (stream->codec) {
+ pjmedia_codec_close(stream->codec);
+ pjmedia_codec_mgr_dealloc_codec(stream->codec_mgr, stream->codec);
+ stream->codec = NULL;
+ }
+
+ /* Free mutex */
+
+ if (stream->jb_mutex) {
+ pj_mutex_destroy(stream->jb_mutex);
+ stream->jb_mutex = NULL;
+ }
+
+ /* Destroy jitter buffer */
+ if (stream->jb)
+ pjmedia_jbuf_destroy(stream->jb);
+
+#if TRACE_JB
+ if (TRACE_JB_OPENED(stream)) {
+ pj_file_close(stream->trace_jb_fd);
+ stream->trace_jb_fd = TRACE_JB_INVALID_FD;
+ }
+#endif
+
+ if (stream->own_pool) {
+ pj_pool_t *pool = stream->own_pool;
+ stream->own_pool = NULL;
+ pj_pool_release(pool);
+ }
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the last frame frame type retreived from the jitter buffer.
+ */
+PJ_DEF(char) pjmedia_stream_get_last_jb_frame_type(pjmedia_stream *stream)
+{
+ return stream->jb_last_frm;
+}
+
+
+/*
+ * Get the port interface.
+ */
+PJ_DEF(pj_status_t) pjmedia_stream_get_port( pjmedia_stream *stream,
+ pjmedia_port **p_port )
+{
+ *p_port = &stream->port;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the transport object
+ */
+PJ_DEF(pjmedia_transport*) pjmedia_stream_get_transport(pjmedia_stream *st)
+{
+ return st->transport;
+}
+
+
+/*
+ * Start stream.
+ */
+PJ_DEF(pj_status_t) pjmedia_stream_start(pjmedia_stream *stream)
+{
+
+ PJ_ASSERT_RETURN(stream && stream->enc && stream->dec, PJ_EINVALIDOP);
+
+ if (stream->enc && (stream->dir & PJMEDIA_DIR_ENCODING)) {
+ stream->enc->paused = 0;
+ //pjmedia_snd_stream_start(stream->enc->snd_stream);
+ PJ_LOG(4,(stream->port.info.name.ptr, "Encoder stream started"));
+ } else {
+ PJ_LOG(4,(stream->port.info.name.ptr, "Encoder stream paused"));
+ }
+
+ if (stream->dec && (stream->dir & PJMEDIA_DIR_DECODING)) {
+ stream->dec->paused = 0;
+ //pjmedia_snd_stream_start(stream->dec->snd_stream);
+ PJ_LOG(4,(stream->port.info.name.ptr, "Decoder stream started"));
+ } else {
+ PJ_LOG(4,(stream->port.info.name.ptr, "Decoder stream paused"));
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_stream_get_info( const pjmedia_stream *stream,
+ pjmedia_stream_info *info)
+{
+ PJ_ASSERT_RETURN(stream && info, PJ_EINVAL);
+
+ pj_memcpy(info, &stream->si, sizeof(pjmedia_stream_info));
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get stream statistics.
+ */
+PJ_DEF(pj_status_t) pjmedia_stream_get_stat( const pjmedia_stream *stream,
+ pjmedia_rtcp_stat *stat)
+{
+ PJ_ASSERT_RETURN(stream && stat, PJ_EINVAL);
+
+ pj_memcpy(stat, &stream->rtcp.stat, sizeof(pjmedia_rtcp_stat));
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Reset the stream statistics in the middle of a stream session.
+ */
+PJ_DEF(pj_status_t) pjmedia_stream_reset_stat(pjmedia_stream *stream)
+{
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ pjmedia_rtcp_init_stat(&stream->rtcp.stat);
+
+ return PJ_SUCCESS;
+}
+
+
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+/*
+ * Get stream extended statistics.
+ */
+PJ_DEF(pj_status_t) pjmedia_stream_get_stat_xr( const pjmedia_stream *stream,
+ pjmedia_rtcp_xr_stat *stat)
+{
+ PJ_ASSERT_RETURN(stream && stat, PJ_EINVAL);
+
+ if (stream->rtcp.xr_enabled) {
+ pj_memcpy(stat, &stream->rtcp.xr_session.stat, sizeof(pjmedia_rtcp_xr_stat));
+ return PJ_SUCCESS;
+ }
+ return PJ_ENOTFOUND;
+}
+#endif
+
+/*
+ * Get jitter buffer state.
+ */
+PJ_DEF(pj_status_t) pjmedia_stream_get_stat_jbuf(const pjmedia_stream *stream,
+ pjmedia_jb_state *state)
+{
+ PJ_ASSERT_RETURN(stream && state, PJ_EINVAL);
+ return pjmedia_jbuf_get_state(stream->jb, state);
+}
+
+/*
+ * Pause stream.
+ */
+PJ_DEF(pj_status_t) pjmedia_stream_pause( pjmedia_stream *stream,
+ pjmedia_dir dir)
+{
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ if ((dir & PJMEDIA_DIR_ENCODING) && stream->enc) {
+ stream->enc->paused = 1;
+ PJ_LOG(4,(stream->port.info.name.ptr, "Encoder stream paused"));
+ }
+
+ if ((dir & PJMEDIA_DIR_DECODING) && stream->dec) {
+ stream->dec->paused = 1;
+
+ /* Also reset jitter buffer */
+ pj_mutex_lock( stream->jb_mutex );
+ pjmedia_jbuf_reset(stream->jb);
+ pj_mutex_unlock( stream->jb_mutex );
+
+ PJ_LOG(4,(stream->port.info.name.ptr, "Decoder stream paused"));
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Resume stream
+ */
+PJ_DEF(pj_status_t) pjmedia_stream_resume( pjmedia_stream *stream,
+ pjmedia_dir dir)
+{
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ if ((dir & PJMEDIA_DIR_ENCODING) && stream->enc) {
+ stream->enc->paused = 0;
+ PJ_LOG(4,(stream->port.info.name.ptr, "Encoder stream resumed"));
+ }
+
+ if ((dir & PJMEDIA_DIR_DECODING) && stream->dec) {
+ stream->dec->paused = 0;
+ PJ_LOG(4,(stream->port.info.name.ptr, "Decoder stream resumed"));
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Dial DTMF
+ */
+PJ_DEF(pj_status_t) pjmedia_stream_dial_dtmf( pjmedia_stream *stream,
+ const pj_str_t *digit_char)
+{
+ pj_status_t status = PJ_SUCCESS;
+
+ /* By convention we use jitter buffer mutex to access DTMF
+ * queue.
+ */
+ PJ_ASSERT_RETURN(stream && digit_char, PJ_EINVAL);
+
+ /* Check that remote can receive DTMF events. */
+ if (stream->tx_event_pt < 0) {
+ return PJMEDIA_RTP_EREMNORFC2833;
+ }
+
+ pj_mutex_lock(stream->jb_mutex);
+
+ if (stream->tx_dtmf_count+digit_char->slen >=
+ (long)PJ_ARRAY_SIZE(stream->tx_dtmf_buf))
+ {
+ status = PJ_ETOOMANY;
+ } else {
+ int i;
+
+ /* convert ASCII digits into payload type first, to make sure
+ * that all digits are valid.
+ */
+ for (i=0; i<digit_char->slen; ++i) {
+ unsigned pt;
+ int dig = pj_tolower(digit_char->ptr[i]);
+
+ if (dig >= '0' && dig <= '9')
+ {
+ pt = dig - '0';
+ }
+ else if (dig >= 'a' && dig <= 'd')
+ {
+ pt = dig - 'a' + 12;
+ }
+ else if (dig == '*')
+ {
+ pt = 10;
+ }
+ else if (dig == '#')
+ {
+ pt = 11;
+ }
+ else
+ {
+ status = PJMEDIA_RTP_EINDTMF;
+ break;
+ }
+
+ stream->tx_dtmf_buf[stream->tx_dtmf_count+i].event = pt;
+ stream->tx_dtmf_buf[stream->tx_dtmf_count+i].duration = 0;
+ }
+
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Increment digit count only if all digits are valid. */
+ stream->tx_dtmf_count += digit_char->slen;
+ }
+
+on_return:
+ pj_mutex_unlock(stream->jb_mutex);
+
+ return status;
+}
+
+
+/*
+ * See if we have DTMF digits in the rx buffer.
+ */
+PJ_DEF(pj_bool_t) pjmedia_stream_check_dtmf(pjmedia_stream *stream)
+{
+ return stream->rx_dtmf_count != 0;
+}
+
+
+/*
+ * Retrieve incoming DTMF digits from the stream's DTMF buffer.
+ */
+PJ_DEF(pj_status_t) pjmedia_stream_get_dtmf( pjmedia_stream *stream,
+ char *digits,
+ unsigned *size)
+{
+ PJ_ASSERT_RETURN(stream && digits && size, PJ_EINVAL);
+
+ pj_assert(sizeof(stream->rx_dtmf_buf[0]) == 0);
+
+ /* By convention, we use jitter buffer's mutex to access DTMF
+ * digits resources.
+ */
+ pj_mutex_lock(stream->jb_mutex);
+
+ if (stream->rx_dtmf_count < *size)
+ *size = stream->rx_dtmf_count;
+
+ if (*size) {
+ pj_memcpy(digits, stream->rx_dtmf_buf, *size);
+ stream->rx_dtmf_count -= *size;
+ if (stream->rx_dtmf_count) {
+ pj_memmove(stream->rx_dtmf_buf,
+ &stream->rx_dtmf_buf[*size],
+ stream->rx_dtmf_count);
+ }
+ }
+
+ pj_mutex_unlock(stream->jb_mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Set callback to be called upon receiving DTMF digits.
+ */
+PJ_DEF(pj_status_t) pjmedia_stream_set_dtmf_callback(pjmedia_stream *stream,
+ void (*cb)(pjmedia_stream*,
+ void *user_data,
+ int digit),
+ void *user_data)
+{
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ /* By convention, we use jitter buffer's mutex to access DTMF
+ * digits resources.
+ */
+ pj_mutex_lock(stream->jb_mutex);
+
+ stream->dtmf_cb = cb;
+ stream->dtmf_cb_user_data = user_data;
+
+ pj_mutex_unlock(stream->jb_mutex);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Send RTCP SDES.
+ */
+PJ_DEF(pj_status_t)
+pjmedia_stream_send_rtcp_sdes( pjmedia_stream *stream )
+{
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ return send_rtcp(stream, PJ_TRUE, PJ_FALSE, PJ_FALSE);
+}
+
+/*
+ * Send RTCP BYE.
+ */
+PJ_DEF(pj_status_t)
+pjmedia_stream_send_rtcp_bye( pjmedia_stream *stream )
+{
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ if (stream->enc && stream->transport) {
+ return send_rtcp(stream, PJ_TRUE, PJ_TRUE, PJ_FALSE);
+ }
+
+ return PJ_SUCCESS;
+}
diff --git a/pjmedia/src/pjmedia/stream_common.c b/pjmedia/src/pjmedia/stream_common.c
new file mode 100644
index 0000000..391dfc3
--- /dev/null
+++ b/pjmedia/src/pjmedia/stream_common.c
@@ -0,0 +1,111 @@
+/* $Id: stream_common.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia/stream_common.h>
+#include <pj/log.h>
+
+#define THIS_FILE "stream_common.c"
+
+/*
+ * Parse fmtp for specified format/payload type.
+ */
+PJ_DEF(pj_status_t) pjmedia_stream_info_parse_fmtp( pj_pool_t *pool,
+ const pjmedia_sdp_media *m,
+ unsigned pt,
+ pjmedia_codec_fmtp *fmtp)
+{
+ const pjmedia_sdp_attr *attr;
+ pjmedia_sdp_fmtp sdp_fmtp;
+ char *p, *p_end, fmt_buf[8];
+ pj_str_t fmt;
+ pj_status_t status;
+
+ pj_assert(m && fmtp);
+
+ pj_bzero(fmtp, sizeof(pjmedia_codec_fmtp));
+
+ /* Get "fmtp" attribute for the format */
+ pj_ansi_sprintf(fmt_buf, "%d", pt);
+ fmt = pj_str(fmt_buf);
+ attr = pjmedia_sdp_media_find_attr2(m, "fmtp", &fmt);
+ if (attr == NULL)
+ return PJ_SUCCESS;
+
+ /* Parse "fmtp" attribute */
+ status = pjmedia_sdp_attr_get_fmtp(attr, &sdp_fmtp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Prepare parsing */
+ p = sdp_fmtp.fmt_param.ptr;
+ p_end = p + sdp_fmtp.fmt_param.slen;
+
+ /* Parse */
+ while (p < p_end) {
+ char *token, *start, *end;
+
+ if (fmtp->cnt >= PJMEDIA_CODEC_MAX_FMTP_CNT) {
+ PJ_LOG(4,(THIS_FILE,
+ "Warning: fmtp parameter count exceeds "
+ "PJMEDIA_CODEC_MAX_FMTP_CNT"));
+ return PJ_SUCCESS;
+ }
+
+ /* Skip whitespaces */
+ while (p < p_end && (*p == ' ' || *p == '\t')) ++p;
+ if (p == p_end)
+ break;
+
+ /* Get token */
+ start = p;
+ while (p < p_end && *p != ';' && *p != '=') ++p;
+ end = p - 1;
+
+ /* Right trim */
+ while (end >= start && (*end == ' ' || *end == '\t' ||
+ *end == '\r' || *end == '\n' ))
+ --end;
+
+ /* Forward a char after trimming */
+ ++end;
+
+ /* Store token */
+ if (end > start) {
+ if (pool) {
+ token = (char*)pj_pool_alloc(pool, end - start);
+ pj_ansi_strncpy(token, start, end - start);
+ } else {
+ token = start;
+ }
+ if (*p == '=')
+ /* Got param name */
+ pj_strset(&fmtp->param[fmtp->cnt].name, token, end - start);
+ else
+ /* Got param value */
+ pj_strset(&fmtp->param[fmtp->cnt++].val, token, end - start);
+ } else if (*p != '=') {
+ ++fmtp->cnt;
+ }
+
+ /* Next */
+ ++p;
+ }
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjmedia/src/pjmedia/stream_info.c b/pjmedia/src/pjmedia/stream_info.c
new file mode 100644
index 0000000..5d336e5
--- /dev/null
+++ b/pjmedia/src/pjmedia/stream_info.c
@@ -0,0 +1,548 @@
+/* $Id: stream_info.c 3982 2012-03-22 09:56:52Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/stream.h>
+#include <pjmedia/stream_common.h>
+#include <pj/ctype.h>
+#include <pj/rand.h>
+
+static const pj_str_t ID_AUDIO = { "audio", 5};
+static const pj_str_t ID_IN = { "IN", 2 };
+static const pj_str_t ID_IP4 = { "IP4", 3};
+static const pj_str_t ID_IP6 = { "IP6", 3};
+static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 };
+static const pj_str_t ID_RTP_SAVP = { "RTP/SAVP", 8 };
+//static const pj_str_t ID_SDP_NAME = { "pjmedia", 7 };
+static const pj_str_t ID_RTPMAP = { "rtpmap", 6 };
+static const pj_str_t ID_TELEPHONE_EVENT = { "telephone-event", 15 };
+
+static const pj_str_t STR_INACTIVE = { "inactive", 8 };
+static const pj_str_t STR_SENDRECV = { "sendrecv", 8 };
+static const pj_str_t STR_SENDONLY = { "sendonly", 8 };
+static const pj_str_t STR_RECVONLY = { "recvonly", 8 };
+
+
+/*
+ * Internal function for collecting codec info and param from the SDP media.
+ */
+static pj_status_t get_audio_codec_info_param(pjmedia_stream_info *si,
+ pj_pool_t *pool,
+ pjmedia_codec_mgr *mgr,
+ const pjmedia_sdp_media *local_m,
+ const pjmedia_sdp_media *rem_m)
+{
+ const pjmedia_sdp_attr *attr;
+ pjmedia_sdp_rtpmap *rtpmap;
+ unsigned i, fmti, pt = 0;
+ pj_status_t status;
+
+ /* Find the first codec which is not telephone-event */
+ for ( fmti = 0; fmti < local_m->desc.fmt_count; ++fmti ) {
+ pjmedia_sdp_rtpmap r;
+
+ if ( !pj_isdigit(*local_m->desc.fmt[fmti].ptr) )
+ return PJMEDIA_EINVALIDPT;
+ pt = pj_strtoul(&local_m->desc.fmt[fmti]);
+
+ if (pt < 96) {
+ /* This is known static PT. Skip rtpmap checking because it is
+ * optional. */
+ break;
+ }
+
+ attr = pjmedia_sdp_media_find_attr(local_m, &ID_RTPMAP,
+ &local_m->desc.fmt[fmti]);
+ if (attr == NULL)
+ continue;
+
+ status = pjmedia_sdp_attr_get_rtpmap(attr, &r);
+ if (status != PJ_SUCCESS)
+ continue;
+
+ if (pj_strcmp(&r.enc_name, &ID_TELEPHONE_EVENT) != 0)
+ break;
+ }
+ if ( fmti >= local_m->desc.fmt_count )
+ return PJMEDIA_EINVALIDPT;
+
+ /* Get payload type for receiving direction */
+ si->rx_pt = pt;
+
+ /* Get codec info.
+ * For static payload types, get the info from codec manager.
+ * For dynamic payload types, MUST get the rtpmap.
+ */
+ if (pt < 96) {
+ pj_bool_t has_rtpmap;
+
+ rtpmap = NULL;
+ has_rtpmap = PJ_TRUE;
+
+ attr = pjmedia_sdp_media_find_attr(local_m, &ID_RTPMAP,
+ &local_m->desc.fmt[fmti]);
+ if (attr == NULL) {
+ has_rtpmap = PJ_FALSE;
+ }
+ if (attr != NULL) {
+ status = pjmedia_sdp_attr_to_rtpmap(pool, attr, &rtpmap);
+ if (status != PJ_SUCCESS)
+ has_rtpmap = PJ_FALSE;
+ }
+
+ /* Build codec format info: */
+ if (has_rtpmap) {
+ si->fmt.type = si->type;
+ si->fmt.pt = pj_strtoul(&local_m->desc.fmt[fmti]);
+ pj_strdup(pool, &si->fmt.encoding_name, &rtpmap->enc_name);
+ si->fmt.clock_rate = rtpmap->clock_rate;
+
+#if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG != 0)
+ /* The session info should have the actual clock rate, because
+ * this info is used for calculationg buffer size, etc in stream
+ */
+ if (si->fmt.pt == PJMEDIA_RTP_PT_G722)
+ si->fmt.clock_rate = 16000;
+#endif
+
+ /* For audio codecs, rtpmap parameters denotes the number of
+ * channels.
+ */
+ if (si->type == PJMEDIA_TYPE_AUDIO && rtpmap->param.slen) {
+ si->fmt.channel_cnt = (unsigned) pj_strtoul(&rtpmap->param);
+ } else {
+ si->fmt.channel_cnt = 1;
+ }
+
+ } else {
+ const pjmedia_codec_info *p_info;
+
+ status = pjmedia_codec_mgr_get_codec_info( mgr, pt, &p_info);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_memcpy(&si->fmt, p_info, sizeof(pjmedia_codec_info));
+ }
+
+ /* For static payload type, pt's are symetric */
+ si->tx_pt = pt;
+
+ } else {
+ pjmedia_codec_id codec_id;
+ pj_str_t codec_id_st;
+ const pjmedia_codec_info *p_info;
+
+ attr = pjmedia_sdp_media_find_attr(local_m, &ID_RTPMAP,
+ &local_m->desc.fmt[fmti]);
+ if (attr == NULL)
+ return PJMEDIA_EMISSINGRTPMAP;
+
+ status = pjmedia_sdp_attr_to_rtpmap(pool, attr, &rtpmap);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Build codec format info: */
+
+ si->fmt.type = si->type;
+ si->fmt.pt = pj_strtoul(&local_m->desc.fmt[fmti]);
+ si->fmt.encoding_name = rtpmap->enc_name;
+ si->fmt.clock_rate = rtpmap->clock_rate;
+
+ /* For audio codecs, rtpmap parameters denotes the number of
+ * channels.
+ */
+ if (si->type == PJMEDIA_TYPE_AUDIO && rtpmap->param.slen) {
+ si->fmt.channel_cnt = (unsigned) pj_strtoul(&rtpmap->param);
+ } else {
+ si->fmt.channel_cnt = 1;
+ }
+
+ /* Normalize the codec info from codec manager. Note that the
+ * payload type will be resetted to its default (it might have
+ * been rewritten by the SDP negotiator to match to the remote
+ * offer), this is intentional as currently some components may
+ * prefer (or even require) the default PT in codec info.
+ */
+ pjmedia_codec_info_to_id(&si->fmt, codec_id, sizeof(codec_id));
+
+ i = 1;
+ codec_id_st = pj_str(codec_id);
+ status = pjmedia_codec_mgr_find_codecs_by_id(mgr, &codec_id_st,
+ &i, &p_info, NULL);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_memcpy(&si->fmt, p_info, sizeof(pjmedia_codec_info));
+
+ /* Determine payload type for outgoing channel, by finding
+ * dynamic payload type in remote SDP that matches the answer.
+ */
+ si->tx_pt = 0xFFFF;
+ for (i=0; i<rem_m->desc.fmt_count; ++i) {
+ unsigned rpt;
+ pjmedia_sdp_attr *r_attr;
+ pjmedia_sdp_rtpmap r_rtpmap;
+
+ rpt = pj_strtoul(&rem_m->desc.fmt[i]);
+ if (rpt < 96)
+ continue;
+
+ r_attr = pjmedia_sdp_media_find_attr(rem_m, &ID_RTPMAP,
+ &rem_m->desc.fmt[i]);
+ if (!r_attr)
+ continue;
+
+ if (pjmedia_sdp_attr_get_rtpmap(r_attr, &r_rtpmap) != PJ_SUCCESS)
+ continue;
+
+ if (!pj_stricmp(&rtpmap->enc_name, &r_rtpmap.enc_name) &&
+ rtpmap->clock_rate == r_rtpmap.clock_rate)
+ {
+ /* Found matched codec. */
+ si->tx_pt = rpt;
+
+ break;
+ }
+ }
+
+ if (si->tx_pt == 0xFFFF)
+ return PJMEDIA_EMISSINGRTPMAP;
+ }
+
+
+ /* Now that we have codec info, get the codec param. */
+ si->param = PJ_POOL_ALLOC_T(pool, pjmedia_codec_param);
+ status = pjmedia_codec_mgr_get_default_param(mgr, &si->fmt,
+ si->param);
+
+ /* Get remote fmtp for our encoder. */
+ pjmedia_stream_info_parse_fmtp(pool, rem_m, si->tx_pt,
+ &si->param->setting.enc_fmtp);
+
+ /* Get local fmtp for our decoder. */
+ pjmedia_stream_info_parse_fmtp(pool, local_m, si->rx_pt,
+ &si->param->setting.dec_fmtp);
+
+ /* Get the remote ptime for our encoder. */
+ attr = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
+ "ptime", NULL);
+ if (attr) {
+ pj_str_t tmp_val = attr->value;
+ unsigned frm_per_pkt;
+
+ pj_strltrim(&tmp_val);
+
+ /* Round up ptime when the specified is not multiple of frm_ptime */
+ frm_per_pkt = (pj_strtoul(&tmp_val) +
+ si->param->info.frm_ptime/2) /
+ si->param->info.frm_ptime;
+ if (frm_per_pkt != 0) {
+ si->param->setting.frm_per_pkt = (pj_uint8_t)frm_per_pkt;
+ }
+ }
+
+ /* Get remote maxptime for our encoder. */
+ attr = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
+ "maxptime", NULL);
+ if (attr) {
+ pj_str_t tmp_val = attr->value;
+
+ pj_strltrim(&tmp_val);
+ si->tx_maxptime = pj_strtoul(&tmp_val);
+ }
+
+ /* When direction is NONE (it means SDP negotiation has failed) we don't
+ * need to return a failure here, as returning failure will cause
+ * the whole SDP to be rejected. See ticket #:
+ * http://
+ *
+ * Thanks Alain Totouom
+ */
+ if (status != PJ_SUCCESS && si->dir != PJMEDIA_DIR_NONE)
+ return status;
+
+
+ /* Get incomming payload type for telephone-events */
+ si->rx_event_pt = -1;
+ for (i=0; i<local_m->attr_count; ++i) {
+ pjmedia_sdp_rtpmap r;
+
+ attr = local_m->attr[i];
+ if (pj_strcmp(&attr->name, &ID_RTPMAP) != 0)
+ continue;
+ if (pjmedia_sdp_attr_get_rtpmap(attr, &r) != PJ_SUCCESS)
+ continue;
+ if (pj_strcmp(&r.enc_name, &ID_TELEPHONE_EVENT) == 0) {
+ si->rx_event_pt = pj_strtoul(&r.pt);
+ break;
+ }
+ }
+
+ /* Get outgoing payload type for telephone-events */
+ si->tx_event_pt = -1;
+ for (i=0; i<rem_m->attr_count; ++i) {
+ pjmedia_sdp_rtpmap r;
+
+ attr = rem_m->attr[i];
+ if (pj_strcmp(&attr->name, &ID_RTPMAP) != 0)
+ continue;
+ if (pjmedia_sdp_attr_get_rtpmap(attr, &r) != PJ_SUCCESS)
+ continue;
+ if (pj_strcmp(&r.enc_name, &ID_TELEPHONE_EVENT) == 0) {
+ si->tx_event_pt = pj_strtoul(&r.pt);
+ break;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Create stream info from SDP media line.
+ */
+PJ_DEF(pj_status_t) pjmedia_stream_info_from_sdp(
+ pjmedia_stream_info *si,
+ pj_pool_t *pool,
+ pjmedia_endpt *endpt,
+ const pjmedia_sdp_session *local,
+ const pjmedia_sdp_session *remote,
+ unsigned stream_idx)
+{
+ pjmedia_codec_mgr *mgr;
+ const pjmedia_sdp_attr *attr;
+ const pjmedia_sdp_media *local_m;
+ const pjmedia_sdp_media *rem_m;
+ const pjmedia_sdp_conn *local_conn;
+ const pjmedia_sdp_conn *rem_conn;
+ int rem_af, local_af;
+ pj_sockaddr local_addr;
+ pj_status_t status;
+
+
+ /* Validate arguments: */
+ PJ_ASSERT_RETURN(pool && si && local && remote, PJ_EINVAL);
+ PJ_ASSERT_RETURN(stream_idx < local->media_count, PJ_EINVAL);
+ PJ_ASSERT_RETURN(stream_idx < remote->media_count, PJ_EINVAL);
+
+ /* Keep SDP shortcuts */
+ local_m = local->media[stream_idx];
+ rem_m = remote->media[stream_idx];
+
+ local_conn = local_m->conn ? local_m->conn : local->conn;
+ if (local_conn == NULL)
+ return PJMEDIA_SDP_EMISSINGCONN;
+
+ rem_conn = rem_m->conn ? rem_m->conn : remote->conn;
+ if (rem_conn == NULL)
+ return PJMEDIA_SDP_EMISSINGCONN;
+
+ /* Media type must be audio */
+ if (pj_stricmp(&local_m->desc.media, &ID_AUDIO) != 0)
+ return PJMEDIA_EINVALIMEDIATYPE;
+
+ /* Get codec manager. */
+ mgr = pjmedia_endpt_get_codec_mgr(endpt);
+
+ /* Reset: */
+
+ pj_bzero(si, sizeof(*si));
+
+#if PJMEDIA_HAS_RTCP_XR && PJMEDIA_STREAM_ENABLE_XR
+ /* Set default RTCP XR enabled/disabled */
+ si->rtcp_xr_enabled = PJ_TRUE;
+#endif
+
+ /* Media type: */
+ si->type = PJMEDIA_TYPE_AUDIO;
+
+ /* Transport protocol */
+
+ /* At this point, transport type must be compatible,
+ * the transport instance will do more validation later.
+ */
+ status = pjmedia_sdp_transport_cmp(&rem_m->desc.transport,
+ &local_m->desc.transport);
+ if (status != PJ_SUCCESS)
+ return PJMEDIA_SDPNEG_EINVANSTP;
+
+ if (pj_stricmp(&local_m->desc.transport, &ID_RTP_AVP) == 0) {
+
+ si->proto = PJMEDIA_TP_PROTO_RTP_AVP;
+
+ } else if (pj_stricmp(&local_m->desc.transport, &ID_RTP_SAVP) == 0) {
+
+ si->proto = PJMEDIA_TP_PROTO_RTP_SAVP;
+
+ } else {
+
+ si->proto = PJMEDIA_TP_PROTO_UNKNOWN;
+ return PJ_SUCCESS;
+ }
+
+
+ /* Check address family in remote SDP */
+ rem_af = pj_AF_UNSPEC();
+ if (pj_stricmp(&rem_conn->net_type, &ID_IN)==0) {
+ if (pj_stricmp(&rem_conn->addr_type, &ID_IP4)==0) {
+ rem_af = pj_AF_INET();
+ } else if (pj_stricmp(&rem_conn->addr_type, &ID_IP6)==0) {
+ rem_af = pj_AF_INET6();
+ }
+ }
+
+ if (rem_af==pj_AF_UNSPEC()) {
+ /* Unsupported address family */
+ return PJ_EAFNOTSUP;
+ }
+
+ /* Set remote address: */
+ status = pj_sockaddr_init(rem_af, &si->rem_addr, &rem_conn->addr,
+ rem_m->desc.port);
+ if (status != PJ_SUCCESS) {
+ /* Invalid IP address. */
+ return PJMEDIA_EINVALIDIP;
+ }
+
+ /* Check address family of local info */
+ local_af = pj_AF_UNSPEC();
+ if (pj_stricmp(&local_conn->net_type, &ID_IN)==0) {
+ if (pj_stricmp(&local_conn->addr_type, &ID_IP4)==0) {
+ local_af = pj_AF_INET();
+ } else if (pj_stricmp(&local_conn->addr_type, &ID_IP6)==0) {
+ local_af = pj_AF_INET6();
+ }
+ }
+
+ if (local_af==pj_AF_UNSPEC()) {
+ /* Unsupported address family */
+ return PJ_SUCCESS;
+ }
+
+ /* Set remote address: */
+ status = pj_sockaddr_init(local_af, &local_addr, &local_conn->addr,
+ local_m->desc.port);
+ if (status != PJ_SUCCESS) {
+ /* Invalid IP address. */
+ return PJMEDIA_EINVALIDIP;
+ }
+
+ /* Local and remote address family must match */
+ if (local_af != rem_af)
+ return PJ_EAFNOTSUP;
+
+ /* Media direction: */
+
+ if (local_m->desc.port == 0 ||
+ pj_sockaddr_has_addr(&local_addr)==PJ_FALSE ||
+ pj_sockaddr_has_addr(&si->rem_addr)==PJ_FALSE ||
+ pjmedia_sdp_media_find_attr(local_m, &STR_INACTIVE, NULL)!=NULL)
+ {
+ /* Inactive stream. */
+
+ si->dir = PJMEDIA_DIR_NONE;
+
+ } else if (pjmedia_sdp_media_find_attr(local_m, &STR_SENDONLY, NULL)!=NULL) {
+
+ /* Send only stream. */
+
+ si->dir = PJMEDIA_DIR_ENCODING;
+
+ } else if (pjmedia_sdp_media_find_attr(local_m, &STR_RECVONLY, NULL)!=NULL) {
+
+ /* Recv only stream. */
+
+ si->dir = PJMEDIA_DIR_DECODING;
+
+ } else {
+
+ /* Send and receive stream. */
+
+ si->dir = PJMEDIA_DIR_ENCODING_DECODING;
+
+ }
+
+ /* No need to do anything else if stream is rejected */
+ if (local_m->desc.port == 0) {
+ return PJ_SUCCESS;
+ }
+
+ /* If "rtcp" attribute is present in the SDP, set the RTCP address
+ * from that attribute. Otherwise, calculate from RTP address.
+ */
+ attr = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
+ "rtcp", NULL);
+ if (attr) {
+ pjmedia_sdp_rtcp_attr rtcp;
+ status = pjmedia_sdp_attr_get_rtcp(attr, &rtcp);
+ if (status == PJ_SUCCESS) {
+ if (rtcp.addr.slen) {
+ status = pj_sockaddr_init(rem_af, &si->rem_rtcp, &rtcp.addr,
+ (pj_uint16_t)rtcp.port);
+ } else {
+ pj_sockaddr_init(rem_af, &si->rem_rtcp, NULL,
+ (pj_uint16_t)rtcp.port);
+ pj_memcpy(pj_sockaddr_get_addr(&si->rem_rtcp),
+ pj_sockaddr_get_addr(&si->rem_addr),
+ pj_sockaddr_get_addr_len(&si->rem_addr));
+ }
+ }
+ }
+
+ if (!pj_sockaddr_has_addr(&si->rem_rtcp)) {
+ int rtcp_port;
+
+ pj_memcpy(&si->rem_rtcp, &si->rem_addr, sizeof(pj_sockaddr));
+ rtcp_port = pj_sockaddr_get_port(&si->rem_addr) + 1;
+ pj_sockaddr_set_port(&si->rem_rtcp, (pj_uint16_t)rtcp_port);
+ }
+
+
+ /* Get the payload number for receive channel. */
+ /*
+ Previously we used to rely on fmt[0] being the selected codec,
+ but some UA sends telephone-event as fmt[0] and this would
+ cause assert failure below.
+
+ Thanks Chris Hamilton <chamilton .at. cs.dal.ca> for this patch.
+
+ // And codec must be numeric!
+ if (!pj_isdigit(*local_m->desc.fmt[0].ptr) ||
+ !pj_isdigit(*rem_m->desc.fmt[0].ptr))
+ {
+ return PJMEDIA_EINVALIDPT;
+ }
+
+ pt = pj_strtoul(&local_m->desc.fmt[0]);
+ pj_assert(PJMEDIA_RTP_PT_TELEPHONE_EVENTS==0 ||
+ pt != PJMEDIA_RTP_PT_TELEPHONE_EVENTS);
+ */
+
+ /* Get codec info and param */
+ status = get_audio_codec_info_param(si, pool, mgr, local_m, rem_m);
+
+ /* Leave SSRC to random. */
+ si->ssrc = pj_rand();
+
+ /* Set default jitter buffer parameter. */
+ si->jb_init = si->jb_max = si->jb_min_pre = si->jb_max_pre = -1;
+
+ return status;
+}
+
diff --git a/pjmedia/src/pjmedia/tonegen.c b/pjmedia/src/pjmedia/tonegen.c
new file mode 100644
index 0000000..f20c371
--- /dev/null
+++ b/pjmedia/src/pjmedia/tonegen.c
@@ -0,0 +1,898 @@
+/* $Id: tonegen.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/tonegen.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/silencedet.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+#include <pj/lock.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+
+/* amplitude */
+#define AMP PJMEDIA_TONEGEN_VOLUME
+
+#ifndef M_PI
+# define M_PI ((DATA)3.141592653589793238462643383279)
+#endif
+
+#if PJMEDIA_TONEGEN_ALG==PJMEDIA_TONEGEN_SINE
+ #include <math.h>
+ #define DATA double
+
+ /*
+ * This is the good old tone generator using sin().
+ * Speed = 1347 usec to generate 1 second, 8KHz dual-tones (2.66GHz P4).
+ * approx. 10.91 MIPS
+ *
+ * 506,535 usec/100.29 MIPS on ARM926EJ-S.
+ */
+ struct gen
+ {
+ DATA add;
+ DATA c;
+ DATA vol;
+ };
+
+ #define GEN_INIT(var,R,F,A) var.add = ((DATA)F)/R, var.c=0, var.vol=A
+ #define GEN_SAMP(val,var) val = (short)(sin(var.c * 2 * M_PI) * \
+ var.vol); \
+ var.c += var.add
+
+#elif PJMEDIA_TONEGEN_ALG==PJMEDIA_TONEGEN_FLOATING_POINT
+ #include <math.h>
+ #define DATA float
+
+ /*
+ * Default floating-point based tone generation using sine wave
+ * generation from:
+ * http://www.musicdsp.org/showone.php?id=10.
+ * This produces good quality tone in relatively faster time than
+ * the normal sin() generator.
+ * Speed = 350 usec to generate 1 second, 8KHz dual-tones (2.66GHz P4).
+ * approx. 2.84 MIPS
+ *
+ * 18,037 usec/3.57 MIPS on ARM926EJ-S.
+ */
+ struct gen
+ {
+ DATA a, s0, s1;
+ };
+
+ #define GEN_INIT(var,R,F,A) var.a = (DATA) (2.0 * sin(M_PI * F / R)); \
+ var.s0 = 0; \
+ var.s1 = (DATA)(0 - (int)A)
+ #define GEN_SAMP(val,var) var.s0 = var.s0 - var.a * var.s1; \
+ var.s1 = var.s1 + var.a * var.s0; \
+ val = (short) var.s0
+
+#elif PJMEDIA_TONEGEN_ALG==PJMEDIA_TONEGEN_FIXED_POINT_CORDIC
+ /* Cordic algorithm with 28 bit size, from:
+ * http://www.dcs.gla.ac.uk/~jhw/cordic/
+ * Speed = 742 usec to generate 1 second, 8KHz dual-tones (2.66GHz P4).
+ * (PJMEDIA_TONEGEN_FIXED_POINT_CORDIC_LOOP=7)
+ * approx. 6.01 MIPS
+ *
+ * ARM926EJ-S results:
+ * loop=7: 8,943 usec/1.77 MIPS
+ * loop=8: 9,872 usec/1.95 MIPS
+ * loop=10: 11,662 usec/2.31 MIPS
+ * loop=12: 13,561 usec/2.69 MIPS
+ */
+ #define CORDIC_1K 0x026DD3B6
+ #define CORDIC_HALF_PI 0x06487ED5
+ #define CORDIC_PI (CORDIC_HALF_PI * 2)
+ #define CORDIC_MUL_BITS 26
+ #define CORDIC_MUL (1 << CORDIC_MUL_BITS)
+ #define CORDIC_NTAB 28
+ #define CORDIC_LOOP PJMEDIA_TONEGEN_FIXED_POINT_CORDIC_LOOP
+
+ static int cordic_ctab [] =
+ {
+ 0x03243F6A, 0x01DAC670, 0x00FADBAF, 0x007F56EA, 0x003FEAB7,
+ 0x001FFD55, 0x000FFFAA, 0x0007FFF5, 0x0003FFFE, 0x0001FFFF,
+ 0x0000FFFF, 0x00007FFF, 0x00003FFF, 0x00001FFF, 0x00000FFF,
+ 0x000007FF, 0x000003FF, 0x000001FF, 0x000000FF, 0x0000007F,
+ 0x0000003F, 0x0000001F, 0x0000000F, 0x00000007, 0x00000003,
+ 0x00000001, 0x00000000, 0x00000000
+ };
+
+ static pj_int32_t cordic(pj_int32_t theta, unsigned n)
+ {
+ unsigned k;
+ int d;
+ pj_int32_t tx;
+ pj_int32_t x = CORDIC_1K, y = 0, z = theta;
+
+ for (k=0; k<n; ++k) {
+ #if 0
+ d = (z>=0) ? 0 : -1;
+ #else
+ /* Only slightly (~2.5%) faster, but not portable? */
+ d = z>>27;
+ #endif
+ tx = x - (((y>>k) ^ d) - d);
+ y = y + (((x>>k) ^ d) - d);
+ z = z - ((cordic_ctab[k] ^ d) - d);
+ x = tx;
+ }
+ return y;
+ }
+
+ /* Note: theta must be uint32 here */
+ static pj_int32_t cordic_sin(pj_uint32_t theta, unsigned n)
+ {
+ if (theta < CORDIC_HALF_PI)
+ return cordic(theta, n);
+ else if (theta < CORDIC_PI)
+ return cordic(CORDIC_HALF_PI-(theta-CORDIC_HALF_PI), n);
+ else if (theta < CORDIC_PI + CORDIC_HALF_PI)
+ return -cordic(theta - CORDIC_PI, n);
+ else if (theta < 2 * CORDIC_PI)
+ return -cordic(CORDIC_HALF_PI-(theta-3*CORDIC_HALF_PI), n);
+ else {
+ pj_assert(!"Invalid cordic_sin() value");
+ return 0;
+ }
+ }
+
+ struct gen
+ {
+ unsigned add;
+ pj_uint32_t c;
+ unsigned vol;
+ };
+
+ #define VOL(var,v) (((v) * var.vol) >> 15)
+ #define GEN_INIT(var,R,F,A) gen_init(&var, R, F, A)
+ #define GEN_SAMP(val,var) val = gen_samp(&var)
+
+ static void gen_init(struct gen *var, unsigned R, unsigned F, unsigned A)
+ {
+ var->add = 2*CORDIC_PI/R * F;
+ var->c = 0;
+ var->vol = A;
+ }
+
+ PJ_INLINE(short) gen_samp(struct gen *var)
+ {
+ pj_int32_t val;
+ val = cordic_sin(var->c, CORDIC_LOOP);
+ /*val = (val * 32767) / CORDIC_MUL;
+ *val = VOL((*var), val);
+ */
+ val = ((val >> 10) * var->vol) >> 16;
+ var->c += var->add;
+ if (var->c > 2*CORDIC_PI)
+ var->c -= (2 * CORDIC_PI);
+ return (short) val;
+ }
+
+#elif PJMEDIA_TONEGEN_ALG==PJMEDIA_TONEGEN_FAST_FIXED_POINT
+
+ /*
+ * Fallback algorithm when floating point is disabled.
+ * This is a very fast fixed point tone generation using sine wave
+ * approximation from
+ * http://www.audiomulch.com/~rossb/code/sinusoids/
+ * Quality wise not so good, but it's blazing fast!
+ * Speed = 117 usec to generate 1 second, 8KHz dual-tones (2.66GHz P4).
+ * approx. 0.95 MIPS
+ *
+ * 1,449 usec/0.29 MIPS on ARM926EJ-S.
+ */
+ PJ_INLINE(int) approximate_sin3(unsigned x)
+ {
+ unsigned s=-(int)(x>>31);
+ x+=x;
+ x=x>>16;
+ x*=x^0xffff; // x=x*(2-x)
+ x+=x; // optional
+ return x^s;
+ }
+ struct gen
+ {
+ unsigned add;
+ unsigned c;
+ unsigned vol;
+ };
+
+ #define MAXI ((unsigned)0xFFFFFFFF)
+ #define SIN approximate_sin3
+ #define VOL(var,v) (((v) * var.vol) >> 15)
+ #define GEN_INIT(var,R,F,A) var.add = MAXI/R * F, var.c=0, var.vol=A
+ #define GEN_SAMP(val,var) val = (short) VOL(var,SIN(var.c)>>16); \
+ var.c += var.add
+
+#else
+ #error "PJMEDIA_TONEGEN_ALG is not set correctly"
+#endif
+
+struct gen_state
+{
+ struct gen tone1;
+ struct gen tone2;
+ pj_bool_t has_tone2;
+};
+
+
+static void init_generate_single_tone(struct gen_state *state,
+ unsigned clock_rate,
+ unsigned freq,
+ unsigned vol)
+{
+ GEN_INIT(state->tone1,clock_rate,freq,vol);
+ state->has_tone2 = PJ_FALSE;
+}
+
+static void generate_single_tone(struct gen_state *state,
+ unsigned channel_count,
+ unsigned samples,
+ short buf[])
+{
+ short *end = buf + samples;
+
+ if (channel_count==1) {
+
+ while (buf < end) {
+ GEN_SAMP(*buf++, state->tone1);
+ }
+
+ } else if (channel_count == 2) {
+
+ while (buf < end) {
+ GEN_SAMP(*buf, state->tone1);
+ *(buf+1) = *buf;
+ buf += 2;
+ }
+ }
+}
+
+
+static void init_generate_dual_tone(struct gen_state *state,
+ unsigned clock_rate,
+ unsigned freq1,
+ unsigned freq2,
+ unsigned vol)
+{
+ GEN_INIT(state->tone1,clock_rate,freq1,vol);
+ GEN_INIT(state->tone2,clock_rate,freq2,vol);
+ state->has_tone2 = PJ_TRUE;
+}
+
+
+static void generate_dual_tone(struct gen_state *state,
+ unsigned channel_count,
+ unsigned samples,
+ short buf[])
+{
+ short *end = buf + samples;
+
+ if (channel_count==1) {
+ int val, val2;
+ while (buf < end) {
+ GEN_SAMP(val, state->tone1);
+ GEN_SAMP(val2, state->tone2);
+ *buf++ = (short)((val+val2) >> 1);
+ }
+ } else if (channel_count == 2) {
+ int val, val2;
+ while (buf < end) {
+
+ GEN_SAMP(val, state->tone1);
+ GEN_SAMP(val2, state->tone2);
+ val = (val + val2) >> 1;
+
+ *buf++ = (short)val;
+ *buf++ = (short)val;
+ }
+ }
+}
+
+
+static void init_generate_tone(struct gen_state *state,
+ unsigned clock_rate,
+ unsigned freq1,
+ unsigned freq2,
+ unsigned vol)
+{
+ if (freq2)
+ init_generate_dual_tone(state, clock_rate, freq1, freq2 ,vol);
+ else
+ init_generate_single_tone(state, clock_rate, freq1,vol);
+}
+
+
+static void generate_tone(struct gen_state *state,
+ unsigned channel_count,
+ unsigned samples,
+ short buf[])
+{
+ if (!state->has_tone2)
+ generate_single_tone(state, channel_count, samples, buf);
+ else
+ generate_dual_tone(state, channel_count, samples, buf);
+}
+
+
+/****************************************************************************/
+
+#define SIGNATURE PJMEDIA_SIG_PORT_TONEGEN
+#define THIS_FILE "tonegen.c"
+
+#if 0
+# define TRACE_(expr) PJ_LOG(4,expr)
+#else
+# define TRACE_(expr)
+#endif
+
+enum flags
+{
+ PJMEDIA_TONE_INITIALIZED = 1,
+ PJMEDIA_TONE_ENABLE_FADE = 2
+};
+
+struct tonegen
+{
+ pjmedia_port base;
+
+ /* options */
+ unsigned options;
+ unsigned playback_options;
+ unsigned fade_in_len; /* fade in for this # of samples */
+ unsigned fade_out_len; /* fade out for this # of samples*/
+
+ /* lock */
+ pj_lock_t *lock;
+
+ /* Digit map */
+ pjmedia_tone_digit_map *digit_map;
+
+ /* Tone generation state */
+ struct gen_state state;
+
+ /* Currently played digits: */
+ unsigned count; /* # of digits */
+ unsigned cur_digit; /* currently played */
+ unsigned dig_samples; /* sample pos in cur digit */
+ pjmedia_tone_desc digits[PJMEDIA_TONEGEN_MAX_DIGITS];/* array of digits*/
+};
+
+
+/* Default digit map is DTMF */
+static pjmedia_tone_digit_map digit_map =
+{
+ 16,
+ {
+ { '0', 941, 1336 },
+ { '1', 697, 1209 },
+ { '2', 697, 1336 },
+ { '3', 697, 1477 },
+ { '4', 770, 1209 },
+ { '5', 770, 1336 },
+ { '6', 770, 1477 },
+ { '7', 852, 1209 },
+ { '8', 852, 1336 },
+ { '9', 852, 1477 },
+ { 'a', 697, 1633 },
+ { 'b', 770, 1633 },
+ { 'c', 852, 1633 },
+ { 'd', 941, 1633 },
+ { '*', 941, 1209 },
+ { '#', 941, 1477 },
+ }
+};
+
+
+static pj_status_t tonegen_get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t tonegen_destroy(pjmedia_port *this_port);
+
+/*
+ * Create an instance of tone generator with the specified parameters.
+ * When the tone generator is first created, it will be loaded with the
+ * default digit map.
+ */
+PJ_DEF(pj_status_t) pjmedia_tonegen_create2(pj_pool_t *pool,
+ const pj_str_t *name,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ unsigned options,
+ pjmedia_port **p_port)
+{
+ const pj_str_t STR_TONE_GEN = pj_str("tonegen");
+ struct tonegen *tonegen;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pool && clock_rate && channel_count &&
+ samples_per_frame && bits_per_sample == 16 &&
+ p_port != NULL, PJ_EINVAL);
+
+ /* Only support mono and stereo */
+ PJ_ASSERT_RETURN(channel_count==1 || channel_count==2, PJ_EINVAL);
+
+ /* Create and initialize port */
+ tonegen = PJ_POOL_ZALLOC_T(pool, struct tonegen);
+ if (name == NULL || name->slen == 0) name = &STR_TONE_GEN;
+ status = pjmedia_port_info_init(&tonegen->base.info, name,
+ SIGNATURE, clock_rate, channel_count,
+ bits_per_sample, samples_per_frame);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ tonegen->options = options;
+ tonegen->base.get_frame = &tonegen_get_frame;
+ tonegen->base.on_destroy = &tonegen_destroy;
+ tonegen->digit_map = &digit_map;
+
+ tonegen->fade_in_len = PJMEDIA_TONEGEN_FADE_IN_TIME * clock_rate / 1000;
+ tonegen->fade_out_len = PJMEDIA_TONEGEN_FADE_OUT_TIME * clock_rate / 1000;
+
+ /* Lock */
+ if (options & PJMEDIA_TONEGEN_NO_LOCK) {
+ status = pj_lock_create_null_mutex(pool, "tonegen", &tonegen->lock);
+ } else {
+ status = pj_lock_create_simple_mutex(pool, "tonegen", &tonegen->lock);
+ }
+
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ TRACE_((THIS_FILE, "Tonegen created: %u/%u/%u/%u", clock_rate,
+ channel_count, samples_per_frame, bits_per_sample));
+
+ /* Done */
+ *p_port = &tonegen->base;
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_tonegen_create( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample,
+ unsigned options,
+ pjmedia_port **p_port)
+{
+ return pjmedia_tonegen_create2(pool, NULL, clock_rate, channel_count,
+ samples_per_frame, bits_per_sample,
+ options, p_port);
+}
+
+
+/*
+ * Check if the tone generator is still busy producing some tones.
+ */
+PJ_DEF(pj_bool_t) pjmedia_tonegen_is_busy(pjmedia_port *port)
+{
+ struct tonegen *tonegen = (struct tonegen*) port;
+ PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_TRUE);
+ return tonegen->count != 0;
+}
+
+
+/*
+ * Instruct the tone generator to stop current processing.
+ */
+PJ_DEF(pj_status_t) pjmedia_tonegen_stop(pjmedia_port *port)
+{
+ struct tonegen *tonegen = (struct tonegen*) port;
+ PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);
+
+ TRACE_((THIS_FILE, "tonegen_stop()"));
+
+ pj_lock_acquire(tonegen->lock);
+ tonegen->count = 0;
+ tonegen->cur_digit = 0;
+ tonegen->dig_samples = 0;
+ pj_lock_release(tonegen->lock);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Instruct the tone generator to stop current processing.
+ */
+PJ_DEF(pj_status_t) pjmedia_tonegen_rewind(pjmedia_port *port)
+{
+ struct tonegen *tonegen = (struct tonegen*) port;
+ PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);
+
+ TRACE_((THIS_FILE, "tonegen_rewind()"));
+
+ /* Reset back to the first tone */
+ pj_lock_acquire(tonegen->lock);
+ tonegen->cur_digit = 0;
+ tonegen->dig_samples = 0;
+ pj_lock_release(tonegen->lock);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Callback to destroy tonegen
+ */
+static pj_status_t tonegen_destroy(pjmedia_port *port)
+{
+ struct tonegen *tonegen = (struct tonegen*) port;
+ PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);
+
+ TRACE_((THIS_FILE, "tonegen_destroy()"));
+
+ pj_lock_acquire(tonegen->lock);
+ pj_lock_release(tonegen->lock);
+
+ pj_lock_destroy(tonegen->lock);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Fill a frame with tones.
+ */
+static pj_status_t tonegen_get_frame(pjmedia_port *port,
+ pjmedia_frame *frame)
+{
+ struct tonegen *tonegen = (struct tonegen*) port;
+ short *dst, *end;
+ unsigned clock_rate = PJMEDIA_PIA_SRATE(&tonegen->base.info);
+
+ PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);
+
+ pj_lock_acquire(tonegen->lock);
+
+ if (tonegen->count == 0) {
+ /* We don't have digits to play */
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+ goto on_return;
+ }
+
+ if (tonegen->cur_digit > tonegen->count) {
+ /* We have played all the digits */
+ if ((tonegen->options|tonegen->playback_options)&PJMEDIA_TONEGEN_LOOP)
+ {
+ /* Reset back to the first tone */
+ tonegen->cur_digit = 0;
+ tonegen->dig_samples = 0;
+
+ TRACE_((THIS_FILE, "tonegen_get_frame(): rewind"));
+
+ } else {
+ tonegen->count = 0;
+ tonegen->cur_digit = 0;
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+ TRACE_((THIS_FILE, "tonegen_get_frame(): no more digit"));
+ goto on_return;
+ }
+ }
+
+ if (tonegen->dig_samples>=(tonegen->digits[tonegen->cur_digit].on_msec+
+ tonegen->digits[tonegen->cur_digit].off_msec)*
+ clock_rate / 1000)
+ {
+ /* We have finished with current digit */
+ tonegen->cur_digit++;
+ tonegen->dig_samples = 0;
+
+ TRACE_((THIS_FILE, "tonegen_get_frame(): next digit"));
+ }
+
+ if (tonegen->cur_digit >= tonegen->count) {
+ /* After we're finished with the last digit, we have played all
+ * the digits
+ */
+ if ((tonegen->options|tonegen->playback_options)&PJMEDIA_TONEGEN_LOOP)
+ {
+ /* Reset back to the first tone */
+ tonegen->cur_digit = 0;
+ tonegen->dig_samples = 0;
+
+ TRACE_((THIS_FILE, "tonegen_get_frame(): rewind"));
+
+ } else {
+ tonegen->count = 0;
+ tonegen->cur_digit = 0;
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+ TRACE_((THIS_FILE, "tonegen_get_frame(): no more digit"));
+ goto on_return;
+ }
+ }
+
+ dst = (short*) frame->buf;
+ end = dst + PJMEDIA_PIA_SPF(&port->info);
+
+ while (dst < end) {
+ pjmedia_tone_desc *dig = &tonegen->digits[tonegen->cur_digit];
+ unsigned required, cnt, on_samp, off_samp;
+
+ required = end - dst;
+ on_samp = dig->on_msec * clock_rate / 1000;
+ off_samp = dig->off_msec * clock_rate / 1000;
+
+ /* Init tonegen */
+ if (tonegen->dig_samples == 0 &&
+ (tonegen->count!=1 || !(dig->flags & PJMEDIA_TONE_INITIALIZED)))
+ {
+ init_generate_tone(&tonegen->state,
+ PJMEDIA_PIA_SRATE(&port->info),
+ dig->freq1, dig->freq2, dig->volume);
+ dig->flags |= PJMEDIA_TONE_INITIALIZED;
+ if (tonegen->cur_digit > 0) {
+ /* Clear initialized flag of previous digit */
+ tonegen->digits[tonegen->cur_digit-1].flags &=
+ (~PJMEDIA_TONE_INITIALIZED);
+ }
+ }
+
+ /* Add tone signal */
+ if (tonegen->dig_samples < on_samp) {
+ cnt = on_samp - tonegen->dig_samples;
+ if (cnt > required)
+ cnt = required;
+ generate_tone(&tonegen->state,
+ PJMEDIA_PIA_CCNT(&port->info),
+ cnt, dst);
+
+ dst += cnt;
+ tonegen->dig_samples += cnt;
+ required -= cnt;
+
+ if ((dig->flags & PJMEDIA_TONE_ENABLE_FADE) &&
+ tonegen->dig_samples == cnt)
+ {
+ /* Fade in */
+ short *samp = (dst - cnt);
+ short *end;
+
+ if (cnt > tonegen->fade_in_len)
+ cnt = tonegen->fade_in_len;
+ end = samp + cnt;
+ if (cnt) {
+ const unsigned step = 0xFFFF / cnt;
+ unsigned scale = 0;
+
+ for (; samp < end; ++samp) {
+ (*samp) = (short)(((*samp) * scale) >> 16);
+ scale += step;
+ }
+ }
+ } else if ((dig->flags & PJMEDIA_TONE_ENABLE_FADE) &&
+ tonegen->dig_samples==on_samp)
+ {
+ /* Fade out */
+ if (cnt > tonegen->fade_out_len)
+ cnt = tonegen->fade_out_len;
+ if (cnt) {
+ short *samp = (dst - cnt);
+ const unsigned step = 0xFFFF / cnt;
+ unsigned scale = 0xFFFF - step;
+
+ for (; samp < dst; ++samp) {
+ (*samp) = (short)(((*samp) * scale) >> 16);
+ scale -= step;
+ }
+ }
+ }
+
+ if (dst == end)
+ break;
+ }
+
+ /* Add silence signal */
+ cnt = off_samp + on_samp - tonegen->dig_samples;
+ if (cnt > required)
+ cnt = required;
+ pjmedia_zero_samples(dst, cnt);
+ dst += cnt;
+ tonegen->dig_samples += cnt;
+
+ /* Move to next digit if we're finished with this tone */
+ if (tonegen->dig_samples >= on_samp + off_samp) {
+ tonegen->cur_digit++;
+ tonegen->dig_samples = 0;
+
+ if (tonegen->cur_digit >= tonegen->count) {
+ /* All digits have been played */
+ if ((tonegen->options & PJMEDIA_TONEGEN_LOOP) ||
+ (tonegen->playback_options & PJMEDIA_TONEGEN_LOOP))
+ {
+ tonegen->cur_digit = 0;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ if (dst < end)
+ pjmedia_zero_samples(dst, end-dst);
+
+ frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame->size = PJMEDIA_PIA_AVG_FSZ(&port->info);
+
+ TRACE_((THIS_FILE, "tonegen_get_frame(): frame created, level=%u",
+ pjmedia_calc_avg_signal((pj_int16_t*)frame->buf, frame->size/2)));
+
+ if (tonegen->cur_digit >= tonegen->count) {
+ if ((tonegen->options|tonegen->playback_options)&PJMEDIA_TONEGEN_LOOP)
+ {
+ /* Reset back to the first tone */
+ tonegen->cur_digit = 0;
+ tonegen->dig_samples = 0;
+
+ TRACE_((THIS_FILE, "tonegen_get_frame(): rewind"));
+
+ } else {
+ tonegen->count = 0;
+ tonegen->cur_digit = 0;
+
+ TRACE_((THIS_FILE, "tonegen_get_frame(): no more digit"));
+ }
+ }
+
+on_return:
+ pj_lock_release(tonegen->lock);
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Play tones.
+ */
+PJ_DEF(pj_status_t) pjmedia_tonegen_play( pjmedia_port *port,
+ unsigned count,
+ const pjmedia_tone_desc tones[],
+ unsigned options)
+{
+ struct tonegen *tonegen = (struct tonegen*) port;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(port && port->info.signature == SIGNATURE &&
+ count && tones, PJ_EINVAL);
+
+ /* Don't put more than available buffer */
+ PJ_ASSERT_RETURN(count+tonegen->count <= PJMEDIA_TONEGEN_MAX_DIGITS,
+ PJ_ETOOMANY);
+
+ pj_lock_acquire(tonegen->lock);
+
+ /* Set playback options */
+ tonegen->playback_options = options;
+
+ /* Copy digits */
+ pj_memcpy(tonegen->digits + tonegen->count,
+ tones, count * sizeof(pjmedia_tone_desc));
+
+ /* Normalize volume, and check if we need to disable fading.
+ * Disable fading if tone off time is zero. Application probably
+ * wants to play this tone continuously (e.g. dial tone).
+ */
+ for (i=0; i<count; ++i) {
+ pjmedia_tone_desc *t = &tonegen->digits[i+tonegen->count];
+ if (t->volume == 0)
+ t->volume = AMP;
+ else if (t->volume < 0)
+ t->volume = (short) -t->volume;
+ /* Reset flags */
+ t->flags = 0;
+ if (t->off_msec != 0)
+ t->flags |= PJMEDIA_TONE_ENABLE_FADE;
+ }
+
+ tonegen->count += count;
+
+ pj_lock_release(tonegen->lock);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Play digits.
+ */
+PJ_DEF(pj_status_t) pjmedia_tonegen_play_digits( pjmedia_port *port,
+ unsigned count,
+ const pjmedia_tone_digit digits[],
+ unsigned options)
+{
+ struct tonegen *tonegen = (struct tonegen*) port;
+ pjmedia_tone_desc tones[PJMEDIA_TONEGEN_MAX_DIGITS];
+ const pjmedia_tone_digit_map *map;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(port && port->info.signature == SIGNATURE &&
+ count && digits, PJ_EINVAL);
+ PJ_ASSERT_RETURN(count < PJMEDIA_TONEGEN_MAX_DIGITS, PJ_ETOOMANY);
+
+ pj_lock_acquire(tonegen->lock);
+
+ map = tonegen->digit_map;
+
+ for (i=0; i<count; ++i) {
+ int d = pj_tolower(digits[i].digit);
+ unsigned j;
+
+ /* Translate ASCII digits with digitmap */
+ for (j=0; j<map->count; ++j) {
+ if (d == map->digits[j].digit)
+ break;
+ }
+ if (j == map->count) {
+ pj_lock_release(tonegen->lock);
+ return PJMEDIA_RTP_EINDTMF;
+ }
+
+ tones[i].freq1 = map->digits[j].freq1;
+ tones[i].freq2 = map->digits[j].freq2;
+ tones[i].on_msec = digits[i].on_msec;
+ tones[i].off_msec = digits[i].off_msec;
+ tones[i].volume = digits[i].volume;
+ }
+
+ pj_lock_release(tonegen->lock);
+
+ return pjmedia_tonegen_play(port, count, tones, options);
+}
+
+
+/*
+ * Get the digit-map currently used by this tone generator.
+ */
+PJ_DEF(pj_status_t) pjmedia_tonegen_get_digit_map(pjmedia_port *port,
+ const pjmedia_tone_digit_map **m)
+{
+ struct tonegen *tonegen = (struct tonegen*) port;
+
+ PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);
+ PJ_ASSERT_RETURN(m != NULL, PJ_EINVAL);
+
+ *m = tonegen->digit_map;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Set digit map to be used by the tone generator.
+ */
+PJ_DEF(pj_status_t) pjmedia_tonegen_set_digit_map(pjmedia_port *port,
+ pjmedia_tone_digit_map *m)
+{
+ struct tonegen *tonegen = (struct tonegen*) port;
+
+ PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);
+ PJ_ASSERT_RETURN(m != NULL, PJ_EINVAL);
+
+ pj_lock_acquire(tonegen->lock);
+
+ tonegen->digit_map = m;
+
+ pj_lock_release(tonegen->lock);
+
+ return PJ_SUCCESS;
+}
+
+
diff --git a/pjmedia/src/pjmedia/transport_adapter_sample.c b/pjmedia/src/pjmedia/transport_adapter_sample.c
new file mode 100644
index 0000000..d665ccb
--- /dev/null
+++ b/pjmedia/src/pjmedia/transport_adapter_sample.c
@@ -0,0 +1,440 @@
+/* $Id: transport_adapter_sample.c 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/transport_adapter_sample.h>
+#include <pjmedia/endpoint.h>
+#include <pj/assert.h>
+#include <pj/pool.h>
+
+
+/* Transport functions prototypes */
+static pj_status_t transport_get_info (pjmedia_transport *tp,
+ pjmedia_transport_info *info);
+static pj_status_t transport_attach (pjmedia_transport *tp,
+ void *user_data,
+ const pj_sockaddr_t *rem_addr,
+ const pj_sockaddr_t *rem_rtcp,
+ unsigned addr_len,
+ void (*rtp_cb)(void*,
+ void*,
+ pj_ssize_t),
+ void (*rtcp_cb)(void*,
+ void*,
+ pj_ssize_t));
+static void transport_detach (pjmedia_transport *tp,
+ void *strm);
+static pj_status_t transport_send_rtp( pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size);
+static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size);
+static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
+ const pj_sockaddr_t *addr,
+ unsigned addr_len,
+ const void *pkt,
+ pj_size_t size);
+static pj_status_t transport_media_create(pjmedia_transport *tp,
+ pj_pool_t *sdp_pool,
+ unsigned options,
+ const pjmedia_sdp_session *rem_sdp,
+ unsigned media_index);
+static pj_status_t transport_encode_sdp(pjmedia_transport *tp,
+ pj_pool_t *sdp_pool,
+ pjmedia_sdp_session *local_sdp,
+ const pjmedia_sdp_session *rem_sdp,
+ unsigned media_index);
+static pj_status_t transport_media_start (pjmedia_transport *tp,
+ pj_pool_t *pool,
+ const pjmedia_sdp_session *local_sdp,
+ const pjmedia_sdp_session *rem_sdp,
+ unsigned media_index);
+static pj_status_t transport_media_stop(pjmedia_transport *tp);
+static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
+ pjmedia_dir dir,
+ unsigned pct_lost);
+static pj_status_t transport_destroy (pjmedia_transport *tp);
+
+
+/* The transport operations */
+static struct pjmedia_transport_op tp_adapter_op =
+{
+ &transport_get_info,
+ &transport_attach,
+ &transport_detach,
+ &transport_send_rtp,
+ &transport_send_rtcp,
+ &transport_send_rtcp2,
+ &transport_media_create,
+ &transport_encode_sdp,
+ &transport_media_start,
+ &transport_media_stop,
+ &transport_simulate_lost,
+ &transport_destroy
+};
+
+
+/* The transport adapter instance */
+struct tp_adapter
+{
+ pjmedia_transport base;
+ pj_bool_t del_base;
+
+ pj_pool_t *pool;
+
+ /* Stream information. */
+ void *stream_user_data;
+ void (*stream_rtp_cb)(void *user_data,
+ void *pkt,
+ pj_ssize_t);
+ void (*stream_rtcp_cb)(void *user_data,
+ void *pkt,
+ pj_ssize_t);
+
+
+ /* Add your own member here.. */
+ pjmedia_transport *slave_tp;
+};
+
+
+/*
+ * Create the adapter.
+ */
+PJ_DEF(pj_status_t) pjmedia_tp_adapter_create( pjmedia_endpt *endpt,
+ const char *name,
+ pjmedia_transport *transport,
+ pj_bool_t del_base,
+ pjmedia_transport **p_tp)
+{
+ pj_pool_t *pool;
+ struct tp_adapter *adapter;
+
+ if (name == NULL)
+ name = "tpad%p";
+
+ /* Create the pool and initialize the adapter structure */
+ pool = pjmedia_endpt_create_pool(endpt, name, 512, 512);
+ adapter = PJ_POOL_ZALLOC_T(pool, struct tp_adapter);
+ adapter->pool = pool;
+ pj_ansi_strncpy(adapter->base.name, pool->obj_name,
+ sizeof(adapter->base.name));
+ adapter->base.type = (pjmedia_transport_type)
+ (PJMEDIA_TRANSPORT_TYPE_USER + 1);
+ adapter->base.op = &tp_adapter_op;
+
+ /* Save the transport as the slave transport */
+ adapter->slave_tp = transport;
+ adapter->del_base = del_base;
+
+ /* Done */
+ *p_tp = &adapter->base;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * get_info() is called to get the transport addresses to be put
+ * in SDP c= line and a=rtcp line.
+ */
+static pj_status_t transport_get_info(pjmedia_transport *tp,
+ pjmedia_transport_info *info)
+{
+ struct tp_adapter *adapter = (struct tp_adapter*)tp;
+
+ /* Since we don't have our own connection here, we just pass
+ * this function to the slave transport.
+ */
+ return pjmedia_transport_get_info(adapter->slave_tp, info);
+}
+
+
+/* This is our RTP callback, that is called by the slave transport when it
+ * receives RTP packet.
+ */
+static void transport_rtp_cb(void *user_data, void *pkt, pj_ssize_t size)
+{
+ struct tp_adapter *adapter = (struct tp_adapter*)user_data;
+
+ pj_assert(adapter->stream_rtp_cb != NULL);
+
+ /* Call stream's callback */
+ adapter->stream_rtp_cb(adapter->stream_user_data, pkt, size);
+}
+
+/* This is our RTCP callback, that is called by the slave transport when it
+ * receives RTCP packet.
+ */
+static void transport_rtcp_cb(void *user_data, void *pkt, pj_ssize_t size)
+{
+ struct tp_adapter *adapter = (struct tp_adapter*)user_data;
+
+ pj_assert(adapter->stream_rtcp_cb != NULL);
+
+ /* Call stream's callback */
+ adapter->stream_rtcp_cb(adapter->stream_user_data, pkt, size);
+}
+
+
+/*
+ * attach() is called by stream to register callbacks that we should
+ * call on receipt of RTP and RTCP packets.
+ */
+static pj_status_t transport_attach(pjmedia_transport *tp,
+ void *user_data,
+ const pj_sockaddr_t *rem_addr,
+ const pj_sockaddr_t *rem_rtcp,
+ unsigned addr_len,
+ void (*rtp_cb)(void*,
+ void*,
+ pj_ssize_t),
+ void (*rtcp_cb)(void*,
+ void*,
+ pj_ssize_t))
+{
+ struct tp_adapter *adapter = (struct tp_adapter*)tp;
+ pj_status_t status;
+
+ /* In this example, we will save the stream information and callbacks
+ * to our structure, and we will register different RTP/RTCP callbacks
+ * instead.
+ */
+ pj_assert(adapter->stream_user_data == NULL);
+ adapter->stream_user_data = user_data;
+ adapter->stream_rtp_cb = rtp_cb;
+ adapter->stream_rtcp_cb = rtcp_cb;
+
+ status = pjmedia_transport_attach(adapter->slave_tp, adapter, rem_addr,
+ rem_rtcp, addr_len, &transport_rtp_cb,
+ &transport_rtcp_cb);
+ if (status != PJ_SUCCESS) {
+ adapter->stream_user_data = NULL;
+ adapter->stream_rtp_cb = NULL;
+ adapter->stream_rtcp_cb = NULL;
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * detach() is called when the media is terminated, and the stream is
+ * to be disconnected from us.
+ */
+static void transport_detach(pjmedia_transport *tp, void *strm)
+{
+ struct tp_adapter *adapter = (struct tp_adapter*)tp;
+
+ PJ_UNUSED_ARG(strm);
+
+ if (adapter->stream_user_data != NULL) {
+ pjmedia_transport_detach(adapter->slave_tp, adapter);
+ adapter->stream_user_data = NULL;
+ adapter->stream_rtp_cb = NULL;
+ adapter->stream_rtcp_cb = NULL;
+ }
+}
+
+
+/*
+ * send_rtp() is called to send RTP packet. The "pkt" and "size" argument
+ * contain both the RTP header and the payload.
+ */
+static pj_status_t transport_send_rtp( pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size)
+{
+ struct tp_adapter *adapter = (struct tp_adapter*)tp;
+
+ /* You may do some processing to the RTP packet here if you want. */
+
+ /* Send the packet using the slave transport */
+ return pjmedia_transport_send_rtp(adapter->slave_tp, pkt, size);
+}
+
+
+/*
+ * send_rtcp() is called to send RTCP packet. The "pkt" and "size" argument
+ * contain the RTCP packet.
+ */
+static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size)
+{
+ struct tp_adapter *adapter = (struct tp_adapter*)tp;
+
+ /* You may do some processing to the RTCP packet here if you want. */
+
+ /* Send the packet using the slave transport */
+ return pjmedia_transport_send_rtcp(adapter->slave_tp, pkt, size);
+}
+
+
+/*
+ * This is another variant of send_rtcp(), with the alternate destination
+ * address in the argument.
+ */
+static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
+ const pj_sockaddr_t *addr,
+ unsigned addr_len,
+ const void *pkt,
+ pj_size_t size)
+{
+ struct tp_adapter *adapter = (struct tp_adapter*)tp;
+ return pjmedia_transport_send_rtcp2(adapter->slave_tp, addr, addr_len,
+ pkt, size);
+}
+
+/*
+ * The media_create() is called when the transport is about to be used for
+ * a new call.
+ */
+static pj_status_t transport_media_create(pjmedia_transport *tp,
+ pj_pool_t *sdp_pool,
+ unsigned options,
+ const pjmedia_sdp_session *rem_sdp,
+ unsigned media_index)
+{
+ struct tp_adapter *adapter = (struct tp_adapter*)tp;
+
+ /* if "rem_sdp" is not NULL, it means we are UAS. You may do some
+ * inspections on the incoming SDP to verify that the SDP is acceptable
+ * for us. If the SDP is not acceptable, we can reject the SDP by
+ * returning non-PJ_SUCCESS.
+ */
+ if (rem_sdp) {
+ /* Do your stuff.. */
+ }
+
+ /* Once we're done with our initialization, pass the call to the
+ * slave transports to let it do it's own initialization too.
+ */
+ return pjmedia_transport_media_create(adapter->slave_tp, sdp_pool, options,
+ rem_sdp, media_index);
+}
+
+/*
+ * The encode_sdp() is called when we're about to send SDP to remote party,
+ * either as SDP offer or as SDP answer.
+ */
+static pj_status_t transport_encode_sdp(pjmedia_transport *tp,
+ pj_pool_t *sdp_pool,
+ pjmedia_sdp_session *local_sdp,
+ const pjmedia_sdp_session *rem_sdp,
+ unsigned media_index)
+{
+ struct tp_adapter *adapter = (struct tp_adapter*)tp;
+
+ /* If "rem_sdp" is not NULL, it means we're encoding SDP answer. You may
+ * do some more checking on the SDP's once again to make sure that
+ * everything is okay before we send SDP.
+ */
+ if (rem_sdp) {
+ /* Do checking stuffs here.. */
+ }
+
+ /* You may do anything to the local_sdp, e.g. adding new attributes, or
+ * even modifying the SDP if you want.
+ */
+ if (1) {
+ /* Say we add a proprietary attribute here.. */
+ pjmedia_sdp_attr *my_attr;
+
+ my_attr = PJ_POOL_ALLOC_T(sdp_pool, pjmedia_sdp_attr);
+ pj_strdup2(sdp_pool, &my_attr->name, "X-adapter");
+ pj_strdup2(sdp_pool, &my_attr->value, "some value");
+
+ pjmedia_sdp_attr_add(&local_sdp->media[media_index]->attr_count,
+ local_sdp->media[media_index]->attr,
+ my_attr);
+ }
+
+ /* And then pass the call to slave transport to let it encode its
+ * information in the SDP. You may choose to call encode_sdp() to slave
+ * first before adding your custom attributes if you want.
+ */
+ return pjmedia_transport_encode_sdp(adapter->slave_tp, sdp_pool, local_sdp,
+ rem_sdp, media_index);
+}
+
+/*
+ * The media_start() is called once both local and remote SDP have been
+ * negotiated successfully, and the media is ready to start. Here we can start
+ * committing our processing.
+ */
+static pj_status_t transport_media_start(pjmedia_transport *tp,
+ pj_pool_t *pool,
+ const pjmedia_sdp_session *local_sdp,
+ const pjmedia_sdp_session *rem_sdp,
+ unsigned media_index)
+{
+ struct tp_adapter *adapter = (struct tp_adapter*)tp;
+
+ /* Do something.. */
+
+ /* And pass the call to the slave transport */
+ return pjmedia_transport_media_start(adapter->slave_tp, pool, local_sdp,
+ rem_sdp, media_index);
+}
+
+/*
+ * The media_stop() is called when media has been stopped.
+ */
+static pj_status_t transport_media_stop(pjmedia_transport *tp)
+{
+ struct tp_adapter *adapter = (struct tp_adapter*)tp;
+
+ /* Do something.. */
+
+ /* And pass the call to the slave transport */
+ return pjmedia_transport_media_stop(adapter->slave_tp);
+}
+
+/*
+ * simulate_lost() is called to simulate packet lost
+ */
+static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
+ pjmedia_dir dir,
+ unsigned pct_lost)
+{
+ struct tp_adapter *adapter = (struct tp_adapter*)tp;
+ return pjmedia_transport_simulate_lost(adapter->slave_tp, dir, pct_lost);
+}
+
+/*
+ * destroy() is called when the transport is no longer needed.
+ */
+static pj_status_t transport_destroy (pjmedia_transport *tp)
+{
+ struct tp_adapter *adapter = (struct tp_adapter*)tp;
+
+ /* Close the slave transport */
+ if (adapter->del_base) {
+ pjmedia_transport_close(adapter->slave_tp);
+ }
+
+ /* Self destruct.. */
+ pj_pool_release(adapter->pool);
+
+ return PJ_SUCCESS;
+}
+
+
+
+
+
diff --git a/pjmedia/src/pjmedia/transport_ice.c b/pjmedia/src/pjmedia/transport_ice.c
new file mode 100644
index 0000000..40bb71b
--- /dev/null
+++ b/pjmedia/src/pjmedia/transport_ice.c
@@ -0,0 +1,1853 @@
+/* $Id: transport_ice.c 3999 2012-03-30 07:10:13Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/transport_ice.h>
+#include <pjnath/errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/rand.h>
+
+#define THIS_FILE "transport_ice.c"
+#if 0
+# define TRACE__(expr) PJ_LOG(5,expr)
+#else
+# define TRACE__(expr)
+#endif
+
+enum oa_role
+{
+ ROLE_NONE,
+ ROLE_OFFERER,
+ ROLE_ANSWERER
+};
+
+struct sdp_state
+{
+ unsigned match_comp_cnt; /* Matching number of components */
+ pj_bool_t ice_mismatch; /* Address doesn't match candidates */
+ pj_bool_t ice_restart; /* Offer to restart ICE */
+ pj_ice_sess_role local_role; /* Our role */
+};
+
+struct transport_ice
+{
+ pjmedia_transport base;
+ pj_pool_t *pool;
+ int af;
+ unsigned options; /**< Transport options. */
+
+ unsigned comp_cnt;
+ pj_ice_strans *ice_st;
+
+ pjmedia_ice_cb cb;
+ unsigned media_option;
+
+ pj_bool_t initial_sdp;
+ enum oa_role oa_role; /**< Last role in SDP offer/answer */
+ struct sdp_state rem_offer_state;/**< Describes the remote offer */
+
+ void *stream;
+ pj_sockaddr remote_rtp;
+ pj_sockaddr remote_rtcp;
+ unsigned addr_len; /**< Length of addresses. */
+
+ pj_bool_t use_ice;
+ pj_sockaddr rtp_src_addr; /**< Actual source RTP address. */
+ pj_sockaddr rtcp_src_addr; /**< Actual source RTCP address. */
+ unsigned rtp_src_cnt; /**< How many pkt from this addr. */
+ unsigned rtcp_src_cnt; /**< How many pkt from this addr. */
+
+ unsigned tx_drop_pct; /**< Percent of tx pkts to drop. */
+ unsigned rx_drop_pct; /**< Percent of rx pkts to drop. */
+
+ void (*rtp_cb)(void*,
+ void*,
+ pj_ssize_t);
+ void (*rtcp_cb)(void*,
+ void*,
+ pj_ssize_t);
+};
+
+
+/*
+ * These are media transport operations.
+ */
+static pj_status_t transport_get_info (pjmedia_transport *tp,
+ pjmedia_transport_info *info);
+static pj_status_t transport_attach (pjmedia_transport *tp,
+ void *user_data,
+ const pj_sockaddr_t *rem_addr,
+ const pj_sockaddr_t *rem_rtcp,
+ unsigned addr_len,
+ void (*rtp_cb)(void*,
+ void*,
+ pj_ssize_t),
+ void (*rtcp_cb)(void*,
+ void*,
+ pj_ssize_t));
+static void transport_detach (pjmedia_transport *tp,
+ void *strm);
+static pj_status_t transport_send_rtp( pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size);
+static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size);
+static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
+ const pj_sockaddr_t *addr,
+ unsigned addr_len,
+ const void *pkt,
+ pj_size_t size);
+static pj_status_t transport_media_create(pjmedia_transport *tp,
+ pj_pool_t *pool,
+ unsigned options,
+ const pjmedia_sdp_session *rem_sdp,
+ unsigned media_index);
+static pj_status_t transport_encode_sdp(pjmedia_transport *tp,
+ pj_pool_t *tmp_pool,
+ pjmedia_sdp_session *sdp_local,
+ const pjmedia_sdp_session *rem_sdp,
+ unsigned media_index);
+static pj_status_t transport_media_start(pjmedia_transport *tp,
+ pj_pool_t *pool,
+ const pjmedia_sdp_session *sdp_local,
+ const pjmedia_sdp_session *rem_sdp,
+ unsigned media_index);
+static pj_status_t transport_media_stop(pjmedia_transport *tp);
+static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
+ pjmedia_dir dir,
+ unsigned pct_lost);
+static pj_status_t transport_destroy (pjmedia_transport *tp);
+
+/*
+ * And these are ICE callbacks.
+ */
+static void ice_on_rx_data(pj_ice_strans *ice_st,
+ unsigned comp_id,
+ void *pkt, pj_size_t size,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len);
+static void ice_on_ice_complete(pj_ice_strans *ice_st,
+ pj_ice_strans_op op,
+ pj_status_t status);
+
+
+static pjmedia_transport_op transport_ice_op =
+{
+ &transport_get_info,
+ &transport_attach,
+ &transport_detach,
+ &transport_send_rtp,
+ &transport_send_rtcp,
+ &transport_send_rtcp2,
+ &transport_media_create,
+ &transport_encode_sdp,
+ &transport_media_start,
+ &transport_media_stop,
+ &transport_simulate_lost,
+ &transport_destroy
+};
+
+static const pj_str_t STR_RTP_AVP = { "RTP/AVP", 7 };
+static const pj_str_t STR_CANDIDATE = { "candidate", 9};
+static const pj_str_t STR_REM_CAND = { "remote-candidates", 17 };
+static const pj_str_t STR_ICE_LITE = { "ice-lite", 8};
+static const pj_str_t STR_ICE_MISMATCH = { "ice-mismatch", 12};
+static const pj_str_t STR_ICE_UFRAG = { "ice-ufrag", 9 };
+static const pj_str_t STR_ICE_PWD = { "ice-pwd", 7 };
+static const pj_str_t STR_IP4 = { "IP4", 3 };
+static const pj_str_t STR_IP6 = { "IP6", 3 };
+static const pj_str_t STR_RTCP = { "rtcp", 4 };
+static const pj_str_t STR_BANDW_RR = { "RR", 2 };
+static const pj_str_t STR_BANDW_RS = { "RS", 2 };
+
+enum {
+ COMP_RTP = 1,
+ COMP_RTCP = 2
+};
+
+/*
+ * Create ICE media transport.
+ */
+PJ_DEF(pj_status_t) pjmedia_ice_create(pjmedia_endpt *endpt,
+ const char *name,
+ unsigned comp_cnt,
+ const pj_ice_strans_cfg *cfg,
+ const pjmedia_ice_cb *cb,
+ pjmedia_transport **p_tp)
+{
+ return pjmedia_ice_create2(endpt, name, comp_cnt, cfg, cb, 0, p_tp);
+}
+
+/*
+ * Create ICE media transport.
+ */
+PJ_DEF(pj_status_t) pjmedia_ice_create2(pjmedia_endpt *endpt,
+ const char *name,
+ unsigned comp_cnt,
+ const pj_ice_strans_cfg *cfg,
+ const pjmedia_ice_cb *cb,
+ unsigned options,
+ pjmedia_transport **p_tp)
+{
+ return pjmedia_ice_create3(endpt, name, comp_cnt, cfg, cb,
+ options, NULL, p_tp);
+}
+
+/*
+ * Create ICE media transport.
+ */
+PJ_DEF(pj_status_t) pjmedia_ice_create3(pjmedia_endpt *endpt,
+ const char *name,
+ unsigned comp_cnt,
+ const pj_ice_strans_cfg *cfg,
+ const pjmedia_ice_cb *cb,
+ unsigned options,
+ void *user_data,
+ pjmedia_transport **p_tp)
+{
+ pj_pool_t *pool;
+ pj_ice_strans_cb ice_st_cb;
+ struct transport_ice *tp_ice;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(endpt && comp_cnt && cfg && p_tp, PJ_EINVAL);
+
+ /* Create transport instance */
+ pool = pjmedia_endpt_create_pool(endpt, name, 512, 512);
+ tp_ice = PJ_POOL_ZALLOC_T(pool, struct transport_ice);
+ tp_ice->pool = pool;
+ tp_ice->af = cfg->af;
+ tp_ice->options = options;
+ tp_ice->comp_cnt = comp_cnt;
+ pj_ansi_strcpy(tp_ice->base.name, pool->obj_name);
+ tp_ice->base.op = &transport_ice_op;
+ tp_ice->base.type = PJMEDIA_TRANSPORT_TYPE_ICE;
+ tp_ice->base.user_data = user_data;
+ tp_ice->initial_sdp = PJ_TRUE;
+ tp_ice->oa_role = ROLE_NONE;
+ tp_ice->use_ice = PJ_FALSE;
+
+ if (cb)
+ pj_memcpy(&tp_ice->cb, cb, sizeof(pjmedia_ice_cb));
+
+ /* Assign return value first because ICE might call callback
+ * in create()
+ */
+ *p_tp = &tp_ice->base;
+
+ /* Configure ICE callbacks */
+ pj_bzero(&ice_st_cb, sizeof(ice_st_cb));
+ ice_st_cb.on_ice_complete = &ice_on_ice_complete;
+ ice_st_cb.on_rx_data = &ice_on_rx_data;
+
+ /* Create ICE */
+ status = pj_ice_strans_create(name, cfg, comp_cnt, tp_ice,
+ &ice_st_cb, &tp_ice->ice_st);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(pool);
+ *p_tp = NULL;
+ return status;
+ }
+
+ /* Done */
+ return PJ_SUCCESS;
+}
+
+/* Disable ICE when SDP from remote doesn't contain a=candidate line */
+static void set_no_ice(struct transport_ice *tp_ice, const char *reason,
+ pj_status_t err)
+{
+ if (err != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(err, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(tp_ice->base.name,
+ "Stopping ICE, reason=%s:%s", reason, errmsg));
+ } else {
+ PJ_LOG(4,(tp_ice->base.name,
+ "Stopping ICE, reason=%s", reason));
+ }
+
+ pj_ice_strans_stop_ice(tp_ice->ice_st);
+
+ tp_ice->use_ice = PJ_FALSE;
+}
+
+
+/* Create SDP candidate attribute */
+static int print_sdp_cand_attr(char *buffer, int max_len,
+ const pj_ice_sess_cand *cand)
+{
+ char ipaddr[PJ_INET6_ADDRSTRLEN+2];
+ int len, len2;
+
+ len = pj_ansi_snprintf( buffer, max_len,
+ "%.*s %u UDP %u %s %u typ ",
+ (int)cand->foundation.slen,
+ cand->foundation.ptr,
+ (unsigned)cand->comp_id,
+ cand->prio,
+ pj_sockaddr_print(&cand->addr, ipaddr,
+ sizeof(ipaddr), 0),
+ (unsigned)pj_sockaddr_get_port(&cand->addr));
+ if (len < 1 || len >= max_len)
+ return -1;
+
+ switch (cand->type) {
+ case PJ_ICE_CAND_TYPE_HOST:
+ len2 = pj_ansi_snprintf(buffer+len, max_len-len, "host");
+ break;
+ case PJ_ICE_CAND_TYPE_SRFLX:
+ case PJ_ICE_CAND_TYPE_RELAYED:
+ case PJ_ICE_CAND_TYPE_PRFLX:
+ len2 = pj_ansi_snprintf(buffer+len, max_len-len,
+ "%s raddr %s rport %d",
+ pj_ice_get_cand_type_name(cand->type),
+ pj_sockaddr_print(&cand->rel_addr, ipaddr,
+ sizeof(ipaddr), 0),
+ (int)pj_sockaddr_get_port(&cand->rel_addr));
+ break;
+ default:
+ pj_assert(!"Invalid candidate type");
+ len2 = -1;
+ break;
+ }
+ if (len2 < 1 || len2 >= max_len)
+ return -1;
+
+ return len+len2;
+}
+
+
+/* Get ice-ufrag and ice-pwd attribute */
+static void get_ice_attr(const pjmedia_sdp_session *rem_sdp,
+ const pjmedia_sdp_media *rem_m,
+ const pjmedia_sdp_attr **p_ice_ufrag,
+ const pjmedia_sdp_attr **p_ice_pwd)
+{
+ pjmedia_sdp_attr *attr;
+
+ /* Find ice-ufrag attribute in media descriptor */
+ attr = pjmedia_sdp_attr_find(rem_m->attr_count, rem_m->attr,
+ &STR_ICE_UFRAG, NULL);
+ if (attr == NULL) {
+ /* Find ice-ufrag attribute in session descriptor */
+ attr = pjmedia_sdp_attr_find(rem_sdp->attr_count, rem_sdp->attr,
+ &STR_ICE_UFRAG, NULL);
+ }
+ *p_ice_ufrag = attr;
+
+ /* Find ice-pwd attribute in media descriptor */
+ attr = pjmedia_sdp_attr_find(rem_m->attr_count, rem_m->attr,
+ &STR_ICE_PWD, NULL);
+ if (attr == NULL) {
+ /* Find ice-pwd attribute in session descriptor */
+ attr = pjmedia_sdp_attr_find(rem_sdp->attr_count, rem_sdp->attr,
+ &STR_ICE_PWD, NULL);
+ }
+ *p_ice_pwd = attr;
+}
+
+
+/* Encode and add "a=ice-mismatch" attribute in the SDP */
+static void encode_ice_mismatch(pj_pool_t *sdp_pool,
+ pjmedia_sdp_session *sdp_local,
+ unsigned media_index)
+{
+ pjmedia_sdp_attr *attr;
+ pjmedia_sdp_media *m = sdp_local->media[media_index];
+
+ attr = PJ_POOL_ALLOC_T(sdp_pool, pjmedia_sdp_attr);
+ attr->name = STR_ICE_MISMATCH;
+ attr->value.slen = 0;
+ pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
+}
+
+
+/* Encode ICE information in SDP */
+static pj_status_t encode_session_in_sdp(struct transport_ice *tp_ice,
+ pj_pool_t *sdp_pool,
+ pjmedia_sdp_session *sdp_local,
+ unsigned media_index,
+ unsigned comp_cnt,
+ pj_bool_t restart_session)
+{
+ enum {
+ ATTR_BUF_LEN = 160, /* Max len of a=candidate attr */
+ RATTR_BUF_LEN= 160 /* Max len of a=remote-candidates attr */
+ };
+ pjmedia_sdp_media *m = sdp_local->media[media_index];
+ pj_str_t local_ufrag, local_pwd;
+ pjmedia_sdp_attr *attr;
+ pj_status_t status;
+
+ /* Must have a session */
+ PJ_ASSERT_RETURN(pj_ice_strans_has_sess(tp_ice->ice_st), PJ_EBUG);
+
+ /* Get ufrag and pwd from current session */
+ pj_ice_strans_get_ufrag_pwd(tp_ice->ice_st, &local_ufrag, &local_pwd,
+ NULL, NULL);
+
+ /* The listing of candidates depends on whether ICE has completed
+ * or not. When ICE has completed:
+ *
+ * 9.1.2.2: Existing Media Streams with ICE Completed
+ * The agent MUST include a candidate attributes for candidates
+ * matching the default destination for each component of the
+ * media stream, and MUST NOT include any other candidates.
+ *
+ * When ICE has not completed, we shall include all candidates.
+ *
+ * Except when we have detected that remote is offering to restart
+ * the session, in this case we will answer with full ICE SDP and
+ * new ufrag/pwd pair.
+ */
+ if (!restart_session && pj_ice_strans_sess_is_complete(tp_ice->ice_st) &&
+ pj_ice_strans_get_state(tp_ice->ice_st) != PJ_ICE_STRANS_STATE_FAILED)
+ {
+ const pj_ice_sess_check *check;
+ char *attr_buf;
+ pjmedia_sdp_conn *conn;
+ pjmedia_sdp_attr *a_rtcp;
+ pj_str_t rem_cand;
+ unsigned comp;
+
+ /* Encode ice-ufrag attribute */
+ attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_UFRAG.ptr,
+ &local_ufrag);
+ pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
+
+ /* Encode ice-pwd attribute */
+ attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_PWD.ptr,
+ &local_pwd);
+ pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
+
+ /* Prepare buffer */
+ attr_buf = (char*) pj_pool_alloc(sdp_pool, ATTR_BUF_LEN);
+ rem_cand.ptr = (char*) pj_pool_alloc(sdp_pool, RATTR_BUF_LEN);
+ rem_cand.slen = 0;
+
+ /* 9.1.2.2: Existing Media Streams with ICE Completed
+ * The default destination for media (i.e., the values of
+ * the IP addresses and ports in the m and c line used for
+ * that media stream) MUST be the local candidate from the
+ * highest priority nominated pair in the valid list for each
+ * component.
+ */
+ check = pj_ice_strans_get_valid_pair(tp_ice->ice_st, 1);
+ if (check == NULL) {
+ pj_assert(!"Shouldn't happen");
+ return PJ_EBUG;
+ }
+
+ /* Override connection line address and media port number */
+ conn = m->conn;
+ if (conn == NULL)
+ conn = sdp_local->conn;
+
+ conn->addr.ptr = (char*) pj_pool_alloc(sdp_pool,
+ PJ_INET6_ADDRSTRLEN);
+ pj_sockaddr_print(&check->lcand->addr, conn->addr.ptr,
+ PJ_INET6_ADDRSTRLEN, 0);
+ conn->addr.slen = pj_ansi_strlen(conn->addr.ptr);
+ m->desc.port = pj_sockaddr_get_port(&check->lcand->addr);
+
+ /* Override address RTCP attribute if it's present */
+ if (comp_cnt == 2 &&
+ (check = pj_ice_strans_get_valid_pair(tp_ice->ice_st,
+ COMP_RTCP)) != NULL &&
+ (a_rtcp = pjmedia_sdp_attr_find(m->attr_count, m->attr,
+ &STR_RTCP, 0)) != NULL)
+ {
+ pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a_rtcp);
+
+ a_rtcp = pjmedia_sdp_attr_create_rtcp(sdp_pool,
+ &check->lcand->addr);
+ if (a_rtcp)
+ pjmedia_sdp_attr_add(&m->attr_count, m->attr, a_rtcp);
+ }
+
+ /* Encode only candidates matching the default destination
+ * for each component
+ */
+ for (comp=0; comp < comp_cnt; ++comp) {
+ int len;
+ pj_str_t value;
+
+ /* Get valid pair for this component */
+ check = pj_ice_strans_get_valid_pair(tp_ice->ice_st, comp+1);
+ if (check == NULL)
+ continue;
+
+ /* Print and add local candidate in the pair */
+ value.ptr = attr_buf;
+ value.slen = print_sdp_cand_attr(attr_buf, ATTR_BUF_LEN,
+ check->lcand);
+ if (value.slen < 0) {
+ pj_assert(!"Not enough attr_buf to print candidate");
+ return PJ_EBUG;
+ }
+
+ attr = pjmedia_sdp_attr_create(sdp_pool, STR_CANDIDATE.ptr,
+ &value);
+ pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
+
+ /* Append to a=remote-candidates attribute */
+ if (pj_ice_strans_get_role(tp_ice->ice_st) ==
+ PJ_ICE_SESS_ROLE_CONTROLLING)
+ {
+ char rem_addr[PJ_INET6_ADDRSTRLEN];
+
+ pj_sockaddr_print(&check->rcand->addr, rem_addr,
+ sizeof(rem_addr), 0);
+ len = pj_ansi_snprintf(
+ rem_cand.ptr + rem_cand.slen,
+ RATTR_BUF_LEN - rem_cand.slen,
+ "%s%u %s %u",
+ (rem_cand.slen==0? "" : " "),
+ comp+1, rem_addr,
+ pj_sockaddr_get_port(&check->rcand->addr)
+ );
+ if (len < 1 || len >= RATTR_BUF_LEN) {
+ pj_assert(!"Not enough buffer to print "
+ "remote-candidates");
+ return PJ_EBUG;
+ }
+
+ rem_cand.slen += len;
+ }
+ }
+
+ /* 9.1.2.2: Existing Media Streams with ICE Completed
+ * In addition, if the agent is controlling, it MUST include
+ * the a=remote-candidates attribute for each media stream
+ * whose check list is in the Completed state. The attribute
+ * contains the remote candidates from the highest priority
+ * nominated pair in the valid list for each component of that
+ * media stream.
+ */
+ if (pj_ice_strans_get_role(tp_ice->ice_st) ==
+ PJ_ICE_SESS_ROLE_CONTROLLING)
+ {
+ attr = pjmedia_sdp_attr_create(sdp_pool, STR_REM_CAND.ptr,
+ &rem_cand);
+ pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
+ }
+
+ } else if (pj_ice_strans_has_sess(tp_ice->ice_st) &&
+ pj_ice_strans_get_state(tp_ice->ice_st) !=
+ PJ_ICE_STRANS_STATE_FAILED)
+ {
+ /* Encode all candidates to SDP media */
+ char *attr_buf;
+ unsigned comp;
+
+ /* If ICE is not restarted, encode current ICE ufrag/pwd.
+ * Otherwise generate new one.
+ */
+ if (!restart_session) {
+ attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_UFRAG.ptr,
+ &local_ufrag);
+ pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
+
+ attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_PWD.ptr,
+ &local_pwd);
+ pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
+
+ } else {
+ pj_str_t str;
+
+ str.slen = PJ_ICE_UFRAG_LEN;
+ str.ptr = (char*) pj_pool_alloc(sdp_pool, str.slen);
+ pj_create_random_string(str.ptr, str.slen);
+ attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_UFRAG.ptr, &str);
+ pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
+
+ str.ptr = (char*) pj_pool_alloc(sdp_pool, str.slen);
+ pj_create_random_string(str.ptr, str.slen);
+ attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_PWD.ptr, &str);
+ pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
+ }
+
+ /* Create buffer to encode candidates as SDP attribute */
+ attr_buf = (char*) pj_pool_alloc(sdp_pool, ATTR_BUF_LEN);
+
+ for (comp=0; comp < comp_cnt; ++comp) {
+ unsigned cand_cnt;
+ pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND];
+ unsigned i;
+
+ cand_cnt = PJ_ARRAY_SIZE(cand);
+ status = pj_ice_strans_enum_cands(tp_ice->ice_st, comp+1,
+ &cand_cnt, cand);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ for (i=0; i<cand_cnt; ++i) {
+ pj_str_t value;
+
+ value.slen = print_sdp_cand_attr(attr_buf, ATTR_BUF_LEN,
+ &cand[i]);
+ if (value.slen < 0) {
+ pj_assert(!"Not enough attr_buf to print candidate");
+ return PJ_EBUG;
+ }
+
+ value.ptr = attr_buf;
+ attr = pjmedia_sdp_attr_create(sdp_pool,
+ STR_CANDIDATE.ptr,
+ &value);
+ pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
+ }
+ }
+ } else {
+ /* ICE has failed, application should have terminated this call */
+ }
+
+ /* Removing a=rtcp line when there is only one component. */
+ if (comp_cnt == 1) {
+ attr = pjmedia_sdp_attr_find(m->attr_count, m->attr, &STR_RTCP, NULL);
+ if (attr)
+ pjmedia_sdp_attr_remove(&m->attr_count, m->attr, attr);
+ /* If RTCP is not in use, we MUST send b=RS:0 and b=RR:0. */
+ pj_assert(m->bandw_count + 2 <= PJ_ARRAY_SIZE(m->bandw));
+ if (m->bandw_count + 2 <= PJ_ARRAY_SIZE(m->bandw)) {
+ m->bandw[m->bandw_count] = PJ_POOL_ZALLOC_T(sdp_pool,
+ pjmedia_sdp_bandw);
+ pj_memcpy(&m->bandw[m->bandw_count]->modifier, &STR_BANDW_RS,
+ sizeof(pj_str_t));
+ m->bandw_count++;
+ m->bandw[m->bandw_count] = PJ_POOL_ZALLOC_T(sdp_pool,
+ pjmedia_sdp_bandw);
+ pj_memcpy(&m->bandw[m->bandw_count]->modifier, &STR_BANDW_RR,
+ sizeof(pj_str_t));
+ m->bandw_count++;
+ }
+ }
+
+
+ return PJ_SUCCESS;
+}
+
+
+/* Parse a=candidate line */
+static pj_status_t parse_cand(const char *obj_name,
+ pj_pool_t *pool,
+ const pj_str_t *orig_input,
+ pj_ice_sess_cand *cand)
+{
+ pj_str_t input;
+ char *token, *host;
+ int af;
+ pj_str_t s;
+ pj_status_t status = PJNATH_EICEINCANDSDP;
+
+ pj_bzero(cand, sizeof(*cand));
+ pj_strdup_with_null(pool, &input, orig_input);
+
+ PJ_UNUSED_ARG(obj_name);
+
+ /* Foundation */
+ token = strtok(input.ptr, " ");
+ if (!token) {
+ TRACE__((obj_name, "Expecting ICE foundation in candidate"));
+ goto on_return;
+ }
+ pj_strdup2(pool, &cand->foundation, token);
+
+ /* Component ID */
+ token = strtok(NULL, " ");
+ if (!token) {
+ TRACE__((obj_name, "Expecting ICE component ID in candidate"));
+ goto on_return;
+ }
+ cand->comp_id = (pj_uint8_t) atoi(token);
+
+ /* Transport */
+ token = strtok(NULL, " ");
+ if (!token) {
+ TRACE__((obj_name, "Expecting ICE transport in candidate"));
+ goto on_return;
+ }
+ if (pj_ansi_stricmp(token, "UDP") != 0) {
+ TRACE__((obj_name,
+ "Expecting ICE UDP transport only in candidate"));
+ goto on_return;
+ }
+
+ /* Priority */
+ token = strtok(NULL, " ");
+ if (!token) {
+ TRACE__((obj_name, "Expecting ICE priority in candidate"));
+ goto on_return;
+ }
+ cand->prio = atoi(token);
+
+ /* Host */
+ host = strtok(NULL, " ");
+ if (!host) {
+ TRACE__((obj_name, "Expecting ICE host in candidate"));
+ goto on_return;
+ }
+ /* Detect address family */
+ if (pj_ansi_strchr(host, ':'))
+ af = pj_AF_INET6();
+ else
+ af = pj_AF_INET();
+ /* Assign address */
+ if (pj_sockaddr_init(af, &cand->addr, pj_cstr(&s, host), 0)) {
+ TRACE__((obj_name, "Invalid ICE candidate address"));
+ goto on_return;
+ }
+
+ /* Port */
+ token = strtok(NULL, " ");
+ if (!token) {
+ TRACE__((obj_name, "Expecting ICE port number in candidate"));
+ goto on_return;
+ }
+ pj_sockaddr_set_port(&cand->addr, (pj_uint16_t)atoi(token));
+
+ /* typ */
+ token = strtok(NULL, " ");
+ if (!token) {
+ TRACE__((obj_name, "Expecting ICE \"typ\" in candidate"));
+ goto on_return;
+ }
+ if (pj_ansi_stricmp(token, "typ") != 0) {
+ TRACE__((obj_name, "Expecting ICE \"typ\" in candidate"));
+ goto on_return;
+ }
+
+ /* candidate type */
+ token = strtok(NULL, " ");
+ if (!token) {
+ TRACE__((obj_name, "Expecting ICE candidate type in candidate"));
+ goto on_return;
+ }
+
+ if (pj_ansi_stricmp(token, "host") == 0) {
+ cand->type = PJ_ICE_CAND_TYPE_HOST;
+
+ } else if (pj_ansi_stricmp(token, "srflx") == 0) {
+ cand->type = PJ_ICE_CAND_TYPE_SRFLX;
+
+ } else if (pj_ansi_stricmp(token, "relay") == 0) {
+ cand->type = PJ_ICE_CAND_TYPE_RELAYED;
+
+ } else if (pj_ansi_stricmp(token, "prflx") == 0) {
+ cand->type = PJ_ICE_CAND_TYPE_PRFLX;
+
+ } else {
+ PJ_LOG(5,(obj_name, "Invalid ICE candidate type %s in candidate",
+ token));
+ goto on_return;
+ }
+
+ status = PJ_SUCCESS;
+
+on_return:
+ return status;
+}
+
+
+/* Create initial SDP offer */
+static pj_status_t create_initial_offer(struct transport_ice *tp_ice,
+ pj_pool_t *sdp_pool,
+ pjmedia_sdp_session *loc_sdp,
+ unsigned media_index)
+{
+ pj_status_t status;
+
+ /* Encode ICE in SDP */
+ status = encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index,
+ tp_ice->comp_cnt, PJ_FALSE);
+ if (status != PJ_SUCCESS) {
+ set_no_ice(tp_ice, "Error encoding SDP answer", status);
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* Verify incoming offer */
+static pj_status_t verify_ice_sdp(struct transport_ice *tp_ice,
+ pj_pool_t *tmp_pool,
+ const pjmedia_sdp_session *rem_sdp,
+ unsigned media_index,
+ pj_ice_sess_role current_ice_role,
+ struct sdp_state *sdp_state)
+{
+ const pjmedia_sdp_media *rem_m;
+ const pjmedia_sdp_attr *ufrag_attr, *pwd_attr;
+ const pjmedia_sdp_conn *rem_conn;
+ pj_bool_t comp1_found=PJ_FALSE, comp2_found=PJ_FALSE, has_rtcp=PJ_FALSE;
+ pj_sockaddr rem_conn_addr, rtcp_addr;
+ unsigned i;
+ pj_status_t status;
+
+ rem_m = rem_sdp->media[media_index];
+
+ /* Get the "ice-ufrag" and "ice-pwd" attributes */
+ get_ice_attr(rem_sdp, rem_m, &ufrag_attr, &pwd_attr);
+
+ /* If "ice-ufrag" or "ice-pwd" are not found, disable ICE */
+ if (ufrag_attr==NULL || pwd_attr==NULL) {
+ sdp_state->match_comp_cnt = 0;
+ return PJ_SUCCESS;
+ }
+
+ /* Verify that default target for each component matches one of the
+ * candidate for the component. Otherwise stop ICE with ICE ice_mismatch
+ * error.
+ */
+
+ /* Component 1 is the c= line */
+ rem_conn = rem_m->conn;
+ if (rem_conn == NULL)
+ rem_conn = rem_sdp->conn;
+ if (!rem_conn)
+ return PJMEDIA_SDP_EMISSINGCONN;
+
+ /* Verify address family matches */
+ if ((tp_ice->af==pj_AF_INET() &&
+ pj_strcmp(&rem_conn->addr_type, &STR_IP4)!=0) ||
+ (tp_ice->af==pj_AF_INET6() &&
+ pj_strcmp(&rem_conn->addr_type, &STR_IP6)!=0))
+ {
+ return PJMEDIA_SDP_ETPORTNOTEQUAL;
+ }
+
+ /* Assign remote connection address */
+ status = pj_sockaddr_init(tp_ice->af, &rem_conn_addr, &rem_conn->addr,
+ (pj_uint16_t)rem_m->desc.port);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (tp_ice->comp_cnt > 1) {
+ const pjmedia_sdp_attr *attr;
+
+ /* Get default RTCP candidate from a=rtcp line, if present, otherwise
+ * calculate default RTCP candidate from default RTP target.
+ */
+ attr = pjmedia_sdp_attr_find(rem_m->attr_count, rem_m->attr,
+ &STR_RTCP, NULL);
+ has_rtcp = (attr != NULL);
+
+ if (attr) {
+ pjmedia_sdp_rtcp_attr rtcp_attr;
+
+ status = pjmedia_sdp_attr_get_rtcp(attr, &rtcp_attr);
+ if (status != PJ_SUCCESS) {
+ /* Error parsing a=rtcp attribute */
+ return status;
+ }
+
+ if (rtcp_attr.addr.slen) {
+ /* Verify address family matches */
+ if ((tp_ice->af==pj_AF_INET() &&
+ pj_strcmp(&rtcp_attr.addr_type, &STR_IP4)!=0) ||
+ (tp_ice->af==pj_AF_INET6() &&
+ pj_strcmp(&rtcp_attr.addr_type, &STR_IP6)!=0))
+ {
+ return PJMEDIA_SDP_ETPORTNOTEQUAL;
+ }
+
+ /* Assign RTCP address */
+ status = pj_sockaddr_init(tp_ice->af, &rtcp_addr,
+ &rtcp_attr.addr,
+ (pj_uint16_t)rtcp_attr.port);
+ if (status != PJ_SUCCESS) {
+ return PJMEDIA_SDP_EINRTCP;
+ }
+ } else {
+ /* Assign RTCP address */
+ status = pj_sockaddr_init(tp_ice->af, &rtcp_addr,
+ NULL,
+ (pj_uint16_t)rtcp_attr.port);
+ if (status != PJ_SUCCESS) {
+ return PJMEDIA_SDP_EINRTCP;
+ }
+ pj_sockaddr_copy_addr(&rtcp_addr, &rem_conn_addr);
+ }
+ } else {
+ unsigned rtcp_port;
+
+ rtcp_port = pj_sockaddr_get_port(&rem_conn_addr) + 1;
+ pj_sockaddr_cp(&rtcp_addr, &rem_conn_addr);
+ pj_sockaddr_set_port(&rtcp_addr, (pj_uint16_t)rtcp_port);
+ }
+ }
+
+ /* Find the default addresses in a=candidate attributes.
+ */
+ for (i=0; i<rem_m->attr_count; ++i) {
+ pj_ice_sess_cand cand;
+
+ if (pj_strcmp(&rem_m->attr[i]->name, &STR_CANDIDATE)!=0)
+ continue;
+
+ status = parse_cand(tp_ice->base.name, tmp_pool,
+ &rem_m->attr[i]->value, &cand);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(4,(tp_ice->base.name,
+ "Error in parsing SDP candidate attribute '%.*s', "
+ "candidate is ignored",
+ (int)rem_m->attr[i]->value.slen,
+ rem_m->attr[i]->value.ptr));
+ continue;
+ }
+
+ if (!comp1_found && cand.comp_id==COMP_RTP &&
+ pj_sockaddr_cmp(&rem_conn_addr, &cand.addr)==0)
+ {
+ comp1_found = PJ_TRUE;
+ } else if (!comp2_found && cand.comp_id==COMP_RTCP &&
+ pj_sockaddr_cmp(&rtcp_addr, &cand.addr)==0)
+ {
+ comp2_found = PJ_TRUE;
+ }
+
+ if (cand.comp_id == COMP_RTCP)
+ has_rtcp = PJ_TRUE;
+
+ if (comp1_found && (comp2_found || tp_ice->comp_cnt==1))
+ break;
+ }
+
+ /* Check matched component count and ice_mismatch */
+ if (comp1_found && (tp_ice->comp_cnt==1 || !has_rtcp)) {
+ sdp_state->match_comp_cnt = 1;
+ sdp_state->ice_mismatch = PJ_FALSE;
+ } else if (comp1_found && comp2_found) {
+ sdp_state->match_comp_cnt = 2;
+ sdp_state->ice_mismatch = PJ_FALSE;
+ } else {
+ sdp_state->match_comp_cnt = (tp_ice->comp_cnt > 1 && has_rtcp)? 2 : 1;
+ sdp_state->ice_mismatch = PJ_TRUE;
+ }
+
+
+ /* Detect remote restarting session */
+ if (pj_ice_strans_has_sess(tp_ice->ice_st) &&
+ (pj_ice_strans_sess_is_running(tp_ice->ice_st) ||
+ pj_ice_strans_sess_is_complete(tp_ice->ice_st)))
+ {
+ pj_str_t rem_run_ufrag, rem_run_pwd;
+ pj_ice_strans_get_ufrag_pwd(tp_ice->ice_st, NULL, NULL,
+ &rem_run_ufrag, &rem_run_pwd);
+ if (pj_strcmp(&ufrag_attr->value, &rem_run_ufrag) ||
+ pj_strcmp(&pwd_attr->value, &rem_run_pwd))
+ {
+ /* Remote offers to restart ICE */
+ sdp_state->ice_restart = PJ_TRUE;
+ } else {
+ sdp_state->ice_restart = PJ_FALSE;
+ }
+ } else {
+ sdp_state->ice_restart = PJ_FALSE;
+ }
+
+ /* Detect our role */
+ if (current_ice_role==PJ_ICE_SESS_ROLE_CONTROLLING) {
+ sdp_state->local_role = PJ_ICE_SESS_ROLE_CONTROLLING;
+ } else {
+ if (pjmedia_sdp_attr_find(rem_sdp->attr_count, rem_sdp->attr,
+ &STR_ICE_LITE, NULL) != NULL)
+ {
+ /* Remote is ICE Lite */
+ sdp_state->local_role = PJ_ICE_SESS_ROLE_CONTROLLING;
+ } else {
+ sdp_state->local_role = PJ_ICE_SESS_ROLE_CONTROLLED;
+ }
+ }
+
+ PJ_LOG(4,(tp_ice->base.name,
+ "Processing SDP: support ICE=%u, common comp_cnt=%u, "
+ "ice_mismatch=%u, ice_restart=%u, local_role=%s",
+ (sdp_state->match_comp_cnt != 0),
+ sdp_state->match_comp_cnt,
+ sdp_state->ice_mismatch,
+ sdp_state->ice_restart,
+ pj_ice_sess_role_name(sdp_state->local_role)));
+
+ return PJ_SUCCESS;
+
+}
+
+
+/* Verify incoming offer and create initial answer */
+static pj_status_t create_initial_answer(struct transport_ice *tp_ice,
+ pj_pool_t *sdp_pool,
+ pjmedia_sdp_session *loc_sdp,
+ const pjmedia_sdp_session *rem_sdp,
+ unsigned media_index)
+{
+ const pjmedia_sdp_media *rem_m = rem_sdp->media[media_index];
+ pj_status_t status;
+
+ /* Check if media is removed (just in case) */
+ if (rem_m->desc.port == 0) {
+ return PJ_SUCCESS;
+ }
+
+ /* Verify the offer */
+ status = verify_ice_sdp(tp_ice, sdp_pool, rem_sdp, media_index,
+ PJ_ICE_SESS_ROLE_CONTROLLED,
+ &tp_ice->rem_offer_state);
+ if (status != PJ_SUCCESS) {
+ set_no_ice(tp_ice, "Invalid SDP offer", status);
+ return status;
+ }
+
+ /* Does remote support ICE? */
+ if (tp_ice->rem_offer_state.match_comp_cnt==0) {
+ set_no_ice(tp_ice, "No ICE found in SDP offer", PJ_SUCCESS);
+ return PJ_SUCCESS;
+ }
+
+ /* ICE ice_mismatch? */
+ if (tp_ice->rem_offer_state.ice_mismatch) {
+ set_no_ice(tp_ice, "ICE ice_mismatch in remote offer", PJ_SUCCESS);
+ encode_ice_mismatch(sdp_pool, loc_sdp, media_index);
+ return PJ_SUCCESS;
+ }
+
+ /* Encode ICE in SDP */
+ status = encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index,
+ tp_ice->rem_offer_state.match_comp_cnt,
+ PJ_FALSE);
+ if (status != PJ_SUCCESS) {
+ set_no_ice(tp_ice, "Error encoding SDP answer", status);
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* Create subsequent SDP offer */
+static pj_status_t create_subsequent_offer(struct transport_ice *tp_ice,
+ pj_pool_t *sdp_pool,
+ pjmedia_sdp_session *loc_sdp,
+ unsigned media_index)
+{
+ unsigned comp_cnt;
+
+ if (pj_ice_strans_has_sess(tp_ice->ice_st) == PJ_FALSE) {
+ /* We don't have ICE */
+ return PJ_SUCCESS;
+ }
+
+ comp_cnt = pj_ice_strans_get_running_comp_cnt(tp_ice->ice_st);
+ return encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index,
+ comp_cnt, PJ_FALSE);
+}
+
+
+/* Create subsequent SDP answer */
+static pj_status_t create_subsequent_answer(struct transport_ice *tp_ice,
+ pj_pool_t *sdp_pool,
+ pjmedia_sdp_session *loc_sdp,
+ const pjmedia_sdp_session *rem_sdp,
+ unsigned media_index)
+{
+ pj_status_t status;
+
+ /* We have a session */
+ status = verify_ice_sdp(tp_ice, sdp_pool, rem_sdp, media_index,
+ PJ_ICE_SESS_ROLE_CONTROLLED,
+ &tp_ice->rem_offer_state);
+ if (status != PJ_SUCCESS) {
+ /* Something wrong with the offer */
+ return status;
+ }
+
+ if (pj_ice_strans_has_sess(tp_ice->ice_st)) {
+ /*
+ * Received subsequent offer while we have ICE active.
+ */
+
+ if (tp_ice->rem_offer_state.match_comp_cnt == 0) {
+ /* Remote no longer offers ICE */
+ return PJ_SUCCESS;
+ }
+
+ if (tp_ice->rem_offer_state.ice_mismatch) {
+ encode_ice_mismatch(sdp_pool, loc_sdp, media_index);
+ return PJ_SUCCESS;
+ }
+
+ status = encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index,
+ tp_ice->rem_offer_state.match_comp_cnt,
+ tp_ice->rem_offer_state.ice_restart);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Done */
+
+ } else {
+ /*
+ * Received subsequent offer while we DON'T have ICE active.
+ */
+
+ if (tp_ice->rem_offer_state.match_comp_cnt == 0) {
+ /* Remote does not support ICE */
+ return PJ_SUCCESS;
+ }
+
+ if (tp_ice->rem_offer_state.ice_mismatch) {
+ encode_ice_mismatch(sdp_pool, loc_sdp, media_index);
+ return PJ_SUCCESS;
+ }
+
+ /* Looks like now remote is offering ICE, so we need to create
+ * ICE session now.
+ */
+ status = pj_ice_strans_init_ice(tp_ice->ice_st,
+ PJ_ICE_SESS_ROLE_CONTROLLED,
+ NULL, NULL);
+ if (status != PJ_SUCCESS) {
+ /* Fail to create new ICE session */
+ return status;
+ }
+
+ status = encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index,
+ tp_ice->rem_offer_state.match_comp_cnt,
+ tp_ice->rem_offer_state.ice_restart);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Done */
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * For both UAC and UAS, pass in the SDP before sending it to remote.
+ * This will add ICE attributes to the SDP.
+ */
+static pj_status_t transport_media_create(pjmedia_transport *tp,
+ pj_pool_t *sdp_pool,
+ unsigned options,
+ const pjmedia_sdp_session *rem_sdp,
+ unsigned media_index)
+{
+ struct transport_ice *tp_ice = (struct transport_ice*)tp;
+ pj_ice_sess_role ice_role;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(media_index);
+ PJ_UNUSED_ARG(sdp_pool);
+
+ tp_ice->media_option = options;
+ tp_ice->oa_role = ROLE_NONE;
+ tp_ice->initial_sdp = PJ_TRUE;
+
+ /* Init ICE, the initial role is set now based on availability of
+ * rem_sdp, but it will be checked again later.
+ */
+ ice_role = (rem_sdp==NULL ? PJ_ICE_SESS_ROLE_CONTROLLING :
+ PJ_ICE_SESS_ROLE_CONTROLLED);
+ status = pj_ice_strans_init_ice(tp_ice->ice_st, ice_role, NULL, NULL);
+
+ /* Done */
+ return status;
+}
+
+
+static pj_status_t transport_encode_sdp(pjmedia_transport *tp,
+ pj_pool_t *sdp_pool,
+ pjmedia_sdp_session *sdp_local,
+ const pjmedia_sdp_session *rem_sdp,
+ unsigned media_index)
+{
+ struct transport_ice *tp_ice = (struct transport_ice*)tp;
+ pj_status_t status;
+
+ /* Validate media transport */
+ /* This transport only support RTP/AVP transport, unless if
+ * transport checking is disabled
+ */
+ if ((tp_ice->media_option & PJMEDIA_TPMED_NO_TRANSPORT_CHECKING) == 0) {
+ pjmedia_sdp_media *loc_m, *rem_m;
+
+ rem_m = rem_sdp? rem_sdp->media[media_index] : NULL;
+ loc_m = sdp_local->media[media_index];
+
+ if (pj_stricmp(&loc_m->desc.transport, &STR_RTP_AVP) ||
+ (rem_m && pj_stricmp(&rem_m->desc.transport, &STR_RTP_AVP)))
+ {
+ pjmedia_sdp_media_deactivate(sdp_pool, loc_m);
+ return PJMEDIA_SDP_EINPROTO;
+ }
+ }
+
+ if (tp_ice->initial_sdp) {
+ if (rem_sdp) {
+ status = create_initial_answer(tp_ice, sdp_pool, sdp_local,
+ rem_sdp, media_index);
+ } else {
+ status = create_initial_offer(tp_ice, sdp_pool, sdp_local,
+ media_index);
+ }
+ } else {
+ if (rem_sdp) {
+ status = create_subsequent_answer(tp_ice, sdp_pool, sdp_local,
+ rem_sdp, media_index);
+ } else {
+ status = create_subsequent_offer(tp_ice, sdp_pool, sdp_local,
+ media_index);
+ }
+ }
+
+ if (status==PJ_SUCCESS) {
+ if (rem_sdp)
+ tp_ice->oa_role = ROLE_ANSWERER;
+ else
+ tp_ice->oa_role = ROLE_OFFERER;
+ }
+
+ return status;
+}
+
+
+/* Start ICE session with the specified remote SDP */
+static pj_status_t start_ice(struct transport_ice *tp_ice,
+ pj_pool_t *tmp_pool,
+ const pjmedia_sdp_session *rem_sdp,
+ unsigned media_index)
+{
+ pjmedia_sdp_media *rem_m = rem_sdp->media[media_index];
+ const pjmedia_sdp_attr *ufrag_attr, *pwd_attr;
+ pj_ice_sess_cand *cand;
+ unsigned i, cand_cnt;
+ pj_status_t status;
+
+ get_ice_attr(rem_sdp, rem_m, &ufrag_attr, &pwd_attr);
+
+ /* Allocate candidate array */
+ cand = (pj_ice_sess_cand*)
+ pj_pool_calloc(tmp_pool, PJ_ICE_MAX_CAND,
+ sizeof(pj_ice_sess_cand));
+
+ /* Get all candidates in the media */
+ cand_cnt = 0;
+ for (i=0; i<rem_m->attr_count && cand_cnt < PJ_ICE_MAX_CAND; ++i) {
+ pjmedia_sdp_attr *attr;
+
+ attr = rem_m->attr[i];
+
+ if (pj_strcmp(&attr->name, &STR_CANDIDATE)!=0)
+ continue;
+
+ /* Parse candidate */
+ status = parse_cand(tp_ice->base.name, tmp_pool, &attr->value,
+ &cand[cand_cnt]);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(4,(tp_ice->base.name,
+ "Error in parsing SDP candidate attribute '%.*s', "
+ "candidate is ignored",
+ (int)attr->value.slen, attr->value.ptr));
+ continue;
+ }
+
+ cand_cnt++;
+ }
+
+ /* Start ICE */
+ return pj_ice_strans_start_ice(tp_ice->ice_st, &ufrag_attr->value,
+ &pwd_attr->value, cand_cnt, cand);
+}
+
+
+/*
+ * Start ICE checks when both offer and answer have been negotiated
+ * by SDP negotiator.
+ */
+static pj_status_t transport_media_start(pjmedia_transport *tp,
+ pj_pool_t *tmp_pool,
+ const pjmedia_sdp_session *sdp_local,
+ const pjmedia_sdp_session *rem_sdp,
+ unsigned media_index)
+{
+ struct transport_ice *tp_ice = (struct transport_ice*)tp;
+ pjmedia_sdp_media *rem_m;
+ enum oa_role current_oa_role;
+ pj_bool_t initial_oa;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(tp && tmp_pool && rem_sdp, PJ_EINVAL);
+ PJ_ASSERT_RETURN(media_index < rem_sdp->media_count, PJ_EINVAL);
+
+ rem_m = rem_sdp->media[media_index];
+
+ initial_oa = tp_ice->initial_sdp;
+ current_oa_role = tp_ice->oa_role;
+
+ /* SDP has been negotiated */
+ tp_ice->initial_sdp = PJ_FALSE;
+ tp_ice->oa_role = ROLE_NONE;
+
+ /* Nothing to do if we don't have ICE session */
+ if (pj_ice_strans_has_sess(tp_ice->ice_st) == PJ_FALSE) {
+ return PJ_SUCCESS;
+ }
+
+ /* Special case for Session Timer. The re-INVITE for session refresh
+ * doesn't call transport_encode_sdp(), causing current_oa_role to
+ * be set to ROLE_NONE. This is a workaround.
+ */
+ if (current_oa_role == ROLE_NONE) {
+ current_oa_role = ROLE_OFFERER;
+ }
+
+ /* Processing depends on the offer/answer role */
+ if (current_oa_role == ROLE_OFFERER) {
+ /*
+ * We are offerer. So this will be the first time we see the
+ * remote's SDP.
+ */
+ struct sdp_state answer_state;
+
+ /* Verify the answer */
+ status = verify_ice_sdp(tp_ice, tmp_pool, rem_sdp, media_index,
+ PJ_ICE_SESS_ROLE_CONTROLLING, &answer_state);
+ if (status != PJ_SUCCESS) {
+ /* Something wrong in the SDP answer */
+ set_no_ice(tp_ice, "Invalid remote SDP answer", status);
+ return status;
+ }
+
+ /* Does it have ICE? */
+ if (answer_state.match_comp_cnt == 0) {
+ /* Remote doesn't support ICE */
+ set_no_ice(tp_ice, "Remote answer doesn't support ICE",
+ PJ_SUCCESS);
+ return PJ_SUCCESS;
+ }
+
+ /* Check if remote has reported ice-mismatch */
+ if (pjmedia_sdp_attr_find(rem_m->attr_count, rem_m->attr,
+ &STR_ICE_MISMATCH, NULL) != NULL)
+ {
+ /* Remote has reported ice-mismatch */
+ set_no_ice(tp_ice,
+ "Remote answer contains 'ice-mismatch' attribute",
+ PJ_SUCCESS);
+ return PJ_SUCCESS;
+ }
+
+ /* Check if remote has indicated a restart */
+ if (answer_state.ice_restart) {
+ PJ_LOG(2,(tp_ice->base.name,
+ "Warning: remote has signalled ICE restart in SDP "
+ "answer which is disallowed. Remote ICE negotiation"
+ " may fail."));
+ }
+
+ /* Check if the answer itself is mismatched */
+ if (answer_state.ice_mismatch) {
+ /* This happens either when a B2BUA modified remote answer but
+ * strangely didn't modify our offer, or remote is not capable
+ * of detecting mismatch in our offer (it didn't put
+ * 'ice-mismatch' attribute in the answer).
+ */
+ PJ_LOG(2,(tp_ice->base.name,
+ "Warning: remote answer mismatch, but it does not "
+ "reject our offer with 'ice-mismatch'. ICE negotiation "
+ "may fail"));
+ }
+
+ /* Do nothing if ICE is complete or running */
+ if (pj_ice_strans_sess_is_running(tp_ice->ice_st)) {
+ PJ_LOG(4,(tp_ice->base.name,
+ "Ignored offer/answer because ICE is running"));
+ return PJ_SUCCESS;
+ }
+
+ if (pj_ice_strans_sess_is_complete(tp_ice->ice_st)) {
+ PJ_LOG(4,(tp_ice->base.name, "ICE session unchanged"));
+ return PJ_SUCCESS;
+ }
+
+ /* Start ICE */
+
+ } else {
+ /*
+ * We are answerer. We've seen and negotiated remote's SDP
+ * before, and the result is in "rem_offer_state".
+ */
+ const pjmedia_sdp_attr *ufrag_attr, *pwd_attr;
+
+ /* Check for ICE in remote offer */
+ if (tp_ice->rem_offer_state.match_comp_cnt == 0) {
+ /* No ICE attribute present */
+ set_no_ice(tp_ice, "Remote no longer offers ICE",
+ PJ_SUCCESS);
+ return PJ_SUCCESS;
+ }
+
+ /* Check for ICE ice_mismatch condition in the offer */
+ if (tp_ice->rem_offer_state.ice_mismatch) {
+ set_no_ice(tp_ice, "Remote offer mismatch: ",
+ PJNATH_EICEMISMATCH);
+ return PJ_SUCCESS;
+ }
+
+ /* If ICE is complete and remote doesn't request restart,
+ * then leave the session as is.
+ */
+ if (!initial_oa && tp_ice->rem_offer_state.ice_restart == PJ_FALSE) {
+ /* Remote has not requested ICE restart, so session is
+ * unchanged.
+ */
+ PJ_LOG(4,(tp_ice->base.name, "ICE session unchanged"));
+ return PJ_SUCCESS;
+ }
+
+ /* Either remote has requested ICE restart or this is our
+ * first answer.
+ */
+
+ /* Stop ICE */
+ if (!initial_oa) {
+ set_no_ice(tp_ice, "restarting by remote request..", PJ_SUCCESS);
+
+ /* We have put new ICE ufrag and pwd in the answer. Now
+ * create a new ICE session with that ufrag/pwd pair.
+ */
+ get_ice_attr(sdp_local, sdp_local->media[media_index],
+ &ufrag_attr, &pwd_attr);
+ status = pj_ice_strans_init_ice(tp_ice->ice_st,
+ tp_ice->rem_offer_state.local_role,
+ &ufrag_attr->value,
+ &pwd_attr->value);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(1,(tp_ice->base.name,
+ "ICE re-initialization failed (status=%d)!",
+ status));
+ return status;
+ }
+ }
+
+ /* Ticket #977: Update role if turns out we're supposed to be the
+ * Controlling agent (e.g. when talking to ice-lite peer).
+ */
+ if (tp_ice->rem_offer_state.local_role==PJ_ICE_SESS_ROLE_CONTROLLING &&
+ pj_ice_strans_has_sess(tp_ice->ice_st))
+ {
+ pj_ice_strans_change_role(tp_ice->ice_st,
+ PJ_ICE_SESS_ROLE_CONTROLLING);
+ }
+
+
+ /* start ICE */
+ }
+
+ /* Now start ICE */
+ status = start_ice(tp_ice, tmp_pool, rem_sdp, media_index);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(1,(tp_ice->base.name,
+ "ICE restart failed (status=%d)!",
+ status));
+ return status;
+ }
+
+ /* Done */
+ tp_ice->use_ice = PJ_TRUE;
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t transport_media_stop(pjmedia_transport *tp)
+{
+ struct transport_ice *tp_ice = (struct transport_ice*)tp;
+
+ set_no_ice(tp_ice, "media stop requested", PJ_SUCCESS);
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t transport_get_info(pjmedia_transport *tp,
+ pjmedia_transport_info *info)
+{
+ struct transport_ice *tp_ice = (struct transport_ice*)tp;
+ pj_ice_sess_cand cand;
+ pj_status_t status;
+
+ pj_bzero(&info->sock_info, sizeof(info->sock_info));
+ info->sock_info.rtp_sock = info->sock_info.rtcp_sock = PJ_INVALID_SOCKET;
+
+ /* Get RTP default address */
+ status = pj_ice_strans_get_def_cand(tp_ice->ice_st, 1, &cand);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_sockaddr_cp(&info->sock_info.rtp_addr_name, &cand.addr);
+
+ /* Get RTCP default address */
+ if (tp_ice->comp_cnt > 1) {
+ status = pj_ice_strans_get_def_cand(tp_ice->ice_st, 2, &cand);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, &cand.addr);
+ }
+
+ /* Set remote address originating RTP & RTCP if this transport has
+ * ICE activated or received any packets.
+ */
+ if (tp_ice->use_ice || tp_ice->rtp_src_cnt) {
+ info->src_rtp_name = tp_ice->rtp_src_addr;
+ }
+ if (tp_ice->use_ice || tp_ice->rtcp_src_cnt) {
+ info->src_rtcp_name = tp_ice->rtcp_src_addr;
+ }
+
+ /* Fill up transport specific info */
+ if (info->specific_info_cnt < PJ_ARRAY_SIZE(info->spc_info)) {
+ pjmedia_transport_specific_info *tsi;
+ pjmedia_ice_transport_info *ii;
+ unsigned i;
+
+ pj_assert(sizeof(*ii) <= sizeof(tsi->buffer));
+ tsi = &info->spc_info[info->specific_info_cnt++];
+ tsi->type = PJMEDIA_TRANSPORT_TYPE_ICE;
+ tsi->cbsize = sizeof(*ii);
+
+ ii = (pjmedia_ice_transport_info*) tsi->buffer;
+ pj_bzero(ii, sizeof(*ii));
+
+ if (pj_ice_strans_has_sess(tp_ice->ice_st))
+ ii->role = pj_ice_strans_get_role(tp_ice->ice_st);
+ else
+ ii->role = PJ_ICE_SESS_ROLE_UNKNOWN;
+ ii->sess_state = pj_ice_strans_get_state(tp_ice->ice_st);
+ ii->comp_cnt = pj_ice_strans_get_running_comp_cnt(tp_ice->ice_st);
+
+ for (i=1; i<=ii->comp_cnt && i<=PJ_ARRAY_SIZE(ii->comp); ++i) {
+ const pj_ice_sess_check *chk;
+
+ chk = pj_ice_strans_get_valid_pair(tp_ice->ice_st, i);
+ if (chk) {
+ ii->comp[i-1].lcand_type = chk->lcand->type;
+ pj_sockaddr_cp(&ii->comp[i-1].lcand_addr,
+ &chk->lcand->addr);
+ ii->comp[i-1].rcand_type = chk->rcand->type;
+ pj_sockaddr_cp(&ii->comp[i-1].rcand_addr,
+ &chk->rcand->addr);
+ }
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t transport_attach (pjmedia_transport *tp,
+ void *stream,
+ const pj_sockaddr_t *rem_addr,
+ const pj_sockaddr_t *rem_rtcp,
+ unsigned addr_len,
+ void (*rtp_cb)(void*,
+ void*,
+ pj_ssize_t),
+ void (*rtcp_cb)(void*,
+ void*,
+ pj_ssize_t))
+{
+ struct transport_ice *tp_ice = (struct transport_ice*)tp;
+
+ tp_ice->stream = stream;
+ tp_ice->rtp_cb = rtp_cb;
+ tp_ice->rtcp_cb = rtcp_cb;
+
+ pj_memcpy(&tp_ice->remote_rtp, rem_addr, addr_len);
+ pj_memcpy(&tp_ice->remote_rtcp, rem_rtcp, addr_len);
+ tp_ice->addr_len = addr_len;
+
+ /* Init source RTP & RTCP addresses and counter */
+ tp_ice->rtp_src_addr = tp_ice->remote_rtp;
+ tp_ice->rtcp_src_addr = tp_ice->remote_rtcp;
+ tp_ice->rtp_src_cnt = 0;
+ tp_ice->rtcp_src_cnt = 0;
+
+ return PJ_SUCCESS;
+}
+
+
+static void transport_detach(pjmedia_transport *tp,
+ void *strm)
+{
+ struct transport_ice *tp_ice = (struct transport_ice*)tp;
+
+ /* TODO: need to solve ticket #460 here */
+
+ tp_ice->rtp_cb = NULL;
+ tp_ice->rtcp_cb = NULL;
+ tp_ice->stream = NULL;
+
+ PJ_UNUSED_ARG(strm);
+}
+
+
+static pj_status_t transport_send_rtp(pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size)
+{
+ struct transport_ice *tp_ice = (struct transport_ice*)tp;
+
+ /* Simulate packet lost on TX direction */
+ if (tp_ice->tx_drop_pct) {
+ if ((pj_rand() % 100) <= (int)tp_ice->tx_drop_pct) {
+ PJ_LOG(5,(tp_ice->base.name,
+ "TX RTP packet dropped because of pkt lost "
+ "simulation"));
+ return PJ_SUCCESS;
+ }
+ }
+
+ return pj_ice_strans_sendto(tp_ice->ice_st, 1,
+ pkt, size, &tp_ice->remote_rtp,
+ tp_ice->addr_len);
+}
+
+
+static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size)
+{
+ return transport_send_rtcp2(tp, NULL, 0, pkt, size);
+}
+
+static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
+ const pj_sockaddr_t *addr,
+ unsigned addr_len,
+ const void *pkt,
+ pj_size_t size)
+{
+ struct transport_ice *tp_ice = (struct transport_ice*)tp;
+ if (tp_ice->comp_cnt > 1) {
+ if (addr == NULL) {
+ addr = &tp_ice->remote_rtcp;
+ addr_len = pj_sockaddr_get_len(addr);
+ }
+ return pj_ice_strans_sendto(tp_ice->ice_st, 2, pkt, size,
+ addr, addr_len);
+ } else {
+ return PJ_SUCCESS;
+ }
+}
+
+
+static void ice_on_rx_data(pj_ice_strans *ice_st, unsigned comp_id,
+ void *pkt, pj_size_t size,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ struct transport_ice *tp_ice;
+ pj_bool_t discard = PJ_FALSE;
+
+ tp_ice = (struct transport_ice*) pj_ice_strans_get_user_data(ice_st);
+
+ if (comp_id==1 && tp_ice->rtp_cb) {
+
+ /* Simulate packet lost on RX direction */
+ if (tp_ice->rx_drop_pct) {
+ if ((pj_rand() % 100) <= (int)tp_ice->rx_drop_pct) {
+ PJ_LOG(5,(tp_ice->base.name,
+ "RX RTP packet dropped because of pkt lost "
+ "simulation"));
+ return;
+ }
+ }
+
+ /* See if source address of RTP packet is different than the
+ * configured address, and switch RTP remote address to
+ * source packet address after several consecutive packets
+ * have been received.
+ */
+ if (!tp_ice->use_ice) {
+ pj_bool_t enable_switch =
+ ((tp_ice->options & PJMEDIA_ICE_NO_SRC_ADDR_CHECKING)==0);
+
+ if (!enable_switch ||
+ pj_sockaddr_cmp(&tp_ice->remote_rtp, src_addr) == 0)
+ {
+ /* Don't switch while we're receiving from remote_rtp */
+ tp_ice->rtp_src_cnt = 0;
+ } else {
+
+ ++tp_ice->rtp_src_cnt;
+
+ /* Check if the source address is recognized. */
+ if (pj_sockaddr_cmp(src_addr, &tp_ice->rtp_src_addr) != 0) {
+ /* Remember the new source address. */
+ pj_sockaddr_cp(&tp_ice->rtp_src_addr, src_addr);
+ /* Reset counter */
+ tp_ice->rtp_src_cnt = 0;
+ discard = PJ_TRUE;
+ }
+
+ if (tp_ice->rtp_src_cnt < PJMEDIA_RTP_NAT_PROBATION_CNT) {
+ discard = PJ_TRUE;
+ } else {
+ char addr_text[80];
+
+ /* Set remote RTP address to source address */
+ pj_sockaddr_cp(&tp_ice->remote_rtp, &tp_ice->rtp_src_addr);
+ tp_ice->addr_len = pj_sockaddr_get_len(&tp_ice->remote_rtp);
+
+ /* Reset counter */
+ tp_ice->rtp_src_cnt = 0;
+
+ PJ_LOG(4,(tp_ice->base.name,
+ "Remote RTP address switched to %s",
+ pj_sockaddr_print(&tp_ice->remote_rtp, addr_text,
+ sizeof(addr_text), 3)));
+
+ /* Also update remote RTCP address if actual RTCP source
+ * address is not heard yet.
+ */
+ if (!pj_sockaddr_has_addr(&tp_ice->rtcp_src_addr)) {
+ pj_uint16_t port;
+
+ pj_sockaddr_cp(&tp_ice->remote_rtcp,
+ &tp_ice->remote_rtp);
+
+ port = (pj_uint16_t)
+ (pj_sockaddr_get_port(&tp_ice->remote_rtp)+1);
+ pj_sockaddr_set_port(&tp_ice->remote_rtcp, port);
+
+ PJ_LOG(4,(tp_ice->base.name,
+ "Remote RTCP address switched to predicted "
+ "address %s",
+ pj_sockaddr_print(&tp_ice->remote_rtcp,
+ addr_text,
+ sizeof(addr_text), 3)));
+ }
+ }
+ }
+ }
+
+ if (!discard)
+ (*tp_ice->rtp_cb)(tp_ice->stream, pkt, size);
+
+ } else if (comp_id==2 && tp_ice->rtcp_cb) {
+
+ /* Check if RTCP source address is the same as the configured
+ * remote address, and switch the address when they are
+ * different.
+ */
+ if (!tp_ice->use_ice &&
+ (tp_ice->options & PJMEDIA_ICE_NO_SRC_ADDR_CHECKING)==0)
+ {
+ if (pj_sockaddr_cmp(&tp_ice->remote_rtcp, src_addr) == 0) {
+ tp_ice->rtcp_src_cnt = 0;
+ } else {
+ char addr_text[80];
+
+ ++tp_ice->rtcp_src_cnt;
+ if (tp_ice->rtcp_src_cnt < PJMEDIA_RTCP_NAT_PROBATION_CNT) {
+ discard = PJ_TRUE;
+ } else {
+ tp_ice->rtcp_src_cnt = 0;
+ pj_sockaddr_cp(&tp_ice->rtcp_src_addr, src_addr);
+ pj_sockaddr_cp(&tp_ice->remote_rtcp, src_addr);
+
+ pj_assert(tp_ice->addr_len==pj_sockaddr_get_len(src_addr));
+
+ PJ_LOG(4,(tp_ice->base.name,
+ "Remote RTCP address switched to %s",
+ pj_sockaddr_print(&tp_ice->remote_rtcp,
+ addr_text, sizeof(addr_text),
+ 3)));
+ }
+ }
+ }
+
+ if (!discard)
+ (*tp_ice->rtcp_cb)(tp_ice->stream, pkt, size);
+ }
+
+ PJ_UNUSED_ARG(src_addr_len);
+}
+
+
+static void ice_on_ice_complete(pj_ice_strans *ice_st,
+ pj_ice_strans_op op,
+ pj_status_t result)
+{
+ struct transport_ice *tp_ice;
+
+ tp_ice = (struct transport_ice*) pj_ice_strans_get_user_data(ice_st);
+
+ /* Notify application */
+ if (tp_ice->cb.on_ice_complete)
+ (*tp_ice->cb.on_ice_complete)(&tp_ice->base, op, result);
+}
+
+
+/* Simulate lost */
+static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
+ pjmedia_dir dir,
+ unsigned pct_lost)
+{
+ struct transport_ice *ice = (struct transport_ice*) tp;
+
+ PJ_ASSERT_RETURN(tp && pct_lost <= 100, PJ_EINVAL);
+
+ if (dir & PJMEDIA_DIR_ENCODING)
+ ice->tx_drop_pct = pct_lost;
+
+ if (dir & PJMEDIA_DIR_DECODING)
+ ice->rx_drop_pct = pct_lost;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy ICE media transport.
+ */
+static pj_status_t transport_destroy(pjmedia_transport *tp)
+{
+ struct transport_ice *tp_ice = (struct transport_ice*)tp;
+
+ if (tp_ice->ice_st) {
+ pj_ice_strans_destroy(tp_ice->ice_st);
+ tp_ice->ice_st = NULL;
+ }
+
+ if (tp_ice->pool) {
+ pj_pool_t *pool = tp_ice->pool;
+ tp_ice->pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjmedia/src/pjmedia/transport_loop.c b/pjmedia/src/pjmedia/transport_loop.c
new file mode 100644
index 0000000..c8a2c34
--- /dev/null
+++ b/pjmedia/src/pjmedia/transport_loop.c
@@ -0,0 +1,404 @@
+/* $Id: transport_loop.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/transport_loop.h>
+#include <pj/array.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/ioqueue.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/rand.h>
+#include <pj/string.h>
+
+
+struct user
+{
+ pj_bool_t rx_disabled; /**< Doesn't want to receive pkt? */
+ void *user_data; /**< Only valid when attached */
+ void (*rtp_cb)( void*, /**< To report incoming RTP. */
+ void*,
+ pj_ssize_t);
+ void (*rtcp_cb)( void*, /**< To report incoming RTCP. */
+ void*,
+ pj_ssize_t);
+};
+
+struct transport_loop
+{
+ pjmedia_transport base; /**< Base transport. */
+
+ pj_pool_t *pool; /**< Memory pool */
+ unsigned user_cnt; /**< Number of attachments */
+ struct user users[4]; /**< Array of users. */
+
+ unsigned tx_drop_pct; /**< Percent of tx pkts to drop. */
+ unsigned rx_drop_pct; /**< Percent of rx pkts to drop. */
+
+};
+
+
+
+/*
+ * These are media transport operations.
+ */
+static pj_status_t transport_get_info (pjmedia_transport *tp,
+ pjmedia_transport_info *info);
+static pj_status_t transport_attach (pjmedia_transport *tp,
+ void *user_data,
+ const pj_sockaddr_t *rem_addr,
+ const pj_sockaddr_t *rem_rtcp,
+ unsigned addr_len,
+ void (*rtp_cb)(void*,
+ void*,
+ pj_ssize_t),
+ void (*rtcp_cb)(void*,
+ void*,
+ pj_ssize_t));
+static void transport_detach (pjmedia_transport *tp,
+ void *strm);
+static pj_status_t transport_send_rtp( pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size);
+static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size);
+static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
+ const pj_sockaddr_t *addr,
+ unsigned addr_len,
+ const void *pkt,
+ pj_size_t size);
+static pj_status_t transport_media_create(pjmedia_transport *tp,
+ pj_pool_t *pool,
+ unsigned options,
+ const pjmedia_sdp_session *sdp_remote,
+ unsigned media_index);
+static pj_status_t transport_encode_sdp(pjmedia_transport *tp,
+ pj_pool_t *pool,
+ pjmedia_sdp_session *sdp_local,
+ const pjmedia_sdp_session *rem_sdp,
+ unsigned media_index);
+static pj_status_t transport_media_start (pjmedia_transport *tp,
+ pj_pool_t *pool,
+ const pjmedia_sdp_session *sdp_local,
+ const pjmedia_sdp_session *sdp_remote,
+ unsigned media_index);
+static pj_status_t transport_media_stop(pjmedia_transport *tp);
+static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
+ pjmedia_dir dir,
+ unsigned pct_lost);
+static pj_status_t transport_destroy (pjmedia_transport *tp);
+
+
+static pjmedia_transport_op transport_udp_op =
+{
+ &transport_get_info,
+ &transport_attach,
+ &transport_detach,
+ &transport_send_rtp,
+ &transport_send_rtcp,
+ &transport_send_rtcp2,
+ &transport_media_create,
+ &transport_encode_sdp,
+ &transport_media_start,
+ &transport_media_stop,
+ &transport_simulate_lost,
+ &transport_destroy
+};
+
+
+/**
+ * Create loopback transport.
+ */
+PJ_DEF(pj_status_t) pjmedia_transport_loop_create(pjmedia_endpt *endpt,
+ pjmedia_transport **p_tp)
+{
+ struct transport_loop *tp;
+ pj_pool_t *pool;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(endpt && p_tp, PJ_EINVAL);
+
+ /* Create transport structure */
+ pool = pjmedia_endpt_create_pool(endpt, "tploop", 512, 512);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ tp = PJ_POOL_ZALLOC_T(pool, struct transport_loop);
+ tp->pool = pool;
+ pj_ansi_strncpy(tp->base.name, tp->pool->obj_name, PJ_MAX_OBJ_NAME-1);
+ tp->base.op = &transport_udp_op;
+ tp->base.type = PJMEDIA_TRANSPORT_TYPE_UDP;
+
+ /* Done */
+ *p_tp = &tp->base;
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_transport_loop_disable_rx( pjmedia_transport *tp,
+ void *user,
+ pj_bool_t disabled)
+{
+ struct transport_loop *loop = (struct transport_loop*) tp;
+ unsigned i;
+
+ for (i=0; i<loop->user_cnt; ++i) {
+ if (loop->users[i].user_data == user) {
+ loop->users[i].rx_disabled = disabled;
+ return PJ_SUCCESS;
+ }
+ }
+ pj_assert(!"Invalid stream user");
+ return PJ_ENOTFOUND;
+}
+
+/**
+ * Close loopback transport.
+ */
+static pj_status_t transport_destroy(pjmedia_transport *tp)
+{
+ struct transport_loop *loop = (struct transport_loop*) tp;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(tp, PJ_EINVAL);
+
+ pj_pool_release(loop->pool);
+
+ return PJ_SUCCESS;
+}
+
+
+/* Called to get the transport info */
+static pj_status_t transport_get_info(pjmedia_transport *tp,
+ pjmedia_transport_info *info)
+{
+ PJ_ASSERT_RETURN(tp && info, PJ_EINVAL);
+
+ info->sock_info.rtp_sock = 1;
+ pj_sockaddr_in_init(&info->sock_info.rtp_addr_name.ipv4, 0, 0);
+ info->sock_info.rtcp_sock = 2;
+ pj_sockaddr_in_init(&info->sock_info.rtcp_addr_name.ipv4, 0, 0);
+
+ return PJ_SUCCESS;
+}
+
+
+/* Called by application to initialize the transport */
+static pj_status_t transport_attach( pjmedia_transport *tp,
+ void *user_data,
+ const pj_sockaddr_t *rem_addr,
+ const pj_sockaddr_t *rem_rtcp,
+ unsigned addr_len,
+ void (*rtp_cb)(void*,
+ void*,
+ pj_ssize_t),
+ void (*rtcp_cb)(void*,
+ void*,
+ pj_ssize_t))
+{
+ struct transport_loop *loop = (struct transport_loop*) tp;
+ unsigned i;
+ const pj_sockaddr *rtcp_addr;
+
+ /* Validate arguments */
+ PJ_ASSERT_RETURN(tp && rem_addr && addr_len, PJ_EINVAL);
+
+ /* Must not be "attached" to same user */
+ for (i=0; i<loop->user_cnt; ++i) {
+ PJ_ASSERT_RETURN(loop->users[i].user_data != user_data,
+ PJ_EINVALIDOP);
+ }
+ PJ_ASSERT_RETURN(loop->user_cnt != PJ_ARRAY_SIZE(loop->users),
+ PJ_ETOOMANY);
+
+ PJ_UNUSED_ARG(rem_rtcp);
+ PJ_UNUSED_ARG(rtcp_addr);
+
+ /* "Attach" the application: */
+
+ /* Save the new user */
+ loop->users[loop->user_cnt].rtp_cb = rtp_cb;
+ loop->users[loop->user_cnt].rtcp_cb = rtcp_cb;
+ loop->users[loop->user_cnt].user_data = user_data;
+ ++loop->user_cnt;
+
+ return PJ_SUCCESS;
+}
+
+
+/* Called by application when it no longer needs the transport */
+static void transport_detach( pjmedia_transport *tp,
+ void *user_data)
+{
+ struct transport_loop *loop = (struct transport_loop*) tp;
+ unsigned i;
+
+ pj_assert(tp);
+
+ for (i=0; i<loop->user_cnt; ++i) {
+ if (loop->users[i].user_data == user_data)
+ break;
+ }
+
+ /* Remove this user */
+ if (i != loop->user_cnt) {
+ pj_array_erase(loop->users, sizeof(loop->users[0]),
+ loop->user_cnt, i);
+ --loop->user_cnt;
+ }
+}
+
+
+/* Called by application to send RTP packet */
+static pj_status_t transport_send_rtp( pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size)
+{
+ struct transport_loop *loop = (struct transport_loop*)tp;
+ unsigned i;
+
+ /* Simulate packet lost on TX direction */
+ if (loop->tx_drop_pct) {
+ if ((pj_rand() % 100) <= (int)loop->tx_drop_pct) {
+ PJ_LOG(5,(loop->base.name,
+ "TX RTP packet dropped because of pkt lost "
+ "simulation"));
+ return PJ_SUCCESS;
+ }
+ }
+
+ /* Simulate packet lost on RX direction */
+ if (loop->rx_drop_pct) {
+ if ((pj_rand() % 100) <= (int)loop->rx_drop_pct) {
+ PJ_LOG(5,(loop->base.name,
+ "RX RTP packet dropped because of pkt lost "
+ "simulation"));
+ return PJ_SUCCESS;
+ }
+ }
+
+ /* Distribute to users */
+ for (i=0; i<loop->user_cnt; ++i) {
+ if (!loop->users[i].rx_disabled && loop->users[i].rtp_cb)
+ (*loop->users[i].rtp_cb)(loop->users[i].user_data, (void*)pkt,
+ size);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* Called by application to send RTCP packet */
+static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size)
+{
+ return transport_send_rtcp2(tp, NULL, 0, pkt, size);
+}
+
+
+/* Called by application to send RTCP packet */
+static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
+ const pj_sockaddr_t *addr,
+ unsigned addr_len,
+ const void *pkt,
+ pj_size_t size)
+{
+ struct transport_loop *loop = (struct transport_loop*)tp;
+ unsigned i;
+
+ PJ_UNUSED_ARG(addr_len);
+ PJ_UNUSED_ARG(addr);
+
+ /* Distribute to users */
+ for (i=0; i<loop->user_cnt; ++i) {
+ if (!loop->users[i].rx_disabled && loop->users[i].rtcp_cb)
+ (*loop->users[i].rtcp_cb)(loop->users[i].user_data, (void*)pkt,
+ size);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t transport_media_create(pjmedia_transport *tp,
+ pj_pool_t *pool,
+ unsigned options,
+ const pjmedia_sdp_session *sdp_remote,
+ unsigned media_index)
+{
+ PJ_UNUSED_ARG(tp);
+ PJ_UNUSED_ARG(pool);
+ PJ_UNUSED_ARG(options);
+ PJ_UNUSED_ARG(sdp_remote);
+ PJ_UNUSED_ARG(media_index);
+ return PJ_SUCCESS;
+}
+
+static pj_status_t transport_encode_sdp(pjmedia_transport *tp,
+ pj_pool_t *pool,
+ pjmedia_sdp_session *sdp_local,
+ const pjmedia_sdp_session *rem_sdp,
+ unsigned media_index)
+{
+ PJ_UNUSED_ARG(tp);
+ PJ_UNUSED_ARG(pool);
+ PJ_UNUSED_ARG(sdp_local);
+ PJ_UNUSED_ARG(rem_sdp);
+ PJ_UNUSED_ARG(media_index);
+ return PJ_SUCCESS;
+}
+
+static pj_status_t transport_media_start(pjmedia_transport *tp,
+ pj_pool_t *pool,
+ const pjmedia_sdp_session *sdp_local,
+ const pjmedia_sdp_session *sdp_remote,
+ unsigned media_index)
+{
+ PJ_UNUSED_ARG(tp);
+ PJ_UNUSED_ARG(pool);
+ PJ_UNUSED_ARG(sdp_local);
+ PJ_UNUSED_ARG(sdp_remote);
+ PJ_UNUSED_ARG(media_index);
+ return PJ_SUCCESS;
+}
+
+static pj_status_t transport_media_stop(pjmedia_transport *tp)
+{
+ PJ_UNUSED_ARG(tp);
+ return PJ_SUCCESS;
+}
+
+static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
+ pjmedia_dir dir,
+ unsigned pct_lost)
+{
+ struct transport_loop *loop = (struct transport_loop*)tp;
+
+ PJ_ASSERT_RETURN(tp && pct_lost <= 100, PJ_EINVAL);
+
+ if (dir & PJMEDIA_DIR_ENCODING)
+ loop->tx_drop_pct = pct_lost;
+
+ if (dir & PJMEDIA_DIR_DECODING)
+ loop->rx_drop_pct = pct_lost;
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjmedia/src/pjmedia/transport_srtp.c b/pjmedia/src/pjmedia/transport_srtp.c
new file mode 100644
index 0000000..1321e75
--- /dev/null
+++ b/pjmedia/src/pjmedia/transport_srtp.c
@@ -0,0 +1,1680 @@
+/* $Id: transport_srtp.c 3999 2012-03-30 07:10:13Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/transport_srtp.h>
+#include <pjmedia/endpoint.h>
+#include <pjlib-util/base64.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+#include <pj/lock.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+
+#include <srtp.h>
+
+#define THIS_FILE "transport_srtp.c"
+
+/* Maximum size of packet */
+#define MAX_RTP_BUFFER_LEN 1500
+#define MAX_RTCP_BUFFER_LEN 1500
+#define MAX_KEY_LEN 32
+
+/* Initial value of probation counter. When probation counter > 0,
+ * it means SRTP is in probation state, and it may restart when
+ * srtp_unprotect() returns err_status_replay_*
+ */
+#define PROBATION_CNT_INIT 100
+
+#define DEACTIVATE_MEDIA(pool, m) pjmedia_sdp_media_deactivate(pool, m)
+
+static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 };
+static const pj_str_t ID_RTP_SAVP = { "RTP/SAVP", 8 };
+static const pj_str_t ID_INACTIVE = { "inactive", 8 };
+static const pj_str_t ID_CRYPTO = { "crypto", 6 };
+
+typedef struct crypto_suite
+{
+ char *name;
+ cipher_type_id_t cipher_type;
+ unsigned cipher_key_len;
+ auth_type_id_t auth_type;
+ unsigned auth_key_len;
+ unsigned srtp_auth_tag_len;
+ unsigned srtcp_auth_tag_len;
+ sec_serv_t service;
+} crypto_suite;
+
+/* Crypto suites as defined on RFC 4568 */
+static crypto_suite crypto_suites[] = {
+ /* plain RTP/RTCP (no cipher & no auth) */
+ {"NULL", NULL_CIPHER, 0, NULL_AUTH, 0, 0, 0, sec_serv_none},
+
+ /* cipher AES_CM, auth HMAC_SHA1, auth tag len = 10 octets */
+ {"AES_CM_128_HMAC_SHA1_80", AES_128_ICM, 30, HMAC_SHA1, 20, 10, 10,
+ sec_serv_conf_and_auth},
+
+ /* cipher AES_CM, auth HMAC_SHA1, auth tag len = 4 octets */
+ {"AES_CM_128_HMAC_SHA1_32", AES_128_ICM, 30, HMAC_SHA1, 20, 4, 10,
+ sec_serv_conf_and_auth},
+
+ /*
+ * F8_128_HMAC_SHA1_8 not supported by libsrtp?
+ * {"F8_128_HMAC_SHA1_8", NULL_CIPHER, 0, NULL_AUTH, 0, 0, 0, sec_serv_none}
+ */
+};
+
+typedef struct transport_srtp
+{
+ pjmedia_transport base; /**< Base transport interface. */
+ pj_pool_t *pool; /**< Pool for transport SRTP. */
+ pj_lock_t *mutex; /**< Mutex for libsrtp contexts.*/
+ char rtp_tx_buffer[MAX_RTP_BUFFER_LEN];
+ char rtcp_tx_buffer[MAX_RTCP_BUFFER_LEN];
+ pjmedia_srtp_setting setting;
+ unsigned media_option;
+
+ /* SRTP policy */
+ pj_bool_t session_inited;
+ pj_bool_t offerer_side;
+ pj_bool_t bypass_srtp;
+ char tx_key[MAX_KEY_LEN];
+ char rx_key[MAX_KEY_LEN];
+ pjmedia_srtp_crypto tx_policy;
+ pjmedia_srtp_crypto rx_policy;
+
+ /* Temporary policy for negotiation */
+ pjmedia_srtp_crypto tx_policy_neg;
+ pjmedia_srtp_crypto rx_policy_neg;
+
+ /* libSRTP contexts */
+ srtp_t srtp_tx_ctx;
+ srtp_t srtp_rx_ctx;
+
+ /* Stream information */
+ void *user_data;
+ void (*rtp_cb)( void *user_data,
+ void *pkt,
+ pj_ssize_t size);
+ void (*rtcp_cb)(void *user_data,
+ void *pkt,
+ pj_ssize_t size);
+
+ /* Transport information */
+ pjmedia_transport *member_tp; /**< Underlying transport. */
+
+ /* SRTP usage policy of peer. This field is updated when media is starting.
+ * This is useful when SRTP is in optional mode and peer is using mandatory
+ * mode, so when local is about to reinvite/update, it should offer
+ * RTP/SAVP instead of offering RTP/AVP.
+ */
+ pjmedia_srtp_use peer_use;
+
+ /* When probation counter > 0, it means SRTP is in probation state,
+ * and it may restart when srtp_unprotect() returns err_status_replay_*
+ */
+ unsigned probation_cnt;
+} transport_srtp;
+
+
+/*
+ * This callback is called by transport when incoming rtp is received
+ */
+static void srtp_rtp_cb( void *user_data, void *pkt, pj_ssize_t size);
+
+/*
+ * This callback is called by transport when incoming rtcp is received
+ */
+static void srtp_rtcp_cb( void *user_data, void *pkt, pj_ssize_t size);
+
+
+/*
+ * These are media transport operations.
+ */
+static pj_status_t transport_get_info (pjmedia_transport *tp,
+ pjmedia_transport_info *info);
+static pj_status_t transport_attach (pjmedia_transport *tp,
+ void *user_data,
+ const pj_sockaddr_t *rem_addr,
+ const pj_sockaddr_t *rem_rtcp,
+ unsigned addr_len,
+ void (*rtp_cb)(void*,
+ void*,
+ pj_ssize_t),
+ void (*rtcp_cb)(void*,
+ void*,
+ pj_ssize_t));
+static void transport_detach (pjmedia_transport *tp,
+ void *strm);
+static pj_status_t transport_send_rtp( pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size);
+static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size);
+static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
+ const pj_sockaddr_t *addr,
+ unsigned addr_len,
+ const void *pkt,
+ pj_size_t size);
+static pj_status_t transport_media_create(pjmedia_transport *tp,
+ pj_pool_t *sdp_pool,
+ unsigned options,
+ const pjmedia_sdp_session *sdp_remote,
+ unsigned media_index);
+static pj_status_t transport_encode_sdp(pjmedia_transport *tp,
+ pj_pool_t *sdp_pool,
+ pjmedia_sdp_session *sdp_local,
+ const pjmedia_sdp_session *sdp_remote,
+ unsigned media_index);
+static pj_status_t transport_media_start (pjmedia_transport *tp,
+ pj_pool_t *pool,
+ const pjmedia_sdp_session *sdp_local,
+ const pjmedia_sdp_session *sdp_remote,
+ unsigned media_index);
+static pj_status_t transport_media_stop(pjmedia_transport *tp);
+static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
+ pjmedia_dir dir,
+ unsigned pct_lost);
+static pj_status_t transport_destroy (pjmedia_transport *tp);
+
+
+
+static pjmedia_transport_op transport_srtp_op =
+{
+ &transport_get_info,
+ &transport_attach,
+ &transport_detach,
+ &transport_send_rtp,
+ &transport_send_rtcp,
+ &transport_send_rtcp2,
+ &transport_media_create,
+ &transport_encode_sdp,
+ &transport_media_start,
+ &transport_media_stop,
+ &transport_simulate_lost,
+ &transport_destroy
+};
+
+/* This function may also be used by other module, e.g: pjmedia/errno.c,
+ * it should have C compatible declaration.
+ */
+PJ_BEGIN_DECL
+ const char* get_libsrtp_errstr(int err);
+PJ_END_DECL
+
+const char* get_libsrtp_errstr(int err)
+{
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+ static char *liberr[] = {
+ "ok", /* err_status_ok = 0 */
+ "unspecified failure", /* err_status_fail = 1 */
+ "unsupported parameter", /* err_status_bad_param = 2 */
+ "couldn't allocate memory", /* err_status_alloc_fail = 3 */
+ "couldn't deallocate properly", /* err_status_dealloc_fail = 4 */
+ "couldn't initialize", /* err_status_init_fail = 5 */
+ "can't process as much data as requested",
+ /* err_status_terminus = 6 */
+ "authentication failure", /* err_status_auth_fail = 7 */
+ "cipher failure", /* err_status_cipher_fail = 8 */
+ "replay check failed (bad index)", /* err_status_replay_fail = 9 */
+ "replay check failed (index too old)",
+ /* err_status_replay_old = 10 */
+ "algorithm failed test routine", /* err_status_algo_fail = 11 */
+ "unsupported operation", /* err_status_no_such_op = 12 */
+ "no appropriate context found", /* err_status_no_ctx = 13 */
+ "unable to perform desired validation",
+ /* err_status_cant_check = 14 */
+ "can't use key any more", /* err_status_key_expired = 15 */
+ "error in use of socket", /* err_status_socket_err = 16 */
+ "error in use POSIX signals", /* err_status_signal_err = 17 */
+ "nonce check failed", /* err_status_nonce_bad = 18 */
+ "couldn't read data", /* err_status_read_fail = 19 */
+ "couldn't write data", /* err_status_write_fail = 20 */
+ "error pasring data", /* err_status_parse_err = 21 */
+ "error encoding data", /* err_status_encode_err = 22 */
+ "error while using semaphores", /* err_status_semaphore_err = 23 */
+ "error while using pfkey" /* err_status_pfkey_err = 24 */
+ };
+ if (err >= 0 && err < (int)PJ_ARRAY_SIZE(liberr)) {
+ return liberr[err];
+ } else {
+ static char msg[32];
+ pj_ansi_snprintf(msg, sizeof(msg), "Unknown libsrtp error %d", err);
+ return msg;
+ }
+#else
+ static char msg[32];
+ pj_ansi_snprintf(msg, sizeof(msg), "libsrtp error %d", err);
+ return msg;
+#endif
+}
+
+static pj_bool_t libsrtp_initialized;
+static void pjmedia_srtp_deinit_lib(pjmedia_endpt *endpt);
+
+PJ_DEF(pj_status_t) pjmedia_srtp_init_lib(pjmedia_endpt *endpt)
+{
+ if (libsrtp_initialized == PJ_FALSE) {
+ err_status_t err;
+
+ err = srtp_init();
+ if (err != err_status_ok) {
+ PJ_LOG(4, (THIS_FILE, "Failed to initialize libsrtp: %s",
+ get_libsrtp_errstr(err)));
+ return PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+ }
+
+ if (pjmedia_endpt_atexit(endpt, pjmedia_srtp_deinit_lib) != PJ_SUCCESS)
+ {
+ /* There will be memory leak when it fails to schedule libsrtp
+ * deinitialization, however the memory leak could be harmless,
+ * since in modern OS's memory used by an application is released
+ * when the application terminates.
+ */
+ PJ_LOG(4, (THIS_FILE, "Failed to register libsrtp deinit."));
+ }
+
+ libsrtp_initialized = PJ_TRUE;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static void pjmedia_srtp_deinit_lib(pjmedia_endpt *endpt)
+{
+ err_status_t err;
+
+ /* Note that currently this SRTP init/deinit is not equipped with
+ * reference counter, it should be safe as normally there is only
+ * one single instance of media endpoint and even if it isn't, the
+ * pjmedia_transport_srtp_create() will invoke SRTP init (the only
+ * drawback should be the delay described by #788).
+ */
+
+ PJ_UNUSED_ARG(endpt);
+
+ err = srtp_deinit();
+ if (err != err_status_ok) {
+ PJ_LOG(4, (THIS_FILE, "Failed to deinitialize libsrtp: %s",
+ get_libsrtp_errstr(err)));
+ }
+
+ libsrtp_initialized = PJ_FALSE;
+}
+
+
+static int get_crypto_idx(const pj_str_t* crypto_name)
+{
+ int i;
+ int cs_cnt = sizeof(crypto_suites)/sizeof(crypto_suites[0]);
+
+ /* treat unspecified crypto_name as crypto 'NULL' */
+ if (crypto_name->slen == 0)
+ return 0;
+
+ for (i=0; i<cs_cnt; ++i) {
+ if (!pj_stricmp2(crypto_name, crypto_suites[i].name))
+ return i;
+ }
+
+ return -1;
+}
+
+
+static int srtp_crypto_cmp(const pjmedia_srtp_crypto* c1,
+ const pjmedia_srtp_crypto* c2)
+{
+ int r;
+
+ r = pj_strcmp(&c1->key, &c2->key);
+ if (r != 0)
+ return r;
+
+ r = pj_stricmp(&c1->name, &c2->name);
+ if (r != 0)
+ return r;
+
+ return (c1->flags != c2->flags);
+}
+
+
+static pj_bool_t srtp_crypto_empty(const pjmedia_srtp_crypto* c)
+{
+ return (c->name.slen==0 || c->key.slen==0);
+}
+
+
+PJ_DEF(void) pjmedia_srtp_setting_default(pjmedia_srtp_setting *opt)
+{
+ unsigned i;
+
+ pj_assert(opt);
+
+ pj_bzero(opt, sizeof(pjmedia_srtp_setting));
+ opt->close_member_tp = PJ_TRUE;
+ opt->use = PJMEDIA_SRTP_OPTIONAL;
+
+ /* Copy default crypto-suites, but skip crypto 'NULL' */
+ opt->crypto_count = sizeof(crypto_suites)/sizeof(crypto_suites[0]) - 1;
+ for (i=0; i<opt->crypto_count; ++i)
+ opt->crypto[i].name = pj_str(crypto_suites[i+1].name);
+}
+
+
+/*
+ * Create an SRTP media transport.
+ */
+PJ_DEF(pj_status_t) pjmedia_transport_srtp_create(
+ pjmedia_endpt *endpt,
+ pjmedia_transport *tp,
+ const pjmedia_srtp_setting *opt,
+ pjmedia_transport **p_tp)
+{
+ pj_pool_t *pool;
+ transport_srtp *srtp;
+ pj_status_t status;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(endpt && tp && p_tp, PJ_EINVAL);
+
+ /* Check crypto availability */
+ if (opt && opt->crypto_count == 0 &&
+ opt->use == PJMEDIA_SRTP_MANDATORY)
+ return PJMEDIA_SRTP_ESDPREQCRYPTO;
+
+ /* Check crypto */
+ if (opt && opt->use != PJMEDIA_SRTP_DISABLED) {
+ for (i=0; i < opt->crypto_count; ++i) {
+ int cs_idx = get_crypto_idx(&opt->crypto[i].name);
+
+ /* check crypto name */
+ if (cs_idx == -1)
+ return PJMEDIA_SRTP_ENOTSUPCRYPTO;
+
+ /* check key length */
+ if (opt->crypto[i].key.slen &&
+ opt->crypto[i].key.slen <
+ (pj_ssize_t)crypto_suites[cs_idx].cipher_key_len)
+ return PJMEDIA_SRTP_EINKEYLEN;
+ }
+ }
+
+ /* Init libsrtp. */
+ status = pjmedia_srtp_init_lib(endpt);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pool = pjmedia_endpt_create_pool(endpt, "srtp%p", 1000, 1000);
+ srtp = PJ_POOL_ZALLOC_T(pool, transport_srtp);
+
+ srtp->pool = pool;
+ srtp->session_inited = PJ_FALSE;
+ srtp->bypass_srtp = PJ_FALSE;
+ srtp->probation_cnt = PROBATION_CNT_INIT;
+
+ if (opt) {
+ srtp->setting = *opt;
+ if (opt->use == PJMEDIA_SRTP_DISABLED)
+ srtp->setting.crypto_count = 0;
+
+ for (i=0; i < srtp->setting.crypto_count; ++i) {
+ int cs_idx = get_crypto_idx(&opt->crypto[i].name);
+ pj_str_t tmp_key = opt->crypto[i].key;
+
+ /* re-set crypto */
+ srtp->setting.crypto[i].name = pj_str(crypto_suites[cs_idx].name);
+ /* cut key length */
+ if (tmp_key.slen)
+ tmp_key.slen = crypto_suites[cs_idx].cipher_key_len;
+ pj_strdup(pool, &srtp->setting.crypto[i].key, &tmp_key);
+ }
+ } else {
+ pjmedia_srtp_setting_default(&srtp->setting);
+ }
+
+ status = pj_lock_create_recursive_mutex(pool, pool->obj_name, &srtp->mutex);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(pool);
+ return status;
+ }
+
+ /* Initialize base pjmedia_transport */
+ pj_memcpy(srtp->base.name, pool->obj_name, PJ_MAX_OBJ_NAME);
+ if (tp)
+ srtp->base.type = tp->type;
+ else
+ srtp->base.type = PJMEDIA_TRANSPORT_TYPE_UDP;
+ srtp->base.op = &transport_srtp_op;
+
+ /* Set underlying transport */
+ srtp->member_tp = tp;
+
+ /* Initialize peer's SRTP usage mode. */
+ srtp->peer_use = srtp->setting.use;
+
+ /* Done */
+ *p_tp = &srtp->base;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Initialize and start SRTP session with the given parameters.
+ */
+PJ_DEF(pj_status_t) pjmedia_transport_srtp_start(
+ pjmedia_transport *tp,
+ const pjmedia_srtp_crypto *tx,
+ const pjmedia_srtp_crypto *rx)
+{
+ transport_srtp *srtp = (transport_srtp*) tp;
+ srtp_policy_t tx_;
+ srtp_policy_t rx_;
+ err_status_t err;
+ int cr_tx_idx = 0;
+ int au_tx_idx = 0;
+ int cr_rx_idx = 0;
+ int au_rx_idx = 0;
+ int crypto_suites_cnt;
+ pj_status_t status = PJ_SUCCESS;
+
+ PJ_ASSERT_RETURN(tp && tx && rx, PJ_EINVAL);
+
+ pj_lock_acquire(srtp->mutex);
+
+ if (srtp->session_inited) {
+ pjmedia_transport_srtp_stop(tp);
+ }
+
+ crypto_suites_cnt = sizeof(crypto_suites)/sizeof(crypto_suites[0]);
+
+ /* Get encryption and authentication method */
+ cr_tx_idx = au_tx_idx = get_crypto_idx(&tx->name);
+ if (tx->flags & PJMEDIA_SRTP_NO_ENCRYPTION)
+ cr_tx_idx = 0;
+ if (tx->flags & PJMEDIA_SRTP_NO_AUTHENTICATION)
+ au_tx_idx = 0;
+
+ cr_rx_idx = au_rx_idx = get_crypto_idx(&rx->name);
+ if (rx->flags & PJMEDIA_SRTP_NO_ENCRYPTION)
+ cr_rx_idx = 0;
+ if (rx->flags & PJMEDIA_SRTP_NO_AUTHENTICATION)
+ au_rx_idx = 0;
+
+ /* Check whether the crypto-suite requested is supported */
+ if (cr_tx_idx == -1 || cr_rx_idx == -1 || au_tx_idx == -1 ||
+ au_rx_idx == -1)
+ {
+ status = PJMEDIA_SRTP_ENOTSUPCRYPTO;
+ goto on_return;
+ }
+
+ /* If all options points to 'NULL' method, just bypass SRTP */
+ if (cr_tx_idx == 0 && cr_rx_idx == 0 && au_tx_idx == 0 && au_rx_idx == 0) {
+ srtp->bypass_srtp = PJ_TRUE;
+ goto on_return;
+ }
+
+ /* Check key length */
+ if (tx->key.slen != (pj_ssize_t)crypto_suites[cr_tx_idx].cipher_key_len ||
+ rx->key.slen != (pj_ssize_t)crypto_suites[cr_rx_idx].cipher_key_len)
+ {
+ status = PJMEDIA_SRTP_EINKEYLEN;
+ goto on_return;
+ }
+
+ /* Init transmit direction */
+ pj_bzero(&tx_, sizeof(srtp_policy_t));
+ pj_memmove(srtp->tx_key, tx->key.ptr, tx->key.slen);
+ if (cr_tx_idx && au_tx_idx)
+ tx_.rtp.sec_serv = sec_serv_conf_and_auth;
+ else if (cr_tx_idx)
+ tx_.rtp.sec_serv = sec_serv_conf;
+ else if (au_tx_idx)
+ tx_.rtp.sec_serv = sec_serv_auth;
+ else
+ tx_.rtp.sec_serv = sec_serv_none;
+ tx_.key = (uint8_t*)srtp->tx_key;
+ tx_.ssrc.type = ssrc_any_outbound;
+ tx_.ssrc.value = 0;
+ tx_.rtp.cipher_type = crypto_suites[cr_tx_idx].cipher_type;
+ tx_.rtp.cipher_key_len = crypto_suites[cr_tx_idx].cipher_key_len;
+ tx_.rtp.auth_type = crypto_suites[au_tx_idx].auth_type;
+ tx_.rtp.auth_key_len = crypto_suites[au_tx_idx].auth_key_len;
+ tx_.rtp.auth_tag_len = crypto_suites[au_tx_idx].srtp_auth_tag_len;
+ tx_.rtcp = tx_.rtp;
+ tx_.rtcp.auth_tag_len = crypto_suites[au_tx_idx].srtcp_auth_tag_len;
+ tx_.next = NULL;
+ err = srtp_create(&srtp->srtp_tx_ctx, &tx_);
+ if (err != err_status_ok) {
+ status = PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+ goto on_return;
+ }
+ srtp->tx_policy = *tx;
+ pj_strset(&srtp->tx_policy.key, srtp->tx_key, tx->key.slen);
+ srtp->tx_policy.name=pj_str(crypto_suites[get_crypto_idx(&tx->name)].name);
+
+
+ /* Init receive direction */
+ pj_bzero(&rx_, sizeof(srtp_policy_t));
+ pj_memmove(srtp->rx_key, rx->key.ptr, rx->key.slen);
+ if (cr_rx_idx && au_rx_idx)
+ rx_.rtp.sec_serv = sec_serv_conf_and_auth;
+ else if (cr_rx_idx)
+ rx_.rtp.sec_serv = sec_serv_conf;
+ else if (au_rx_idx)
+ rx_.rtp.sec_serv = sec_serv_auth;
+ else
+ rx_.rtp.sec_serv = sec_serv_none;
+ rx_.key = (uint8_t*)srtp->rx_key;
+ rx_.ssrc.type = ssrc_any_inbound;
+ rx_.ssrc.value = 0;
+ rx_.rtp.sec_serv = crypto_suites[cr_rx_idx].service;
+ rx_.rtp.cipher_type = crypto_suites[cr_rx_idx].cipher_type;
+ rx_.rtp.cipher_key_len = crypto_suites[cr_rx_idx].cipher_key_len;
+ rx_.rtp.auth_type = crypto_suites[au_rx_idx].auth_type;
+ rx_.rtp.auth_key_len = crypto_suites[au_rx_idx].auth_key_len;
+ rx_.rtp.auth_tag_len = crypto_suites[au_rx_idx].srtp_auth_tag_len;
+ rx_.rtcp = rx_.rtp;
+ rx_.rtcp.auth_tag_len = crypto_suites[au_rx_idx].srtcp_auth_tag_len;
+ rx_.next = NULL;
+ err = srtp_create(&srtp->srtp_rx_ctx, &rx_);
+ if (err != err_status_ok) {
+ srtp_dealloc(srtp->srtp_tx_ctx);
+ status = PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+ goto on_return;
+ }
+ srtp->rx_policy = *rx;
+ pj_strset(&srtp->rx_policy.key, srtp->rx_key, rx->key.slen);
+ srtp->rx_policy.name=pj_str(crypto_suites[get_crypto_idx(&rx->name)].name);
+
+ /* Declare SRTP session initialized */
+ srtp->session_inited = PJ_TRUE;
+
+ PJ_LOG(5, (srtp->pool->obj_name, "TX: %s key=%s", srtp->tx_policy.name.ptr,
+ octet_string_hex_string(tx->key.ptr, tx->key.slen)));
+ if (srtp->tx_policy.flags) {
+ PJ_LOG(5,(srtp->pool->obj_name,"TX: disable%s%s", (cr_tx_idx?"":" enc"),
+ (au_tx_idx?"":" auth")));
+ }
+
+ PJ_LOG(5, (srtp->pool->obj_name, "RX: %s key=%s", srtp->rx_policy.name.ptr,
+ octet_string_hex_string(rx->key.ptr, rx->key.slen)));
+ if (srtp->rx_policy.flags) {
+ PJ_LOG(5,(srtp->pool->obj_name,"RX: disable%s%s", (cr_rx_idx?"":" enc"),
+ (au_rx_idx?"":" auth")));
+ }
+
+on_return:
+ pj_lock_release(srtp->mutex);
+ return status;
+}
+
+/*
+ * Stop SRTP session.
+ */
+PJ_DEF(pj_status_t) pjmedia_transport_srtp_stop(pjmedia_transport *srtp)
+{
+ transport_srtp *p_srtp = (transport_srtp*) srtp;
+ err_status_t err;
+
+ PJ_ASSERT_RETURN(srtp, PJ_EINVAL);
+
+ pj_lock_acquire(p_srtp->mutex);
+
+ if (!p_srtp->session_inited) {
+ pj_lock_release(p_srtp->mutex);
+ return PJ_SUCCESS;
+ }
+
+ err = srtp_dealloc(p_srtp->srtp_rx_ctx);
+ if (err != err_status_ok) {
+ PJ_LOG(4, (p_srtp->pool->obj_name,
+ "Failed to dealloc RX SRTP context: %s",
+ get_libsrtp_errstr(err)));
+ }
+ err = srtp_dealloc(p_srtp->srtp_tx_ctx);
+ if (err != err_status_ok) {
+ PJ_LOG(4, (p_srtp->pool->obj_name,
+ "Failed to dealloc TX SRTP context: %s",
+ get_libsrtp_errstr(err)));
+ }
+
+ p_srtp->session_inited = PJ_FALSE;
+ pj_bzero(&p_srtp->rx_policy, sizeof(p_srtp->rx_policy));
+ pj_bzero(&p_srtp->tx_policy, sizeof(p_srtp->tx_policy));
+
+ pj_lock_release(p_srtp->mutex);
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pjmedia_transport *) pjmedia_transport_srtp_get_member(
+ pjmedia_transport *tp)
+{
+ transport_srtp *srtp = (transport_srtp*) tp;
+
+ PJ_ASSERT_RETURN(tp, NULL);
+
+ return srtp->member_tp;
+}
+
+
+static pj_status_t transport_get_info(pjmedia_transport *tp,
+ pjmedia_transport_info *info)
+{
+ transport_srtp *srtp = (transport_srtp*) tp;
+ pjmedia_srtp_info srtp_info;
+ int spc_info_idx;
+
+ PJ_ASSERT_RETURN(tp && info, PJ_EINVAL);
+ PJ_ASSERT_RETURN(info->specific_info_cnt <
+ PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXCNT, PJ_ETOOMANY);
+ PJ_ASSERT_RETURN(sizeof(pjmedia_srtp_info) <=
+ PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXSIZE, PJ_ENOMEM);
+
+ srtp_info.active = srtp->session_inited;
+ srtp_info.rx_policy = srtp->rx_policy;
+ srtp_info.tx_policy = srtp->tx_policy;
+ srtp_info.use = srtp->setting.use;
+ srtp_info.peer_use = srtp->peer_use;
+
+ spc_info_idx = info->specific_info_cnt++;
+ info->spc_info[spc_info_idx].type = PJMEDIA_TRANSPORT_TYPE_SRTP;
+ info->spc_info[spc_info_idx].cbsize = sizeof(srtp_info);
+ pj_memcpy(&info->spc_info[spc_info_idx].buffer, &srtp_info,
+ sizeof(srtp_info));
+
+ return pjmedia_transport_get_info(srtp->member_tp, info);
+}
+
+static pj_status_t transport_attach(pjmedia_transport *tp,
+ void *user_data,
+ const pj_sockaddr_t *rem_addr,
+ const pj_sockaddr_t *rem_rtcp,
+ unsigned addr_len,
+ void (*rtp_cb) (void*, void*,
+ pj_ssize_t),
+ void (*rtcp_cb)(void*, void*,
+ pj_ssize_t))
+{
+ transport_srtp *srtp = (transport_srtp*) tp;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(tp && rem_addr && addr_len, PJ_EINVAL);
+
+ /* Save the callbacks */
+ pj_lock_acquire(srtp->mutex);
+ srtp->rtp_cb = rtp_cb;
+ srtp->rtcp_cb = rtcp_cb;
+ srtp->user_data = user_data;
+ pj_lock_release(srtp->mutex);
+
+ /* Attach itself to transport */
+ status = pjmedia_transport_attach(srtp->member_tp, srtp, rem_addr,
+ rem_rtcp, addr_len, &srtp_rtp_cb,
+ &srtp_rtcp_cb);
+ if (status != PJ_SUCCESS) {
+ pj_lock_acquire(srtp->mutex);
+ srtp->rtp_cb = NULL;
+ srtp->rtcp_cb = NULL;
+ srtp->user_data = NULL;
+ pj_lock_release(srtp->mutex);
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static void transport_detach(pjmedia_transport *tp, void *strm)
+{
+ transport_srtp *srtp = (transport_srtp*) tp;
+
+ PJ_UNUSED_ARG(strm);
+ PJ_ASSERT_ON_FAIL(tp, return);
+
+ if (srtp->member_tp) {
+ pjmedia_transport_detach(srtp->member_tp, srtp);
+ }
+
+ /* Clear up application infos from transport */
+ pj_lock_acquire(srtp->mutex);
+ srtp->rtp_cb = NULL;
+ srtp->rtcp_cb = NULL;
+ srtp->user_data = NULL;
+ pj_lock_release(srtp->mutex);
+}
+
+static pj_status_t transport_send_rtp( pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size)
+{
+ pj_status_t status;
+ transport_srtp *srtp = (transport_srtp*) tp;
+ int len = size;
+ err_status_t err;
+
+ if (srtp->bypass_srtp)
+ return pjmedia_transport_send_rtp(srtp->member_tp, pkt, size);
+
+ if (size > sizeof(srtp->rtp_tx_buffer))
+ return PJ_ETOOBIG;
+
+ pj_memcpy(srtp->rtp_tx_buffer, pkt, size);
+
+ pj_lock_acquire(srtp->mutex);
+ if (!srtp->session_inited) {
+ pj_lock_release(srtp->mutex);
+ return PJ_EINVALIDOP;
+ }
+ err = srtp_protect(srtp->srtp_tx_ctx, srtp->rtp_tx_buffer, &len);
+ pj_lock_release(srtp->mutex);
+
+ if (err == err_status_ok) {
+ status = pjmedia_transport_send_rtp(srtp->member_tp, srtp->rtp_tx_buffer, len);
+ } else {
+ status = PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+ }
+
+ return status;
+}
+
+static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size)
+{
+ return transport_send_rtcp2(tp, NULL, 0, pkt, size);
+}
+
+static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
+ const pj_sockaddr_t *addr,
+ unsigned addr_len,
+ const void *pkt,
+ pj_size_t size)
+{
+ pj_status_t status;
+ transport_srtp *srtp = (transport_srtp*) tp;
+ int len = size;
+ err_status_t err;
+
+ if (srtp->bypass_srtp) {
+ return pjmedia_transport_send_rtcp2(srtp->member_tp, addr, addr_len,
+ pkt, size);
+ }
+
+ if (size > sizeof(srtp->rtcp_tx_buffer))
+ return PJ_ETOOBIG;
+
+ pj_memcpy(srtp->rtcp_tx_buffer, pkt, size);
+
+ pj_lock_acquire(srtp->mutex);
+ if (!srtp->session_inited) {
+ pj_lock_release(srtp->mutex);
+ return PJ_EINVALIDOP;
+ }
+ err = srtp_protect_rtcp(srtp->srtp_tx_ctx, srtp->rtcp_tx_buffer, &len);
+ pj_lock_release(srtp->mutex);
+
+ if (err == err_status_ok) {
+ status = pjmedia_transport_send_rtcp2(srtp->member_tp, addr, addr_len,
+ srtp->rtcp_tx_buffer, len);
+ } else {
+ status = PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+ }
+
+ return status;
+}
+
+
+static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
+ pjmedia_dir dir,
+ unsigned pct_lost)
+{
+ transport_srtp *srtp = (transport_srtp *) tp;
+
+ PJ_ASSERT_RETURN(tp, PJ_EINVAL);
+
+ return pjmedia_transport_simulate_lost(srtp->member_tp, dir, pct_lost);
+}
+
+static pj_status_t transport_destroy (pjmedia_transport *tp)
+{
+ transport_srtp *srtp = (transport_srtp *) tp;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(tp, PJ_EINVAL);
+
+ if (srtp->setting.close_member_tp && srtp->member_tp) {
+ pjmedia_transport_close(srtp->member_tp);
+ }
+
+ status = pjmedia_transport_srtp_stop(tp);
+
+ /* In case mutex is being acquired by other thread */
+ pj_lock_acquire(srtp->mutex);
+ pj_lock_release(srtp->mutex);
+
+ pj_lock_destroy(srtp->mutex);
+ pj_pool_release(srtp->pool);
+
+ return status;
+}
+
+/*
+ * This callback is called by transport when incoming rtp is received
+ */
+static void srtp_rtp_cb( void *user_data, void *pkt, pj_ssize_t size)
+{
+ transport_srtp *srtp = (transport_srtp *) user_data;
+ int len = size;
+ err_status_t err;
+ void (*cb)(void*, void*, pj_ssize_t) = NULL;
+ void *cb_data = NULL;
+
+ if (srtp->bypass_srtp) {
+ srtp->rtp_cb(srtp->user_data, pkt, size);
+ return;
+ }
+
+ if (size < 0) {
+ return;
+ }
+
+ /* Make sure buffer is 32bit aligned */
+ PJ_ASSERT_ON_FAIL( (((long)pkt) & 0x03)==0, return );
+
+ if (srtp->probation_cnt > 0)
+ --srtp->probation_cnt;
+
+ pj_lock_acquire(srtp->mutex);
+
+ if (!srtp->session_inited) {
+ pj_lock_release(srtp->mutex);
+ return;
+ }
+ err = srtp_unprotect(srtp->srtp_rx_ctx, (pj_uint8_t*)pkt, &len);
+ if (srtp->probation_cnt > 0 &&
+ (err == err_status_replay_old || err == err_status_replay_fail))
+ {
+ /* Handle such condition that stream is updated (RTP seq is reinited
+ * & SRTP is restarted), but some old packets are still coming
+ * so SRTP is learning wrong RTP seq. While the newly inited RTP seq
+ * comes, SRTP thinks the RTP seq is replayed, so srtp_unprotect()
+ * will return err_status_replay_*. Restarting SRTP can resolve this.
+ */
+ pjmedia_srtp_crypto tx, rx;
+ pj_status_t status;
+
+ tx = srtp->tx_policy;
+ rx = srtp->rx_policy;
+ status = pjmedia_transport_srtp_start((pjmedia_transport*)srtp,
+ &tx, &rx);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(5,(srtp->pool->obj_name, "Failed to restart SRTP, err=%s",
+ get_libsrtp_errstr(err)));
+ } else if (!srtp->bypass_srtp) {
+ err = srtp_unprotect(srtp->srtp_rx_ctx, (pj_uint8_t*)pkt, &len);
+ }
+ }
+
+ if (err != err_status_ok) {
+ PJ_LOG(5,(srtp->pool->obj_name,
+ "Failed to unprotect SRTP, pkt size=%d, err=%s",
+ size, get_libsrtp_errstr(err)));
+ } else {
+ cb = srtp->rtp_cb;
+ cb_data = srtp->user_data;
+ }
+
+ pj_lock_release(srtp->mutex);
+
+ if (cb) {
+ (*cb)(cb_data, pkt, len);
+ }
+}
+
+/*
+ * This callback is called by transport when incoming rtcp is received
+ */
+static void srtp_rtcp_cb( void *user_data, void *pkt, pj_ssize_t size)
+{
+ transport_srtp *srtp = (transport_srtp *) user_data;
+ int len = size;
+ err_status_t err;
+ void (*cb)(void*, void*, pj_ssize_t) = NULL;
+ void *cb_data = NULL;
+
+ if (srtp->bypass_srtp) {
+ srtp->rtcp_cb(srtp->user_data, pkt, size);
+ return;
+ }
+
+ if (size < 0) {
+ return;
+ }
+
+ /* Make sure buffer is 32bit aligned */
+ PJ_ASSERT_ON_FAIL( (((long)pkt) & 0x03)==0, return );
+
+ pj_lock_acquire(srtp->mutex);
+
+ if (!srtp->session_inited) {
+ pj_lock_release(srtp->mutex);
+ return;
+ }
+ err = srtp_unprotect_rtcp(srtp->srtp_rx_ctx, (pj_uint8_t*)pkt, &len);
+ if (err != err_status_ok) {
+ PJ_LOG(5,(srtp->pool->obj_name,
+ "Failed to unprotect SRTCP, pkt size=%d, err=%s",
+ size, get_libsrtp_errstr(err)));
+ } else {
+ cb = srtp->rtcp_cb;
+ cb_data = srtp->user_data;
+ }
+
+ pj_lock_release(srtp->mutex);
+
+ if (cb) {
+ (*cb)(cb_data, pkt, len);
+ }
+}
+
+/* Generate crypto attribute, including crypto key.
+ * If crypto-suite chosen is crypto NULL, just return PJ_SUCCESS,
+ * and set buffer_len = 0.
+ */
+static pj_status_t generate_crypto_attr_value(pj_pool_t *pool,
+ char *buffer, int *buffer_len,
+ pjmedia_srtp_crypto *crypto,
+ int tag)
+{
+ pj_status_t status;
+ int cs_idx = get_crypto_idx(&crypto->name);
+ char b64_key[PJ_BASE256_TO_BASE64_LEN(MAX_KEY_LEN)+1];
+ int b64_key_len = sizeof(b64_key);
+
+ if (cs_idx == -1)
+ return PJMEDIA_SRTP_ENOTSUPCRYPTO;
+
+ /* Crypto-suite NULL. */
+ if (cs_idx == 0) {
+ *buffer_len = 0;
+ return PJ_SUCCESS;
+ }
+
+ /* Generate key if not specified. */
+ if (crypto->key.slen == 0) {
+ pj_bool_t key_ok;
+ char key[MAX_KEY_LEN];
+ err_status_t err;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(MAX_KEY_LEN >= crypto_suites[cs_idx].cipher_key_len,
+ PJ_ETOOSMALL);
+
+ do {
+ key_ok = PJ_TRUE;
+
+ err = crypto_get_random((unsigned char*)key,
+ crypto_suites[cs_idx].cipher_key_len);
+ if (err != err_status_ok) {
+ PJ_LOG(5,(THIS_FILE, "Failed generating random key: %s",
+ get_libsrtp_errstr(err)));
+ return PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+ }
+ for (i=0; i<crypto_suites[cs_idx].cipher_key_len && key_ok; ++i)
+ if (key[i] == 0) key_ok = PJ_FALSE;
+
+ } while (!key_ok);
+ crypto->key.ptr = (char*)
+ pj_pool_zalloc(pool,
+ crypto_suites[cs_idx].cipher_key_len);
+ pj_memcpy(crypto->key.ptr, key, crypto_suites[cs_idx].cipher_key_len);
+ crypto->key.slen = crypto_suites[cs_idx].cipher_key_len;
+ }
+
+ if (crypto->key.slen != (pj_ssize_t)crypto_suites[cs_idx].cipher_key_len)
+ return PJMEDIA_SRTP_EINKEYLEN;
+
+ /* Key transmitted via SDP should be base64 encoded. */
+ status = pj_base64_encode((pj_uint8_t*)crypto->key.ptr, crypto->key.slen,
+ b64_key, &b64_key_len);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(5,(THIS_FILE, "Failed encoding plain key to base64"));
+ return status;
+ }
+
+ b64_key[b64_key_len] = '\0';
+
+ PJ_ASSERT_RETURN(*buffer_len >= (crypto->name.slen + \
+ b64_key_len + 16), PJ_ETOOSMALL);
+
+ /* Print the crypto attribute value. */
+ *buffer_len = pj_ansi_snprintf(buffer, *buffer_len, "%d %s inline:%s",
+ tag,
+ crypto_suites[cs_idx].name,
+ b64_key);
+
+ return PJ_SUCCESS;
+}
+
+/* Parse crypto attribute line */
+static pj_status_t parse_attr_crypto(pj_pool_t *pool,
+ const pjmedia_sdp_attr *attr,
+ pjmedia_srtp_crypto *crypto,
+ int *tag)
+{
+ pj_str_t input;
+ char *token;
+ int token_len;
+ pj_str_t tmp;
+ pj_status_t status;
+ int itmp;
+
+ pj_bzero(crypto, sizeof(*crypto));
+ pj_strdup_with_null(pool, &input, &attr->value);
+
+ /* Tag */
+ token = strtok(input.ptr, " ");
+ if (!token) {
+ PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting tag"));
+ return PJMEDIA_SDP_EINATTR;
+ }
+ token_len = pj_ansi_strlen(token);
+
+ /* Tag must not use leading zeroes. */
+ if (token_len > 1 && *token == '0')
+ return PJMEDIA_SDP_EINATTR;
+
+ /* Tag must be decimal, i.e: contains only digit '0'-'9'. */
+ for (itmp = 0; itmp < token_len; ++itmp)
+ if (!pj_isdigit(token[itmp]))
+ return PJMEDIA_SDP_EINATTR;
+
+ /* Get tag value. */
+ *tag = atoi(token);
+
+ /* Crypto-suite */
+ token = strtok(NULL, " ");
+ if (!token) {
+ PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting crypto suite"));
+ return PJMEDIA_SDP_EINATTR;
+ }
+ crypto->name = pj_str(token);
+
+ /* Key method */
+ token = strtok(NULL, ":");
+ if (!token) {
+ PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting key method"));
+ return PJMEDIA_SDP_EINATTR;
+ }
+ if (pj_ansi_stricmp(token, "inline")) {
+ PJ_LOG(4,(THIS_FILE, "Attribute crypto key method '%s' not supported!",
+ token));
+ return PJMEDIA_SDP_EINATTR;
+ }
+
+ /* Key */
+ token = strtok(NULL, "| ");
+ if (!token) {
+ PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting key"));
+ return PJMEDIA_SDP_EINATTR;
+ }
+ tmp = pj_str(token);
+ if (PJ_BASE64_TO_BASE256_LEN(tmp.slen) > MAX_KEY_LEN) {
+ PJ_LOG(4,(THIS_FILE, "Key too long"));
+ return PJMEDIA_SRTP_EINKEYLEN;
+ }
+
+ /* Decode key */
+ crypto->key.ptr = (char*) pj_pool_zalloc(pool, MAX_KEY_LEN);
+ itmp = MAX_KEY_LEN;
+ status = pj_base64_decode(&tmp, (pj_uint8_t*)crypto->key.ptr,
+ &itmp);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(4,(THIS_FILE, "Failed decoding crypto key from base64"));
+ return status;
+ }
+ crypto->key.slen = itmp;
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t transport_media_create(pjmedia_transport *tp,
+ pj_pool_t *sdp_pool,
+ unsigned options,
+ const pjmedia_sdp_session *sdp_remote,
+ unsigned media_index)
+{
+ struct transport_srtp *srtp = (struct transport_srtp*) tp;
+ unsigned member_tp_option;
+
+ PJ_ASSERT_RETURN(tp, PJ_EINVAL);
+
+ pj_bzero(&srtp->rx_policy_neg, sizeof(srtp->rx_policy_neg));
+ pj_bzero(&srtp->tx_policy_neg, sizeof(srtp->tx_policy_neg));
+
+ srtp->media_option = options;
+ member_tp_option = options | PJMEDIA_TPMED_NO_TRANSPORT_CHECKING;
+
+ srtp->offerer_side = sdp_remote == NULL;
+
+ /* Validations */
+ if (srtp->offerer_side) {
+
+ if (srtp->setting.use == PJMEDIA_SRTP_DISABLED)
+ goto BYPASS_SRTP;
+
+ } else {
+
+ pjmedia_sdp_media *m_rem;
+
+ m_rem = sdp_remote->media[media_index];
+
+ /* Nothing to do on inactive media stream */
+ if (pjmedia_sdp_media_find_attr(m_rem, &ID_INACTIVE, NULL))
+ goto BYPASS_SRTP;
+
+ /* Validate remote media transport based on SRTP usage option.
+ */
+ switch (srtp->setting.use) {
+ case PJMEDIA_SRTP_DISABLED:
+ if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) == 0)
+ return PJMEDIA_SRTP_ESDPINTRANSPORT;
+ goto BYPASS_SRTP;
+ case PJMEDIA_SRTP_OPTIONAL:
+ break;
+ case PJMEDIA_SRTP_MANDATORY:
+ if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) != 0)
+ return PJMEDIA_SRTP_ESDPINTRANSPORT;
+ break;
+ }
+
+ }
+ goto PROPAGATE_MEDIA_CREATE;
+
+BYPASS_SRTP:
+ srtp->bypass_srtp = PJ_TRUE;
+ member_tp_option &= ~PJMEDIA_TPMED_NO_TRANSPORT_CHECKING;
+
+PROPAGATE_MEDIA_CREATE:
+ return pjmedia_transport_media_create(srtp->member_tp, sdp_pool,
+ member_tp_option, sdp_remote,
+ media_index);
+}
+
+static pj_status_t transport_encode_sdp(pjmedia_transport *tp,
+ pj_pool_t *sdp_pool,
+ pjmedia_sdp_session *sdp_local,
+ const pjmedia_sdp_session *sdp_remote,
+ unsigned media_index)
+{
+ struct transport_srtp *srtp = (struct transport_srtp*) tp;
+ pjmedia_sdp_media *m_rem, *m_loc;
+ enum { MAXLEN = 512 };
+ char buffer[MAXLEN];
+ int buffer_len;
+ pj_status_t status;
+ pjmedia_sdp_attr *attr;
+ pj_str_t attr_value;
+ unsigned i, j;
+
+ PJ_ASSERT_RETURN(tp && sdp_pool && sdp_local, PJ_EINVAL);
+
+ pj_bzero(&srtp->rx_policy_neg, sizeof(srtp->rx_policy_neg));
+ pj_bzero(&srtp->tx_policy_neg, sizeof(srtp->tx_policy_neg));
+
+ srtp->offerer_side = sdp_remote == NULL;
+
+ m_rem = sdp_remote ? sdp_remote->media[media_index] : NULL;
+ m_loc = sdp_local->media[media_index];
+
+ /* Bypass if media transport is not RTP/AVP or RTP/SAVP */
+ if (pj_stricmp(&m_loc->desc.transport, &ID_RTP_AVP) != 0 &&
+ pj_stricmp(&m_loc->desc.transport, &ID_RTP_SAVP) != 0)
+ goto BYPASS_SRTP;
+
+ /* If the media is inactive, do nothing. */
+ /* No, we still need to process SRTP offer/answer even if the media is
+ * marked as inactive, because the transport is still alive in this
+ * case (e.g. for keep-alive). See:
+ * http://trac.pjsip.org/repos/ticket/1079
+ */
+ /*
+ if (pjmedia_sdp_media_find_attr(m_loc, &ID_INACTIVE, NULL) ||
+ (m_rem && pjmedia_sdp_media_find_attr(m_rem, &ID_INACTIVE, NULL)))
+ goto BYPASS_SRTP;
+ */
+
+ /* Check remote media transport & set local media transport
+ * based on SRTP usage option.
+ */
+ if (srtp->offerer_side) {
+
+ /* Generate transport */
+ switch (srtp->setting.use) {
+ case PJMEDIA_SRTP_DISABLED:
+ goto BYPASS_SRTP;
+ case PJMEDIA_SRTP_OPTIONAL:
+ m_loc->desc.transport =
+ (srtp->peer_use == PJMEDIA_SRTP_MANDATORY)?
+ ID_RTP_SAVP : ID_RTP_AVP;
+ break;
+ case PJMEDIA_SRTP_MANDATORY:
+ m_loc->desc.transport = ID_RTP_SAVP;
+ break;
+ }
+
+ /* Generate crypto attribute if not yet */
+ if (pjmedia_sdp_media_find_attr(m_loc, &ID_CRYPTO, NULL) == NULL) {
+ for (i=0; i<srtp->setting.crypto_count; ++i) {
+ /* Offer crypto-suites based on setting. */
+ buffer_len = MAXLEN;
+ status = generate_crypto_attr_value(srtp->pool, buffer, &buffer_len,
+ &srtp->setting.crypto[i],
+ i+1);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* If buffer_len==0, just skip the crypto attribute. */
+ if (buffer_len) {
+ pj_strset(&attr_value, buffer, buffer_len);
+ attr = pjmedia_sdp_attr_create(srtp->pool, ID_CRYPTO.ptr,
+ &attr_value);
+ m_loc->attr[m_loc->attr_count++] = attr;
+ }
+ }
+ }
+
+ } else {
+ /* Answerer side */
+
+ pj_assert(sdp_remote && m_rem);
+
+ /* Generate transport */
+ switch (srtp->setting.use) {
+ case PJMEDIA_SRTP_DISABLED:
+ if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) == 0)
+ return PJMEDIA_SRTP_ESDPINTRANSPORT;
+ goto BYPASS_SRTP;
+ case PJMEDIA_SRTP_OPTIONAL:
+ m_loc->desc.transport = m_rem->desc.transport;
+ break;
+ case PJMEDIA_SRTP_MANDATORY:
+ if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) != 0)
+ return PJMEDIA_SRTP_ESDPINTRANSPORT;
+ m_loc->desc.transport = ID_RTP_SAVP;
+ break;
+ }
+
+ /* Generate crypto attribute if not yet */
+ if (pjmedia_sdp_media_find_attr(m_loc, &ID_CRYPTO, NULL) == NULL) {
+
+ pjmedia_srtp_crypto tmp_rx_crypto;
+ pj_bool_t has_crypto_attr = PJ_FALSE;
+ int matched_idx = -1;
+ int chosen_tag = 0;
+ int tags[64]; /* assume no more than 64 crypto attrs in a media */
+ unsigned cr_attr_count = 0;
+
+ /* Find supported crypto-suite, get the tag, and assign policy_local */
+ for (i=0; i<m_rem->attr_count; ++i) {
+ if (pj_stricmp(&m_rem->attr[i]->name, &ID_CRYPTO) != 0)
+ continue;
+
+ has_crypto_attr = PJ_TRUE;
+
+ status = parse_attr_crypto(srtp->pool, m_rem->attr[i],
+ &tmp_rx_crypto, &tags[cr_attr_count]);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Check duplicated tag */
+ for (j=0; j<cr_attr_count; ++j) {
+ if (tags[j] == tags[cr_attr_count]) {
+ DEACTIVATE_MEDIA(sdp_pool, m_loc);
+ return PJMEDIA_SRTP_ESDPDUPCRYPTOTAG;
+ }
+ }
+
+ if (matched_idx == -1) {
+ /* lets see if the crypto-suite offered is supported */
+ for (j=0; j<srtp->setting.crypto_count; ++j)
+ if (pj_stricmp(&tmp_rx_crypto.name,
+ &srtp->setting.crypto[j].name) == 0)
+ {
+ int cs_idx = get_crypto_idx(&tmp_rx_crypto.name);
+
+ /* Force to use test key */
+ /* bad keys for snom: */
+ //char *hex_test_key = "58b29c5c8f42308120ce857e439f2d"
+ // "7810a8b10ad0b1446be5470faea496";
+ //char *hex_test_key = "20a26aac7ba062d356ff52b61e3993"
+ // "ccb78078f12c64db94b9c294927fd0";
+ //pj_str_t *test_key = &srtp->setting.crypto[j].key;
+ //char *raw_test_key = pj_pool_zalloc(srtp->pool, 64);
+ //hex_string_to_octet_string(
+ // raw_test_key,
+ // hex_test_key,
+ // strlen(hex_test_key));
+ //pj_strset(test_key, raw_test_key,
+ // crypto_suites[cs_idx].cipher_key_len);
+ /* EO Force to use test key */
+
+ if (tmp_rx_crypto.key.slen !=
+ (int)crypto_suites[cs_idx].cipher_key_len)
+ return PJMEDIA_SRTP_EINKEYLEN;
+
+ srtp->rx_policy_neg = tmp_rx_crypto;
+ chosen_tag = tags[cr_attr_count];
+ matched_idx = j;
+ break;
+ }
+ }
+ cr_attr_count++;
+ }
+
+ /* Check crypto negotiation result */
+ switch (srtp->setting.use) {
+ case PJMEDIA_SRTP_DISABLED:
+ pj_assert(!"Should never reach here");
+ break;
+
+ case PJMEDIA_SRTP_OPTIONAL:
+ /* bypass SRTP when no crypto-attr and remote uses RTP/AVP */
+ if (!has_crypto_attr &&
+ pj_stricmp(&m_rem->desc.transport, &ID_RTP_AVP) == 0)
+ goto BYPASS_SRTP;
+ /* bypass SRTP when nothing match and remote uses RTP/AVP */
+ else if (matched_idx == -1 &&
+ pj_stricmp(&m_rem->desc.transport, &ID_RTP_AVP) == 0)
+ goto BYPASS_SRTP;
+ break;
+
+ case PJMEDIA_SRTP_MANDATORY:
+ /* Do nothing, intentional */
+ break;
+ }
+
+ /* No crypto attr */
+ if (!has_crypto_attr) {
+ DEACTIVATE_MEDIA(sdp_pool, m_loc);
+ return PJMEDIA_SRTP_ESDPREQCRYPTO;
+ }
+
+ /* No crypto match */
+ if (matched_idx == -1) {
+ DEACTIVATE_MEDIA(sdp_pool, m_loc);
+ return PJMEDIA_SRTP_ENOTSUPCRYPTO;
+ }
+
+ /* we have to generate crypto answer,
+ * with srtp->tx_policy_neg matched the offer
+ * and rem_tag contains matched offer tag.
+ */
+ buffer_len = MAXLEN;
+ status = generate_crypto_attr_value(srtp->pool, buffer, &buffer_len,
+ &srtp->setting.crypto[matched_idx],
+ chosen_tag);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ srtp->tx_policy_neg = srtp->setting.crypto[matched_idx];
+
+ /* If buffer_len==0, just skip the crypto attribute. */
+ if (buffer_len) {
+ pj_strset(&attr_value, buffer, buffer_len);
+ attr = pjmedia_sdp_attr_create(sdp_pool, ID_CRYPTO.ptr,
+ &attr_value);
+ m_loc->attr[m_loc->attr_count++] = attr;
+ }
+
+ /* At this point, we get valid rx_policy_neg & tx_policy_neg. */
+ }
+
+ }
+ goto PROPAGATE_MEDIA_CREATE;
+
+BYPASS_SRTP:
+ /* Do not update this flag here as actually the media session hasn't been
+ * updated.
+ */
+ //srtp->bypass_srtp = PJ_TRUE;
+
+PROPAGATE_MEDIA_CREATE:
+ return pjmedia_transport_encode_sdp(srtp->member_tp, sdp_pool,
+ sdp_local, sdp_remote, media_index);
+}
+
+
+
+static pj_status_t transport_media_start(pjmedia_transport *tp,
+ pj_pool_t *pool,
+ const pjmedia_sdp_session *sdp_local,
+ const pjmedia_sdp_session *sdp_remote,
+ unsigned media_index)
+{
+ struct transport_srtp *srtp = (struct transport_srtp*) tp;
+ pjmedia_sdp_media *m_rem, *m_loc;
+ pj_status_t status;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(tp && pool && sdp_local && sdp_remote, PJ_EINVAL);
+
+ m_rem = sdp_remote->media[media_index];
+ m_loc = sdp_local->media[media_index];
+
+ if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) == 0)
+ srtp->peer_use = PJMEDIA_SRTP_MANDATORY;
+ else
+ srtp->peer_use = PJMEDIA_SRTP_OPTIONAL;
+
+ /* For answerer side, this function will just have to start SRTP */
+
+ /* Check remote media transport & set local media transport
+ * based on SRTP usage option.
+ */
+ if (srtp->offerer_side) {
+ if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) {
+ if (pjmedia_sdp_media_find_attr(m_rem, &ID_CRYPTO, NULL)) {
+ DEACTIVATE_MEDIA(pool, m_loc);
+ return PJMEDIA_SRTP_ESDPINCRYPTO;
+ }
+ goto BYPASS_SRTP;
+ } else if (srtp->setting.use == PJMEDIA_SRTP_OPTIONAL) {
+ // Regardless the answer's transport type (RTP/AVP or RTP/SAVP),
+ // the answer must be processed through in optional mode.
+ // Please note that at this point transport type is ensured to be
+ // RTP/AVP or RTP/SAVP, see transport_media_create()
+ //if (pj_stricmp(&m_rem->desc.transport, &m_loc->desc.transport)) {
+ //DEACTIVATE_MEDIA(pool, m_loc);
+ //return PJMEDIA_SDP_EINPROTO;
+ //}
+ } else if (srtp->setting.use == PJMEDIA_SRTP_MANDATORY) {
+ if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP)) {
+ DEACTIVATE_MEDIA(pool, m_loc);
+ return PJMEDIA_SDP_EINPROTO;
+ }
+ }
+ }
+
+ if (srtp->offerer_side) {
+ /* find supported crypto-suite, get the tag, and assign policy_local */
+ pjmedia_srtp_crypto tmp_tx_crypto;
+ pj_bool_t has_crypto_attr = PJ_FALSE;
+ int rem_tag;
+
+ for (i=0; i<m_rem->attr_count; ++i) {
+ if (pj_stricmp(&m_rem->attr[i]->name, &ID_CRYPTO) != 0)
+ continue;
+
+ /* more than one crypto attribute in media answer */
+ if (has_crypto_attr) {
+ DEACTIVATE_MEDIA(pool, m_loc);
+ return PJMEDIA_SRTP_ESDPAMBIGUEANS;
+ }
+
+ has_crypto_attr = PJ_TRUE;
+
+ status = parse_attr_crypto(srtp->pool, m_rem->attr[i],
+ &tmp_tx_crypto, &rem_tag);
+ if (status != PJ_SUCCESS)
+ return status;
+
+
+ /* our offer tag is always ordered by setting */
+ if (rem_tag < 1 || rem_tag > (int)srtp->setting.crypto_count) {
+ DEACTIVATE_MEDIA(pool, m_loc);
+ return PJMEDIA_SRTP_ESDPINCRYPTOTAG;
+ }
+
+ /* match the crypto name */
+ if (pj_stricmp(&tmp_tx_crypto.name,
+ &srtp->setting.crypto[rem_tag-1].name) != 0)
+ {
+ DEACTIVATE_MEDIA(pool, m_loc);
+ return PJMEDIA_SRTP_ECRYPTONOTMATCH;
+ }
+
+ srtp->tx_policy_neg = srtp->setting.crypto[rem_tag-1];
+ srtp->rx_policy_neg = tmp_tx_crypto;
+ }
+
+ if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) {
+ /* should never reach here */
+ goto BYPASS_SRTP;
+ } else if (srtp->setting.use == PJMEDIA_SRTP_OPTIONAL) {
+ if (!has_crypto_attr)
+ goto BYPASS_SRTP;
+ } else if (srtp->setting.use == PJMEDIA_SRTP_MANDATORY) {
+ if (!has_crypto_attr) {
+ DEACTIVATE_MEDIA(pool, m_loc);
+ return PJMEDIA_SRTP_ESDPREQCRYPTO;
+ }
+ }
+
+ /* At this point, we get valid rx_policy_neg & tx_policy_neg. */
+ }
+
+ /* Make sure we have the SRTP policies */
+ if (srtp_crypto_empty(&srtp->tx_policy_neg) ||
+ srtp_crypto_empty(&srtp->rx_policy_neg))
+ {
+ goto BYPASS_SRTP;
+ }
+
+ /* Reset probation counts */
+ srtp->probation_cnt = PROBATION_CNT_INIT;
+
+ /* Got policy_local & policy_remote, let's initalize the SRTP */
+
+ /* Ticket #1075: media_start() is called whenever media description
+ * gets updated, e.g: call hold, however we should restart SRTP only
+ * when the SRTP policy settings are updated.
+ */
+ if (srtp_crypto_cmp(&srtp->tx_policy_neg, &srtp->tx_policy) ||
+ srtp_crypto_cmp(&srtp->rx_policy_neg, &srtp->rx_policy))
+ {
+ status = pjmedia_transport_srtp_start(tp,
+ &srtp->tx_policy_neg,
+ &srtp->rx_policy_neg);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ srtp->bypass_srtp = PJ_FALSE;
+
+ goto PROPAGATE_MEDIA_START;
+
+BYPASS_SRTP:
+ srtp->bypass_srtp = PJ_TRUE;
+ srtp->peer_use = PJMEDIA_SRTP_DISABLED;
+ if (srtp->session_inited) {
+ pjmedia_transport_srtp_stop(tp);
+ }
+
+PROPAGATE_MEDIA_START:
+ return pjmedia_transport_media_start(srtp->member_tp, pool,
+ sdp_local, sdp_remote,
+ media_index);
+}
+
+static pj_status_t transport_media_stop(pjmedia_transport *tp)
+{
+ struct transport_srtp *srtp = (struct transport_srtp*) tp;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(tp, PJ_EINVAL);
+
+ status = pjmedia_transport_media_stop(srtp->member_tp);
+ if (status != PJ_SUCCESS)
+ PJ_LOG(4, (srtp->pool->obj_name,
+ "SRTP failed stop underlying media transport."));
+
+ return pjmedia_transport_srtp_stop(tp);
+}
+
+/* Utility */
+PJ_DEF(pj_status_t) pjmedia_transport_srtp_decrypt_pkt(pjmedia_transport *tp,
+ pj_bool_t is_rtp,
+ void *pkt,
+ int *pkt_len)
+{
+ transport_srtp *srtp = (transport_srtp *)tp;
+ err_status_t err;
+
+ if (srtp->bypass_srtp)
+ return PJ_SUCCESS;
+
+ PJ_ASSERT_RETURN(tp && pkt && (*pkt_len>0), PJ_EINVAL);
+ PJ_ASSERT_RETURN(srtp->session_inited, PJ_EINVALIDOP);
+
+ /* Make sure buffer is 32bit aligned */
+ PJ_ASSERT_ON_FAIL( (((long)pkt) & 0x03)==0, return PJ_EINVAL);
+
+ pj_lock_acquire(srtp->mutex);
+
+ if (!srtp->session_inited) {
+ pj_lock_release(srtp->mutex);
+ return PJ_EINVALIDOP;
+ }
+
+ if (is_rtp)
+ err = srtp_unprotect(srtp->srtp_rx_ctx, pkt, pkt_len);
+ else
+ err = srtp_unprotect_rtcp(srtp->srtp_rx_ctx, pkt, pkt_len);
+
+ if (err != err_status_ok) {
+ PJ_LOG(5,(srtp->pool->obj_name,
+ "Failed to unprotect SRTP, pkt size=%d, err=%s",
+ *pkt_len, get_libsrtp_errstr(err)));
+ }
+
+ pj_lock_release(srtp->mutex);
+
+ return (err==err_status_ok) ? PJ_SUCCESS : PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+}
+
+#endif
+
+
diff --git a/pjmedia/src/pjmedia/transport_udp.c b/pjmedia/src/pjmedia/transport_udp.c
new file mode 100644
index 0000000..76f9bbb
--- /dev/null
+++ b/pjmedia/src/pjmedia/transport_udp.c
@@ -0,0 +1,915 @@
+/* $Id: transport_udp.c 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/transport_udp.h>
+#include <pj/addr_resolv.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/ioqueue.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/rand.h>
+#include <pj/string.h>
+
+
+/* Maximum size of incoming RTP packet */
+#define RTP_LEN PJMEDIA_MAX_MTU
+
+/* Maximum size of incoming RTCP packet */
+#define RTCP_LEN 600
+
+/* Maximum pending write operations */
+#define MAX_PENDING 4
+
+static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 };
+
+/* Pending write buffer */
+typedef struct pending_write
+{
+ char buffer[RTP_LEN];
+ pj_ioqueue_op_key_t op_key;
+} pending_write;
+
+
+struct transport_udp
+{
+ pjmedia_transport base; /**< Base transport. */
+
+ pj_pool_t *pool; /**< Memory pool */
+ unsigned options; /**< Transport options. */
+ unsigned media_options; /**< Transport media options. */
+ void *user_data; /**< Only valid when attached */
+ pj_bool_t attached; /**< Has attachment? */
+ pj_sockaddr rem_rtp_addr; /**< Remote RTP address */
+ pj_sockaddr rem_rtcp_addr; /**< Remote RTCP address */
+ int addr_len; /**< Length of addresses. */
+ void (*rtp_cb)( void*, /**< To report incoming RTP. */
+ void*,
+ pj_ssize_t);
+ void (*rtcp_cb)( void*, /**< To report incoming RTCP. */
+ void*,
+ pj_ssize_t);
+
+ unsigned tx_drop_pct; /**< Percent of tx pkts to drop. */
+ unsigned rx_drop_pct; /**< Percent of rx pkts to drop. */
+
+ pj_sock_t rtp_sock; /**< RTP socket */
+ pj_sockaddr rtp_addr_name; /**< Published RTP address. */
+ pj_ioqueue_key_t *rtp_key; /**< RTP socket key in ioqueue */
+ pj_ioqueue_op_key_t rtp_read_op; /**< Pending read operation */
+ unsigned rtp_write_op_id;/**< Next write_op to use */
+ pending_write rtp_pending_write[MAX_PENDING]; /**< Pending write */
+ pj_sockaddr rtp_src_addr; /**< Actual packet src addr. */
+ unsigned rtp_src_cnt; /**< How many pkt from this addr. */
+ int rtp_addrlen; /**< Address length. */
+ char rtp_pkt[RTP_LEN];/**< Incoming RTP packet buffer */
+
+ pj_sock_t rtcp_sock; /**< RTCP socket */
+ pj_sockaddr rtcp_addr_name; /**< Published RTCP address. */
+ pj_sockaddr rtcp_src_addr; /**< Actual source RTCP address. */
+ unsigned rtcp_src_cnt; /**< How many pkt from this addr. */
+ int rtcp_addr_len; /**< Length of RTCP src address. */
+ pj_ioqueue_key_t *rtcp_key; /**< RTCP socket key in ioqueue */
+ pj_ioqueue_op_key_t rtcp_read_op; /**< Pending read operation */
+ pj_ioqueue_op_key_t rtcp_write_op; /**< Pending write operation */
+ char rtcp_pkt[RTCP_LEN];/**< Incoming RTCP packet buffer */
+};
+
+
+
+static void on_rx_rtp( pj_ioqueue_key_t *key,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_read);
+static void on_rx_rtcp(pj_ioqueue_key_t *key,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_read);
+
+/*
+ * These are media transport operations.
+ */
+static pj_status_t transport_get_info (pjmedia_transport *tp,
+ pjmedia_transport_info *info);
+static pj_status_t transport_attach (pjmedia_transport *tp,
+ void *user_data,
+ const pj_sockaddr_t *rem_addr,
+ const pj_sockaddr_t *rem_rtcp,
+ unsigned addr_len,
+ void (*rtp_cb)(void*,
+ void*,
+ pj_ssize_t),
+ void (*rtcp_cb)(void*,
+ void*,
+ pj_ssize_t));
+static void transport_detach (pjmedia_transport *tp,
+ void *strm);
+static pj_status_t transport_send_rtp( pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size);
+static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size);
+static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
+ const pj_sockaddr_t *addr,
+ unsigned addr_len,
+ const void *pkt,
+ pj_size_t size);
+static pj_status_t transport_media_create(pjmedia_transport *tp,
+ pj_pool_t *pool,
+ unsigned options,
+ const pjmedia_sdp_session *sdp_remote,
+ unsigned media_index);
+static pj_status_t transport_encode_sdp(pjmedia_transport *tp,
+ pj_pool_t *pool,
+ pjmedia_sdp_session *sdp_local,
+ const pjmedia_sdp_session *rem_sdp,
+ unsigned media_index);
+static pj_status_t transport_media_start (pjmedia_transport *tp,
+ pj_pool_t *pool,
+ const pjmedia_sdp_session *sdp_local,
+ const pjmedia_sdp_session *sdp_remote,
+ unsigned media_index);
+static pj_status_t transport_media_stop(pjmedia_transport *tp);
+static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
+ pjmedia_dir dir,
+ unsigned pct_lost);
+static pj_status_t transport_destroy (pjmedia_transport *tp);
+
+
+static pjmedia_transport_op transport_udp_op =
+{
+ &transport_get_info,
+ &transport_attach,
+ &transport_detach,
+ &transport_send_rtp,
+ &transport_send_rtcp,
+ &transport_send_rtcp2,
+ &transport_media_create,
+ &transport_encode_sdp,
+ &transport_media_start,
+ &transport_media_stop,
+ &transport_simulate_lost,
+ &transport_destroy
+};
+
+
+/**
+ * Create UDP stream transport.
+ */
+PJ_DEF(pj_status_t) pjmedia_transport_udp_create( pjmedia_endpt *endpt,
+ const char *name,
+ int port,
+ unsigned options,
+ pjmedia_transport **p_tp)
+{
+ return pjmedia_transport_udp_create2(endpt, name, NULL, port, options,
+ p_tp);
+}
+
+/**
+ * Create UDP stream transport.
+ */
+PJ_DEF(pj_status_t) pjmedia_transport_udp_create2(pjmedia_endpt *endpt,
+ const char *name,
+ const pj_str_t *addr,
+ int port,
+ unsigned options,
+ pjmedia_transport **p_tp)
+{
+ return pjmedia_transport_udp_create3(endpt, pj_AF_INET(), name,
+ addr, port, options, p_tp);
+}
+
+/**
+ * Create UDP stream transport.
+ */
+PJ_DEF(pj_status_t) pjmedia_transport_udp_create3(pjmedia_endpt *endpt,
+ int af,
+ const char *name,
+ const pj_str_t *addr,
+ int port,
+ unsigned options,
+ pjmedia_transport **p_tp)
+{
+ pjmedia_sock_info si;
+ pj_status_t status;
+
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(endpt && port && p_tp, PJ_EINVAL);
+
+
+ pj_bzero(&si, sizeof(pjmedia_sock_info));
+ si.rtp_sock = si.rtcp_sock = PJ_INVALID_SOCKET;
+
+ /* Create RTP socket */
+ status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &si.rtp_sock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Bind RTP socket */
+ status = pj_sockaddr_init(af, &si.rtp_addr_name, addr, (pj_uint16_t)port);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_sock_bind(si.rtp_sock, &si.rtp_addr_name,
+ pj_sockaddr_get_len(&si.rtp_addr_name));
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+
+ /* Create RTCP socket */
+ status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &si.rtcp_sock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Bind RTCP socket */
+ status = pj_sockaddr_init(af, &si.rtcp_addr_name, addr,
+ (pj_uint16_t)(port+1));
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_sock_bind(si.rtcp_sock, &si.rtcp_addr_name,
+ pj_sockaddr_get_len(&si.rtcp_addr_name));
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+
+ /* Create UDP transport by attaching socket info */
+ return pjmedia_transport_udp_attach( endpt, name, &si, options, p_tp);
+
+
+on_error:
+ if (si.rtp_sock != PJ_INVALID_SOCKET)
+ pj_sock_close(si.rtp_sock);
+ if (si.rtcp_sock != PJ_INVALID_SOCKET)
+ pj_sock_close(si.rtcp_sock);
+ return status;
+}
+
+
+/**
+ * Create UDP stream transport from existing socket info.
+ */
+PJ_DEF(pj_status_t) pjmedia_transport_udp_attach( pjmedia_endpt *endpt,
+ const char *name,
+ const pjmedia_sock_info *si,
+ unsigned options,
+ pjmedia_transport **p_tp)
+{
+ struct transport_udp *tp;
+ pj_pool_t *pool;
+ pj_ioqueue_t *ioqueue;
+ pj_ioqueue_callback rtp_cb, rtcp_cb;
+ pj_ssize_t size;
+ unsigned i;
+ pj_status_t status;
+
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(endpt && si && p_tp, PJ_EINVAL);
+
+ /* Get ioqueue instance */
+ ioqueue = pjmedia_endpt_get_ioqueue(endpt);
+
+ if (name==NULL)
+ name = "udp%p";
+
+ /* Create transport structure */
+ pool = pjmedia_endpt_create_pool(endpt, name, 512, 512);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ tp = PJ_POOL_ZALLOC_T(pool, struct transport_udp);
+ tp->pool = pool;
+ tp->options = options;
+ pj_memcpy(tp->base.name, pool->obj_name, PJ_MAX_OBJ_NAME);
+ tp->base.op = &transport_udp_op;
+ tp->base.type = PJMEDIA_TRANSPORT_TYPE_UDP;
+
+ /* Copy socket infos */
+ tp->rtp_sock = si->rtp_sock;
+ tp->rtp_addr_name = si->rtp_addr_name;
+ tp->rtcp_sock = si->rtcp_sock;
+ tp->rtcp_addr_name = si->rtcp_addr_name;
+
+ /* If address is 0.0.0.0, use host's IP address */
+ if (!pj_sockaddr_has_addr(&tp->rtp_addr_name)) {
+ pj_sockaddr hostip;
+
+ status = pj_gethostip(tp->rtp_addr_name.addr.sa_family, &hostip);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ pj_memcpy(pj_sockaddr_get_addr(&tp->rtp_addr_name),
+ pj_sockaddr_get_addr(&hostip),
+ pj_sockaddr_get_addr_len(&hostip));
+ }
+
+ /* Same with RTCP */
+ if (!pj_sockaddr_has_addr(&tp->rtcp_addr_name)) {
+ pj_memcpy(pj_sockaddr_get_addr(&tp->rtcp_addr_name),
+ pj_sockaddr_get_addr(&tp->rtp_addr_name),
+ pj_sockaddr_get_addr_len(&tp->rtp_addr_name));
+ }
+
+ /* Setup RTP socket with the ioqueue */
+ pj_bzero(&rtp_cb, sizeof(rtp_cb));
+ rtp_cb.on_read_complete = &on_rx_rtp;
+
+ status = pj_ioqueue_register_sock(pool, ioqueue, tp->rtp_sock, tp,
+ &rtp_cb, &tp->rtp_key);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Disallow concurrency so that detach() and destroy() are
+ * synchronized with the callback.
+ */
+ status = pj_ioqueue_set_concurrency(tp->rtp_key, PJ_FALSE);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ pj_ioqueue_op_key_init(&tp->rtp_read_op, sizeof(tp->rtp_read_op));
+ for (i=0; i<PJ_ARRAY_SIZE(tp->rtp_pending_write); ++i)
+ pj_ioqueue_op_key_init(&tp->rtp_pending_write[i].op_key,
+ sizeof(tp->rtp_pending_write[i].op_key));
+
+ /* Kick of pending RTP read from the ioqueue */
+ tp->rtp_addrlen = sizeof(tp->rtp_src_addr);
+ size = sizeof(tp->rtp_pkt);
+ status = pj_ioqueue_recvfrom(tp->rtp_key, &tp->rtp_read_op,
+ tp->rtp_pkt, &size, PJ_IOQUEUE_ALWAYS_ASYNC,
+ &tp->rtp_src_addr, &tp->rtp_addrlen);
+ if (status != PJ_EPENDING)
+ goto on_error;
+
+
+ /* Setup RTCP socket with ioqueue */
+ pj_bzero(&rtcp_cb, sizeof(rtcp_cb));
+ rtcp_cb.on_read_complete = &on_rx_rtcp;
+
+ status = pj_ioqueue_register_sock(pool, ioqueue, tp->rtcp_sock, tp,
+ &rtcp_cb, &tp->rtcp_key);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_ioqueue_set_concurrency(tp->rtcp_key, PJ_FALSE);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ pj_ioqueue_op_key_init(&tp->rtcp_read_op, sizeof(tp->rtcp_read_op));
+ pj_ioqueue_op_key_init(&tp->rtcp_write_op, sizeof(tp->rtcp_write_op));
+
+
+ /* Kick of pending RTCP read from the ioqueue */
+ size = sizeof(tp->rtcp_pkt);
+ tp->rtcp_addr_len = sizeof(tp->rtcp_src_addr);
+ status = pj_ioqueue_recvfrom( tp->rtcp_key, &tp->rtcp_read_op,
+ tp->rtcp_pkt, &size, PJ_IOQUEUE_ALWAYS_ASYNC,
+ &tp->rtcp_src_addr, &tp->rtcp_addr_len);
+ if (status != PJ_EPENDING)
+ goto on_error;
+
+
+ /* Done */
+ *p_tp = &tp->base;
+ return PJ_SUCCESS;
+
+
+on_error:
+ transport_destroy(&tp->base);
+ return status;
+}
+
+
+/**
+ * Close UDP transport.
+ */
+static pj_status_t transport_destroy(pjmedia_transport *tp)
+{
+ struct transport_udp *udp = (struct transport_udp*) tp;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(tp, PJ_EINVAL);
+
+ /* Must not close while application is using this */
+ //PJ_ASSERT_RETURN(!udp->attached, PJ_EINVALIDOP);
+
+
+ if (udp->rtp_key) {
+ /* This will block the execution if callback is still
+ * being called.
+ */
+ pj_ioqueue_unregister(udp->rtp_key);
+ udp->rtp_key = NULL;
+ udp->rtp_sock = PJ_INVALID_SOCKET;
+ } else if (udp->rtp_sock != PJ_INVALID_SOCKET) {
+ pj_sock_close(udp->rtp_sock);
+ udp->rtp_sock = PJ_INVALID_SOCKET;
+ }
+
+ if (udp->rtcp_key) {
+ pj_ioqueue_unregister(udp->rtcp_key);
+ udp->rtcp_key = NULL;
+ udp->rtcp_sock = PJ_INVALID_SOCKET;
+ } else if (udp->rtcp_sock != PJ_INVALID_SOCKET) {
+ pj_sock_close(udp->rtcp_sock);
+ udp->rtcp_sock = PJ_INVALID_SOCKET;
+ }
+
+ pj_pool_release(udp->pool);
+
+ return PJ_SUCCESS;
+}
+
+
+/* Notification from ioqueue about incoming RTP packet */
+static void on_rx_rtp( pj_ioqueue_key_t *key,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_read)
+{
+ struct transport_udp *udp;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(op_key);
+
+ udp = (struct transport_udp*) pj_ioqueue_get_user_data(key);
+
+ do {
+ void (*cb)(void*,void*,pj_ssize_t);
+ void *user_data;
+ pj_bool_t discard = PJ_FALSE;
+
+ cb = udp->rtp_cb;
+ user_data = udp->user_data;
+
+ /* Simulate packet lost on RX direction */
+ if (udp->rx_drop_pct) {
+ if ((pj_rand() % 100) <= (int)udp->rx_drop_pct) {
+ PJ_LOG(5,(udp->base.name,
+ "RX RTP packet dropped because of pkt lost "
+ "simulation"));
+ discard = PJ_TRUE;
+ }
+ }
+
+ /* See if source address of RTP packet is different than the
+ * configured address, and switch RTP remote address to
+ * source packet address after several consecutive packets
+ * have been received.
+ */
+ if (bytes_read>0 &&
+ (udp->options & PJMEDIA_UDP_NO_SRC_ADDR_CHECKING)==0)
+ {
+ if (pj_sockaddr_cmp(&udp->rem_rtp_addr, &udp->rtp_src_addr) == 0) {
+ /* We're still receiving from rem_rtp_addr. Don't switch. */
+ udp->rtp_src_cnt = 0;
+ } else {
+ udp->rtp_src_cnt++;
+
+ if (udp->rtp_src_cnt < PJMEDIA_RTP_NAT_PROBATION_CNT) {
+ discard = PJ_TRUE;
+ } else {
+
+ char addr_text[80];
+
+ /* Set remote RTP address to source address */
+ pj_memcpy(&udp->rem_rtp_addr, &udp->rtp_src_addr,
+ sizeof(pj_sockaddr));
+
+ /* Reset counter */
+ udp->rtp_src_cnt = 0;
+
+ PJ_LOG(4,(udp->base.name,
+ "Remote RTP address switched to %s",
+ pj_sockaddr_print(&udp->rtp_src_addr, addr_text,
+ sizeof(addr_text), 3)));
+
+ /* Also update remote RTCP address if actual RTCP source
+ * address is not heard yet.
+ */
+ if (!pj_sockaddr_has_addr(&udp->rtcp_src_addr)) {
+ pj_uint16_t port;
+
+ pj_memcpy(&udp->rem_rtcp_addr, &udp->rem_rtp_addr,
+ sizeof(pj_sockaddr));
+ pj_sockaddr_copy_addr(&udp->rem_rtcp_addr,
+ &udp->rem_rtp_addr);
+ port = (pj_uint16_t)
+ (pj_sockaddr_get_port(&udp->rem_rtp_addr)+1);
+ pj_sockaddr_set_port(&udp->rem_rtcp_addr, port);
+
+ pj_memcpy(&udp->rtcp_src_addr, &udp->rem_rtcp_addr,
+ sizeof(pj_sockaddr));
+
+ PJ_LOG(4,(udp->base.name,
+ "Remote RTCP address switched to predicted"
+ " address %s",
+ pj_sockaddr_print(&udp->rtcp_src_addr,
+ addr_text,
+ sizeof(addr_text), 3)));
+
+ }
+ }
+ }
+ }
+
+ if (!discard && udp->attached && cb)
+ (*cb)(user_data, udp->rtp_pkt, bytes_read);
+
+ bytes_read = sizeof(udp->rtp_pkt);
+ udp->rtp_addrlen = sizeof(udp->rtp_src_addr);
+ status = pj_ioqueue_recvfrom(udp->rtp_key, &udp->rtp_read_op,
+ udp->rtp_pkt, &bytes_read, 0,
+ &udp->rtp_src_addr,
+ &udp->rtp_addrlen);
+
+ if (status != PJ_EPENDING && status != PJ_SUCCESS)
+ bytes_read = -status;
+
+ } while (status != PJ_EPENDING && status != PJ_ECANCELLED);
+}
+
+
+/* Notification from ioqueue about incoming RTCP packet */
+static void on_rx_rtcp(pj_ioqueue_key_t *key,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_read)
+{
+ struct transport_udp *udp;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(op_key);
+
+ udp = (struct transport_udp*) pj_ioqueue_get_user_data(key);
+
+ do {
+ void (*cb)(void*,void*,pj_ssize_t);
+ void *user_data;
+
+ cb = udp->rtcp_cb;
+ user_data = udp->user_data;
+
+ if (udp->attached && cb)
+ (*cb)(user_data, udp->rtcp_pkt, bytes_read);
+
+ /* Check if RTCP source address is the same as the configured
+ * remote address, and switch the address when they are
+ * different.
+ */
+ if (bytes_read>0 &&
+ (udp->options & PJMEDIA_UDP_NO_SRC_ADDR_CHECKING)==0)
+ {
+ if (pj_sockaddr_cmp(&udp->rem_rtcp_addr, &udp->rtcp_src_addr) == 0) {
+ /* Still receiving from rem_rtcp_addr, don't switch */
+ udp->rtcp_src_cnt = 0;
+ } else {
+ ++udp->rtcp_src_cnt;
+
+ if (udp->rtcp_src_cnt >= PJMEDIA_RTCP_NAT_PROBATION_CNT ) {
+ char addr_text[80];
+
+ udp->rtcp_src_cnt = 0;
+ pj_memcpy(&udp->rem_rtcp_addr, &udp->rtcp_src_addr,
+ sizeof(pj_sockaddr));
+
+ PJ_LOG(4,(udp->base.name,
+ "Remote RTCP address switched to %s",
+ pj_sockaddr_print(&udp->rtcp_src_addr, addr_text,
+ sizeof(addr_text), 3)));
+ }
+ }
+ }
+
+ bytes_read = sizeof(udp->rtcp_pkt);
+ udp->rtcp_addr_len = sizeof(udp->rtcp_src_addr);
+ status = pj_ioqueue_recvfrom(udp->rtcp_key, &udp->rtcp_read_op,
+ udp->rtcp_pkt, &bytes_read, 0,
+ &udp->rtcp_src_addr,
+ &udp->rtcp_addr_len);
+ if (status != PJ_EPENDING && status != PJ_SUCCESS)
+ bytes_read = -status;
+
+ } while (status != PJ_EPENDING && status != PJ_ECANCELLED);
+}
+
+
+/* Called to get the transport info */
+static pj_status_t transport_get_info(pjmedia_transport *tp,
+ pjmedia_transport_info *info)
+{
+ struct transport_udp *udp = (struct transport_udp*)tp;
+ PJ_ASSERT_RETURN(tp && info, PJ_EINVAL);
+
+ info->sock_info.rtp_sock = udp->rtp_sock;
+ info->sock_info.rtp_addr_name = udp->rtp_addr_name;
+ info->sock_info.rtcp_sock = udp->rtcp_sock;
+ info->sock_info.rtcp_addr_name = udp->rtcp_addr_name;
+
+ /* Get remote address originating RTP & RTCP. */
+ info->src_rtp_name = udp->rtp_src_addr;
+ info->src_rtcp_name = udp->rtcp_src_addr;
+
+ return PJ_SUCCESS;
+}
+
+
+/* Called by application to initialize the transport */
+static pj_status_t transport_attach( pjmedia_transport *tp,
+ void *user_data,
+ const pj_sockaddr_t *rem_addr,
+ const pj_sockaddr_t *rem_rtcp,
+ unsigned addr_len,
+ void (*rtp_cb)(void*,
+ void*,
+ pj_ssize_t),
+ void (*rtcp_cb)(void*,
+ void*,
+ pj_ssize_t))
+{
+ struct transport_udp *udp = (struct transport_udp*) tp;
+ const pj_sockaddr *rtcp_addr;
+
+ /* Validate arguments */
+ PJ_ASSERT_RETURN(tp && rem_addr && addr_len, PJ_EINVAL);
+
+ /* Must not be "attached" to existing application */
+ PJ_ASSERT_RETURN(!udp->attached, PJ_EINVALIDOP);
+
+ /* Lock the ioqueue keys to make sure that callbacks are
+ * not executed. See ticket #844 for details.
+ */
+ pj_ioqueue_lock_key(udp->rtp_key);
+ pj_ioqueue_lock_key(udp->rtcp_key);
+
+ /* "Attach" the application: */
+
+ /* Copy remote RTP address */
+ pj_memcpy(&udp->rem_rtp_addr, rem_addr, addr_len);
+
+ /* Copy remote RTP address, if one is specified. */
+ rtcp_addr = (const pj_sockaddr*) rem_rtcp;
+ if (rtcp_addr && pj_sockaddr_has_addr(rtcp_addr)) {
+ pj_memcpy(&udp->rem_rtcp_addr, rem_rtcp, addr_len);
+
+ } else {
+ unsigned rtcp_port;
+
+ /* Otherwise guess the RTCP address from the RTP address */
+ pj_memcpy(&udp->rem_rtcp_addr, rem_addr, addr_len);
+ rtcp_port = pj_sockaddr_get_port(&udp->rem_rtp_addr) + 1;
+ pj_sockaddr_set_port(&udp->rem_rtcp_addr, (pj_uint16_t)rtcp_port);
+ }
+
+ /* Save the callbacks */
+ udp->rtp_cb = rtp_cb;
+ udp->rtcp_cb = rtcp_cb;
+ udp->user_data = user_data;
+
+ /* Save address length */
+ udp->addr_len = addr_len;
+
+ /* Last, mark transport as attached */
+ udp->attached = PJ_TRUE;
+
+ /* Reset source RTP & RTCP addresses and counter */
+ pj_bzero(&udp->rtp_src_addr, sizeof(udp->rtp_src_addr));
+ pj_bzero(&udp->rtcp_src_addr, sizeof(udp->rtcp_src_addr));
+ udp->rtp_src_cnt = 0;
+ udp->rtcp_src_cnt = 0;
+
+ /* Unlock keys */
+ pj_ioqueue_unlock_key(udp->rtcp_key);
+ pj_ioqueue_unlock_key(udp->rtp_key);
+
+ return PJ_SUCCESS;
+}
+
+
+/* Called by application when it no longer needs the transport */
+static void transport_detach( pjmedia_transport *tp,
+ void *user_data)
+{
+ struct transport_udp *udp = (struct transport_udp*) tp;
+
+ pj_assert(tp);
+
+ if (udp->attached) {
+ /* Lock the ioqueue keys to make sure that callbacks are
+ * not executed. See ticket #460 for details.
+ */
+ pj_ioqueue_lock_key(udp->rtp_key);
+ pj_ioqueue_lock_key(udp->rtcp_key);
+
+ /* User data is unreferenced on Release build */
+ PJ_UNUSED_ARG(user_data);
+
+ /* As additional checking, check if the same user data is specified */
+ pj_assert(user_data == udp->user_data);
+
+ /* First, mark transport as unattached */
+ udp->attached = PJ_FALSE;
+
+ /* Clear up application infos from transport */
+ udp->rtp_cb = NULL;
+ udp->rtcp_cb = NULL;
+ udp->user_data = NULL;
+
+ /* Unlock keys */
+ pj_ioqueue_unlock_key(udp->rtcp_key);
+ pj_ioqueue_unlock_key(udp->rtp_key);
+ }
+}
+
+
+/* Called by application to send RTP packet */
+static pj_status_t transport_send_rtp( pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size)
+{
+ struct transport_udp *udp = (struct transport_udp*)tp;
+ pj_ssize_t sent;
+ unsigned id;
+ struct pending_write *pw;
+ pj_status_t status;
+
+ /* Must be attached */
+ PJ_ASSERT_RETURN(udp->attached, PJ_EINVALIDOP);
+
+ /* Check that the size is supported */
+ PJ_ASSERT_RETURN(size <= RTP_LEN, PJ_ETOOBIG);
+
+ /* Simulate packet lost on TX direction */
+ if (udp->tx_drop_pct) {
+ if ((pj_rand() % 100) <= (int)udp->tx_drop_pct) {
+ PJ_LOG(5,(udp->base.name,
+ "TX RTP packet dropped because of pkt lost "
+ "simulation"));
+ return PJ_SUCCESS;
+ }
+ }
+
+
+ id = udp->rtp_write_op_id;
+ pw = &udp->rtp_pending_write[id];
+
+ /* We need to copy packet to our buffer because when the
+ * operation is pending, caller might write something else
+ * to the original buffer.
+ */
+ pj_memcpy(pw->buffer, pkt, size);
+
+ sent = size;
+ status = pj_ioqueue_sendto( udp->rtp_key,
+ &udp->rtp_pending_write[id].op_key,
+ pw->buffer, &sent, 0,
+ &udp->rem_rtp_addr,
+ udp->addr_len);
+
+ udp->rtp_write_op_id = (udp->rtp_write_op_id + 1) %
+ PJ_ARRAY_SIZE(udp->rtp_pending_write);
+
+ if (status==PJ_SUCCESS || status==PJ_EPENDING)
+ return PJ_SUCCESS;
+
+ return status;
+}
+
+/* Called by application to send RTCP packet */
+static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size)
+{
+ return transport_send_rtcp2(tp, NULL, 0, pkt, size);
+}
+
+
+/* Called by application to send RTCP packet */
+static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
+ const pj_sockaddr_t *addr,
+ unsigned addr_len,
+ const void *pkt,
+ pj_size_t size)
+{
+ struct transport_udp *udp = (struct transport_udp*)tp;
+ pj_ssize_t sent;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(udp->attached, PJ_EINVALIDOP);
+
+ if (addr == NULL) {
+ addr = &udp->rem_rtcp_addr;
+ addr_len = udp->addr_len;
+ }
+
+ sent = size;
+ status = pj_ioqueue_sendto( udp->rtcp_key, &udp->rtcp_write_op,
+ pkt, &sent, 0, addr, addr_len);
+
+ if (status==PJ_SUCCESS || status==PJ_EPENDING)
+ return PJ_SUCCESS;
+
+ return status;
+}
+
+
+static pj_status_t transport_media_create(pjmedia_transport *tp,
+ pj_pool_t *pool,
+ unsigned options,
+ const pjmedia_sdp_session *sdp_remote,
+ unsigned media_index)
+{
+ struct transport_udp *udp = (struct transport_udp*)tp;
+
+ PJ_ASSERT_RETURN(tp && pool, PJ_EINVAL);
+ udp->media_options = options;
+
+ PJ_UNUSED_ARG(sdp_remote);
+ PJ_UNUSED_ARG(media_index);
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t transport_encode_sdp(pjmedia_transport *tp,
+ pj_pool_t *pool,
+ pjmedia_sdp_session *sdp_local,
+ const pjmedia_sdp_session *rem_sdp,
+ unsigned media_index)
+{
+ struct transport_udp *udp = (struct transport_udp*)tp;
+
+ /* Validate media transport */
+ /* By now, this transport only support RTP/AVP transport */
+ if ((udp->media_options & PJMEDIA_TPMED_NO_TRANSPORT_CHECKING) == 0) {
+ pjmedia_sdp_media *m_rem, *m_loc;
+
+ m_rem = rem_sdp? rem_sdp->media[media_index] : NULL;
+ m_loc = sdp_local->media[media_index];
+
+ if (pj_stricmp(&m_loc->desc.transport, &ID_RTP_AVP) ||
+ (m_rem && pj_stricmp(&m_rem->desc.transport, &ID_RTP_AVP)))
+ {
+ pjmedia_sdp_media_deactivate(pool, m_loc);
+ return PJMEDIA_SDP_EINPROTO;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t transport_media_start(pjmedia_transport *tp,
+ pj_pool_t *pool,
+ const pjmedia_sdp_session *sdp_local,
+ const pjmedia_sdp_session *sdp_remote,
+ unsigned media_index)
+{
+ PJ_ASSERT_RETURN(tp && pool && sdp_local, PJ_EINVAL);
+
+ PJ_UNUSED_ARG(tp);
+ PJ_UNUSED_ARG(pool);
+ PJ_UNUSED_ARG(sdp_local);
+ PJ_UNUSED_ARG(sdp_remote);
+ PJ_UNUSED_ARG(media_index);
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t transport_media_stop(pjmedia_transport *tp)
+{
+ PJ_UNUSED_ARG(tp);
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
+ pjmedia_dir dir,
+ unsigned pct_lost)
+{
+ struct transport_udp *udp = (struct transport_udp*)tp;
+
+ PJ_ASSERT_RETURN(tp && pct_lost <= 100, PJ_EINVAL);
+
+ if (dir & PJMEDIA_DIR_ENCODING)
+ udp->tx_drop_pct = pct_lost;
+
+ if (dir & PJMEDIA_DIR_DECODING)
+ udp->rx_drop_pct = pct_lost;
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjmedia/src/pjmedia/types.c b/pjmedia/src/pjmedia/types.c
new file mode 100644
index 0000000..b280ea8
--- /dev/null
+++ b/pjmedia/src/pjmedia/types.c
@@ -0,0 +1,47 @@
+/* $Id: types.c 3715 2011-08-19 09:35:25Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/types.h>
+#include <pj/assert.h>
+
+/**
+ * Utility function to return the string name for a pjmedia_type.
+ *
+ * @param t The media type.
+ *
+ * @return String.
+ */
+PJ_DEF(const char*) pjmedia_type_name(pjmedia_type t)
+{
+ const char *type_names[] = {
+ "none",
+ "audio",
+ "video",
+ "application",
+ "unknown"
+ };
+
+ pj_assert(t < PJ_ARRAY_SIZE(type_names));
+ pj_assert(PJMEDIA_TYPE_UNKNOWN == 4);
+
+ if (t < PJ_ARRAY_SIZE(type_names))
+ return type_names[t];
+ else
+ return "??";
+}
diff --git a/pjmedia/src/pjmedia/vid_codec.c b/pjmedia/src/pjmedia/vid_codec.c
new file mode 100644
index 0000000..57c7c6c
--- /dev/null
+++ b/pjmedia/src/pjmedia/vid_codec.c
@@ -0,0 +1,759 @@
+/* $Id: vid_codec.c 4008 2012-04-03 04:03:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/vid_codec.h>
+#include <pjmedia/errno.h>
+#include <pj/array.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/string.h>
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+
+#define THIS_FILE "vid_codec.c"
+
+static pjmedia_vid_codec_mgr *def_vid_codec_mgr;
+
+
+/* Definition of default codecs parameters */
+typedef struct pjmedia_vid_codec_default_param
+{
+ pj_pool_t *pool;
+ pjmedia_vid_codec_param *param;
+} pjmedia_vid_codec_default_param;
+
+
+/*
+ * Codec manager maintains array of these structs for each supported
+ * codec.
+ */
+typedef struct pjmedia_vid_codec_desc
+{
+ pjmedia_vid_codec_info info; /**< Codec info. */
+ pjmedia_codec_id id; /**< Fully qualified name */
+ pjmedia_codec_priority prio; /**< Priority. */
+ pjmedia_vid_codec_factory *factory; /**< The factory. */
+ pjmedia_vid_codec_default_param *def_param; /**< Default codecs
+ parameters. */
+} pjmedia_vid_codec_desc;
+
+
+/* The declaration of video codec manager */
+struct pjmedia_vid_codec_mgr
+{
+ /** Pool factory instance. */
+ pj_pool_factory *pf;
+
+ /** Codec manager mutex. */
+ pj_mutex_t *mutex;
+
+ /** List of codec factories registered to codec manager. */
+ pjmedia_vid_codec_factory factory_list;
+
+ /** Number of supported codecs. */
+ unsigned codec_cnt;
+
+ /** Array of codec descriptor. */
+ pjmedia_vid_codec_desc codec_desc[PJMEDIA_CODEC_MGR_MAX_CODECS];
+
+};
+
+
+
+/* Sort codecs in codec manager based on priorities */
+static void sort_codecs(pjmedia_vid_codec_mgr *mgr);
+
+
+/*
+ * Duplicate video codec parameter.
+ */
+PJ_DEF(pjmedia_vid_codec_param*) pjmedia_vid_codec_param_clone(
+ pj_pool_t *pool,
+ const pjmedia_vid_codec_param *src)
+{
+ pjmedia_vid_codec_param *p;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(pool && src, NULL);
+
+ p = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_codec_param);
+
+ /* Update codec param */
+ pj_memcpy(p, src, sizeof(pjmedia_vid_codec_param));
+ for (i = 0; i < src->dec_fmtp.cnt; ++i) {
+ pj_strdup(pool, &p->dec_fmtp.param[i].name,
+ &src->dec_fmtp.param[i].name);
+ pj_strdup(pool, &p->dec_fmtp.param[i].val,
+ &src->dec_fmtp.param[i].val);
+ }
+ for (i = 0; i < src->enc_fmtp.cnt; ++i) {
+ pj_strdup(pool, &p->enc_fmtp.param[i].name,
+ &src->enc_fmtp.param[i].name);
+ pj_strdup(pool, &p->enc_fmtp.param[i].val,
+ &src->enc_fmtp.param[i].val);
+ }
+
+ return p;
+}
+
+/*
+ * Initialize codec manager.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_create(
+ pj_pool_t *pool,
+ pjmedia_vid_codec_mgr **p_mgr)
+{
+ pjmedia_vid_codec_mgr *mgr;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pool, PJ_EINVAL);
+
+ mgr = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_codec_mgr);
+ mgr->pf = pool->factory;
+ pj_list_init (&mgr->factory_list);
+ mgr->codec_cnt = 0;
+
+ /* Create mutex */
+ status = pj_mutex_create_recursive(pool, "vid-codec-mgr", &mgr->mutex);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (!def_vid_codec_mgr)
+ def_vid_codec_mgr = mgr;
+
+ if (p_mgr)
+ *p_mgr = mgr;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Initialize codec manager.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_destroy (pjmedia_vid_codec_mgr *mgr)
+{
+ if (!mgr) mgr = def_vid_codec_mgr;
+ PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+ /* Destroy mutex */
+ if (mgr->mutex)
+ pj_mutex_destroy(mgr->mutex);
+
+ /* Just for safety, set codec manager states to zero */
+ pj_bzero(mgr, sizeof(pjmedia_vid_codec_mgr));
+
+ if (mgr == def_vid_codec_mgr)
+ def_vid_codec_mgr = NULL;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pjmedia_vid_codec_mgr*) pjmedia_vid_codec_mgr_instance(void)
+{
+ //pj_assert(def_vid_codec_mgr);
+ return def_vid_codec_mgr;
+}
+
+PJ_DEF(void) pjmedia_vid_codec_mgr_set_instance(pjmedia_vid_codec_mgr* mgr)
+{
+ def_vid_codec_mgr = mgr;
+}
+
+
+/*
+ * Register a codec factory.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_register_factory(
+ pjmedia_vid_codec_mgr *mgr,
+ pjmedia_vid_codec_factory *factory)
+{
+ pjmedia_vid_codec_info info[PJMEDIA_CODEC_MGR_MAX_CODECS];
+ unsigned i, count;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(factory, PJ_EINVAL);
+
+ if (!mgr) mgr = def_vid_codec_mgr;
+ PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+ /* Enum codecs */
+ count = PJ_ARRAY_SIZE(info);
+ status = factory->op->enum_info(factory, &count, info);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_mutex_lock(mgr->mutex);
+
+ /* Check codec count */
+ if (count + mgr->codec_cnt > PJ_ARRAY_SIZE(mgr->codec_desc)) {
+ pj_mutex_unlock(mgr->mutex);
+ return PJ_ETOOMANY;
+ }
+
+
+ /* Save the codecs */
+ for (i=0; i<count; ++i) {
+ pj_memcpy( &mgr->codec_desc[mgr->codec_cnt+i],
+ &info[i], sizeof(pjmedia_vid_codec_info));
+ mgr->codec_desc[mgr->codec_cnt+i].prio = PJMEDIA_CODEC_PRIO_NORMAL;
+ mgr->codec_desc[mgr->codec_cnt+i].factory = factory;
+ pjmedia_vid_codec_info_to_id( &info[i],
+ mgr->codec_desc[mgr->codec_cnt+i].id,
+ sizeof(pjmedia_codec_id));
+ }
+
+ /* Update count */
+ mgr->codec_cnt += count;
+
+ /* Re-sort codec based on priorities */
+ sort_codecs(mgr);
+
+ /* Add factory to the list */
+ pj_list_push_back(&mgr->factory_list, factory);
+
+ pj_mutex_unlock(mgr->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Unregister a codec factory.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_unregister_factory(
+ pjmedia_vid_codec_mgr *mgr,
+ pjmedia_vid_codec_factory *factory)
+{
+ unsigned i;
+ PJ_ASSERT_RETURN(factory, PJ_EINVAL);
+
+ if (!mgr) mgr = def_vid_codec_mgr;
+ PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+ pj_mutex_lock(mgr->mutex);
+
+ /* Factory must be registered. */
+ if (pj_list_find_node(&mgr->factory_list, factory) != factory) {
+ pj_mutex_unlock(mgr->mutex);
+ return PJ_ENOTFOUND;
+ }
+
+ /* Erase factory from the factory list */
+ pj_list_erase(factory);
+
+
+ /* Remove all supported codecs from the codec manager that were created
+ * by the specified factory.
+ */
+ for (i=0; i<mgr->codec_cnt; ) {
+
+ if (mgr->codec_desc[i].factory == factory) {
+ /* Remove the codec from array of codec descriptions */
+ pj_array_erase(mgr->codec_desc, sizeof(mgr->codec_desc[0]),
+ mgr->codec_cnt, i);
+ --mgr->codec_cnt;
+
+ } else {
+ ++i;
+ }
+ }
+
+ pj_mutex_unlock(mgr->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Enum all codecs.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_enum_codecs(
+ pjmedia_vid_codec_mgr *mgr,
+ unsigned *count,
+ pjmedia_vid_codec_info codecs[],
+ unsigned *prio)
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(count && codecs, PJ_EINVAL);
+
+ if (!mgr) mgr = def_vid_codec_mgr;
+ PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+ pj_mutex_lock(mgr->mutex);
+
+ if (*count > mgr->codec_cnt)
+ *count = mgr->codec_cnt;
+
+ for (i=0; i<*count; ++i) {
+ pj_memcpy(&codecs[i],
+ &mgr->codec_desc[i].info,
+ sizeof(pjmedia_vid_codec_info));
+ }
+
+ if (prio) {
+ for (i=0; i < *count; ++i)
+ prio[i] = mgr->codec_desc[i].prio;
+ }
+
+ pj_mutex_unlock(mgr->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get codec info for the specified payload type.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_get_codec_info(
+ pjmedia_vid_codec_mgr *mgr,
+ unsigned pt,
+ const pjmedia_vid_codec_info **p_info)
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(p_info, PJ_EINVAL);
+
+ if (!mgr) mgr = def_vid_codec_mgr;
+ PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+ pj_mutex_lock(mgr->mutex);
+
+ for (i=0; i<mgr->codec_cnt; ++i) {
+ if (mgr->codec_desc[i].info.pt == pt) {
+ *p_info = &mgr->codec_desc[i].info;
+
+ pj_mutex_unlock(mgr->mutex);
+ return PJ_SUCCESS;
+ }
+ }
+
+ pj_mutex_unlock(mgr->mutex);
+
+ return PJMEDIA_CODEC_EUNSUP;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_get_codec_info2(
+ pjmedia_vid_codec_mgr *mgr,
+ pjmedia_format_id fmt_id,
+ const pjmedia_vid_codec_info **p_info)
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(p_info, PJ_EINVAL);
+
+ if (!mgr) mgr = def_vid_codec_mgr;
+ PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+ pj_mutex_lock(mgr->mutex);
+
+ for (i=0; i<mgr->codec_cnt; ++i) {
+ if (mgr->codec_desc[i].info.fmt_id == fmt_id) {
+ *p_info = &mgr->codec_desc[i].info;
+
+ pj_mutex_unlock(mgr->mutex);
+ return PJ_SUCCESS;
+ }
+ }
+
+ pj_mutex_unlock(mgr->mutex);
+
+ return PJMEDIA_CODEC_EUNSUP;
+}
+
+
+/*
+ * Convert codec info struct into a unique codec identifier.
+ * A codec identifier looks something like "H263/34".
+ */
+PJ_DEF(char*) pjmedia_vid_codec_info_to_id(
+ const pjmedia_vid_codec_info *info,
+ char *id, unsigned max_len )
+{
+ int len;
+
+ PJ_ASSERT_RETURN(info && id && max_len, NULL);
+
+ len = pj_ansi_snprintf(id, max_len, "%.*s/%u",
+ (int)info->encoding_name.slen,
+ info->encoding_name.ptr,
+ info->pt);
+
+ if (len < 1 || len >= (int)max_len) {
+ id[0] = '\0';
+ return NULL;
+ }
+
+ return id;
+}
+
+
+/*
+ * Find codecs by the unique codec identifier. This function will find
+ * all codecs that match the codec identifier prefix. For example, if
+ * "L16" is specified, then it will find "L16/8000/1", "L16/16000/1",
+ * and so on, up to the maximum count specified in the argument.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_find_codecs_by_id(
+ pjmedia_vid_codec_mgr *mgr,
+ const pj_str_t *codec_id,
+ unsigned *count,
+ const pjmedia_vid_codec_info *p_info[],
+ unsigned prio[])
+{
+ unsigned i, found = 0;
+
+ PJ_ASSERT_RETURN(codec_id && count && *count, PJ_EINVAL);
+
+ if (!mgr) mgr = def_vid_codec_mgr;
+ PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+ pj_mutex_lock(mgr->mutex);
+
+ for (i=0; i<mgr->codec_cnt; ++i) {
+
+ if (codec_id->slen == 0 ||
+ pj_strnicmp2(codec_id, mgr->codec_desc[i].id,
+ codec_id->slen) == 0)
+ {
+
+ if (p_info)
+ p_info[found] = &mgr->codec_desc[i].info;
+ if (prio)
+ prio[found] = mgr->codec_desc[i].prio;
+
+ ++found;
+
+ if (found >= *count)
+ break;
+ }
+
+ }
+
+ pj_mutex_unlock(mgr->mutex);
+
+ *count = found;
+
+ return found ? PJ_SUCCESS : PJ_ENOTFOUND;
+}
+
+
+/* Swap two codecs positions in codec manager */
+static void swap_codec(pjmedia_vid_codec_mgr *mgr, unsigned i, unsigned j)
+{
+ pjmedia_vid_codec_desc tmp;
+
+ pj_memcpy(&tmp, &mgr->codec_desc[i], sizeof(pjmedia_vid_codec_desc));
+
+ pj_memcpy(&mgr->codec_desc[i], &mgr->codec_desc[j],
+ sizeof(pjmedia_vid_codec_desc));
+
+ pj_memcpy(&mgr->codec_desc[j], &tmp, sizeof(pjmedia_vid_codec_desc));
+}
+
+
+/* Sort codecs in codec manager based on priorities */
+static void sort_codecs(pjmedia_vid_codec_mgr *mgr)
+{
+ unsigned i;
+
+ /* Re-sort */
+ for (i=0; i<mgr->codec_cnt; ++i) {
+ unsigned j, max;
+
+ for (max=i, j=i+1; j<mgr->codec_cnt; ++j) {
+ if (mgr->codec_desc[j].prio > mgr->codec_desc[max].prio)
+ max = j;
+ }
+
+ if (max != i)
+ swap_codec(mgr, i, max);
+ }
+
+ /* Change PJMEDIA_CODEC_PRIO_HIGHEST codecs to NEXT_HIGHER */
+ for (i=0; i<mgr->codec_cnt; ++i) {
+ if (mgr->codec_desc[i].prio == PJMEDIA_CODEC_PRIO_HIGHEST)
+ mgr->codec_desc[i].prio = PJMEDIA_CODEC_PRIO_NEXT_HIGHER;
+ else
+ break;
+ }
+}
+
+
+/**
+ * Set codec priority. The codec priority determines the order of
+ * the codec in the SDP created by the endpoint. If more than one codecs
+ * are found with the same codec_id prefix, then the function sets the
+ * priorities of all those codecs.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_set_codec_priority(
+ pjmedia_vid_codec_mgr *mgr,
+ const pj_str_t *codec_id,
+ pj_uint8_t prio)
+{
+ unsigned i, found = 0;
+
+ PJ_ASSERT_RETURN(codec_id, PJ_EINVAL);
+
+ if (!mgr) mgr = def_vid_codec_mgr;
+ PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+ pj_mutex_lock(mgr->mutex);
+
+ /* Update the priorities of affected codecs */
+ for (i=0; i<mgr->codec_cnt; ++i)
+ {
+ if (codec_id->slen == 0 ||
+ pj_strnicmp2(codec_id, mgr->codec_desc[i].id,
+ codec_id->slen) == 0)
+ {
+ mgr->codec_desc[i].prio = (pjmedia_codec_priority) prio;
+ ++found;
+ }
+ }
+
+ if (!found) {
+ pj_mutex_unlock(mgr->mutex);
+ return PJ_ENOTFOUND;
+ }
+
+ /* Re-sort codecs */
+ sort_codecs(mgr);
+
+ pj_mutex_unlock(mgr->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Allocate one codec.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_alloc_codec(
+ pjmedia_vid_codec_mgr *mgr,
+ const pjmedia_vid_codec_info *info,
+ pjmedia_vid_codec **p_codec)
+{
+ pjmedia_vid_codec_factory *factory;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(info && p_codec, PJ_EINVAL);
+
+ if (!mgr) mgr = def_vid_codec_mgr;
+ PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+ *p_codec = NULL;
+
+ pj_mutex_lock(mgr->mutex);
+
+ factory = mgr->factory_list.next;
+ while (factory != &mgr->factory_list) {
+
+ if ( (*factory->op->test_alloc)(factory, info) == PJ_SUCCESS ) {
+
+ status = (*factory->op->alloc_codec)(factory, info, p_codec);
+ if (status == PJ_SUCCESS) {
+ pj_mutex_unlock(mgr->mutex);
+ return PJ_SUCCESS;
+ }
+
+ }
+
+ factory = factory->next;
+ }
+
+ pj_mutex_unlock(mgr->mutex);
+
+ return PJMEDIA_CODEC_EUNSUP;
+}
+
+
+/*
+ * Get default codec parameter.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_get_default_param(
+ pjmedia_vid_codec_mgr *mgr,
+ const pjmedia_vid_codec_info *info,
+ pjmedia_vid_codec_param *param )
+{
+ pjmedia_vid_codec_factory *factory;
+ pj_status_t status;
+ pjmedia_codec_id codec_id;
+ pjmedia_vid_codec_desc *codec_desc = NULL;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(info && param, PJ_EINVAL);
+
+ if (!mgr) mgr = def_vid_codec_mgr;
+ PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+ if (!pjmedia_vid_codec_info_to_id(info, (char*)&codec_id,
+ sizeof(codec_id)))
+ return PJ_EINVAL;
+
+ pj_mutex_lock(mgr->mutex);
+
+ /* First, lookup default param in codec desc */
+ for (i=0; i < mgr->codec_cnt; ++i) {
+ if (pj_ansi_stricmp(codec_id, mgr->codec_desc[i].id) == 0) {
+ codec_desc = &mgr->codec_desc[i];
+ break;
+ }
+ }
+
+ /* If we found the codec and its default param is set, return it */
+ if (codec_desc && codec_desc->def_param) {
+ pj_memcpy(param, codec_desc->def_param->param,
+ sizeof(pjmedia_vid_codec_param));
+
+ pj_mutex_unlock(mgr->mutex);
+ return PJ_SUCCESS;
+ }
+
+ /* Otherwise query the default param from codec factory */
+ factory = mgr->factory_list.next;
+ while (factory != &mgr->factory_list) {
+
+ if ( (*factory->op->test_alloc)(factory, info) == PJ_SUCCESS ) {
+
+ status = (*factory->op->default_attr)(factory, info, param);
+ if (status == PJ_SUCCESS) {
+ /* Check for invalid max_bps. */
+ //if (param->info.max_bps < param->info.avg_bps)
+ // param->info.max_bps = param->info.avg_bps;
+
+ pj_mutex_unlock(mgr->mutex);
+ return PJ_SUCCESS;
+ }
+
+ }
+
+ factory = factory->next;
+ }
+
+ pj_mutex_unlock(mgr->mutex);
+
+
+ return PJMEDIA_CODEC_EUNSUP;
+}
+
+
+/*
+ * Set default codec parameter.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_set_default_param(
+ pjmedia_vid_codec_mgr *mgr,
+ const pjmedia_vid_codec_info *info,
+ const pjmedia_vid_codec_param *param )
+{
+ unsigned i;
+ pjmedia_codec_id codec_id;
+ pjmedia_vid_codec_desc *codec_desc = NULL;
+ pj_pool_t *pool, *old_pool = NULL;
+ pjmedia_vid_codec_default_param *p;
+
+ PJ_ASSERT_RETURN(info, PJ_EINVAL);
+
+ if (!mgr) mgr = def_vid_codec_mgr;
+ PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+ if (!pjmedia_vid_codec_info_to_id(info, (char*)&codec_id, sizeof(codec_id)))
+ return PJ_EINVAL;
+
+ pj_mutex_lock(mgr->mutex);
+
+ /* Lookup codec desc */
+ for (i=0; i < mgr->codec_cnt; ++i) {
+ if (pj_ansi_stricmp(codec_id, mgr->codec_desc[i].id) == 0) {
+ codec_desc = &mgr->codec_desc[i];
+ break;
+ }
+ }
+
+ /* Codec not found */
+ if (!codec_desc) {
+ pj_mutex_unlock(mgr->mutex);
+ return PJMEDIA_CODEC_EUNSUP;
+ }
+
+ /* If codec param is previously set */
+ if (codec_desc->def_param) {
+ pj_assert(codec_desc->def_param->pool);
+ old_pool = codec_desc->def_param->pool;
+ codec_desc->def_param = NULL;
+ }
+
+ /* When param is set to NULL, i.e: setting default codec param to library
+ * default setting, just return PJ_SUCCESS.
+ */
+ if (NULL == param) {
+ pj_mutex_unlock(mgr->mutex);
+ if (old_pool)
+ pj_pool_release(old_pool);
+ return PJ_SUCCESS;
+ }
+
+ /* Create new default codec param instance */
+ pool = pj_pool_create(mgr->pf, (char*)codec_id, 256, 256, NULL);
+ codec_desc->def_param = PJ_POOL_ZALLOC_T(pool,
+ pjmedia_vid_codec_default_param);
+ p = codec_desc->def_param;
+ p->pool = pool;
+
+ /* Update codec default param */
+ p->param = pjmedia_vid_codec_param_clone(pool, param);
+ if (!p->param)
+ return PJ_EINVAL;
+
+ codec_desc->def_param = p;
+
+ pj_mutex_unlock(mgr->mutex);
+
+ /* Release old pool at the very end, as application tends to apply changes
+ * to the existing/old codec param fetched using
+ * pjmedia_vid_codec_mgr_get_default_param() which doesn't do deep clone.
+ */
+ if (old_pool)
+ pj_pool_release(old_pool);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Dealloc codec.
+ */
+PJ_DEF(pj_status_t)
+pjmedia_vid_codec_mgr_dealloc_codec(pjmedia_vid_codec_mgr *mgr,
+ pjmedia_vid_codec *codec)
+{
+ PJ_ASSERT_RETURN(codec, PJ_EINVAL);
+
+ if (!mgr) mgr = def_vid_codec_mgr;
+ PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+ return (*codec->factory->op->dealloc_codec)(codec->factory, codec);
+}
+
+
+#endif /* PJMEDIA_HAS_VIDEO */
diff --git a/pjmedia/src/pjmedia/vid_codec_util.c b/pjmedia/src/pjmedia/vid_codec_util.c
new file mode 100644
index 0000000..e51fff4
--- /dev/null
+++ b/pjmedia/src/pjmedia/vid_codec_util.c
@@ -0,0 +1,646 @@
+/* $Id: vid_codec_util.c 3995 2012-03-29 10:54:01Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/vid_codec_util.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/stream_common.h>
+#include <pjlib-util/base64.h>
+#include <pj/ctype.h>
+#include <pj/math.h>
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+
+#define THIS_FILE "vid_codec_util.c"
+
+/* If this is set to non-zero, H.264 custom negotiation will require
+ * "profile-level-id" and "packetization-mode" to be exact match to
+ * get a successful negotiation. Note that flexible answer (updating
+ * SDP answer to match remote offer) is always active regardless the
+ * value of this macro.
+ */
+#define H264_STRICT_SDP_NEGO 0
+
+
+/* ITU resolution definition */
+struct mpi_resolution_t
+{
+ pj_str_t name;
+ pjmedia_rect_size size;
+}
+mpi_resolutions [] =
+{
+ {{"CIF",3}, {352,288}},
+ {{"QCIF",4}, {176,144}},
+ {{"SQCIF",5}, {88,72}},
+ {{"CIF4",4}, {704,576}},
+ {{"CIF16",5}, {1408,1142}},
+};
+
+
+/* Parse fmtp value for custom resolution, e.g: "CUSTOM=800,600,2" */
+static pj_status_t parse_custom_res_fmtp(const pj_str_t *fmtp_val,
+ pjmedia_rect_size *size,
+ unsigned *mpi)
+{
+ const char *p, *p_end;
+ pj_str_t token;
+ unsigned long val[3] = {0};
+ unsigned i = 0;
+
+ p = token.ptr = fmtp_val->ptr;
+ p_end = p + fmtp_val->slen;
+
+ while (p<=p_end && i<PJ_ARRAY_SIZE(val)) {
+ if (*p==',' || p==p_end) {
+ token.slen = (char*)p - token.ptr;
+ val[i++] = pj_strtoul(&token);
+ token.ptr = (char*)p+1;
+ }
+ ++p;
+ }
+
+ if (!val[0] || !val[1])
+ return PJ_ETOOSMALL;
+
+ if (val[2]<1 || val[2]>32)
+ return PJ_EINVAL;
+
+ size->w = val[0];
+ size->h = val[1];
+ *mpi = val[2];
+ return PJ_SUCCESS;
+}
+
+
+/* H263 fmtp parser */
+PJ_DEF(pj_status_t) pjmedia_vid_codec_parse_h263_fmtp(
+ const pjmedia_codec_fmtp *fmtp,
+ pjmedia_vid_codec_h263_fmtp *h263_fmtp)
+{
+ const pj_str_t CUSTOM = {"CUSTOM", 6};
+ unsigned i;
+
+ pj_bzero(h263_fmtp, sizeof(*h263_fmtp));
+
+ for (i=0; i<fmtp->cnt; ++i) {
+ unsigned j;
+ pj_bool_t parsed = PJ_FALSE;
+
+ if (h263_fmtp->mpi_cnt >= PJ_ARRAY_SIZE(h263_fmtp->mpi)) {
+ pj_assert(!"Too small MPI array in H263 fmtp");
+ continue;
+ }
+
+ /* Standard size MPIs */
+ for (j=0; j<PJ_ARRAY_SIZE(mpi_resolutions) && !parsed; ++j) {
+ if (pj_stricmp(&fmtp->param[i].name, &mpi_resolutions[j].name)==0)
+ {
+ unsigned mpi;
+
+ mpi = pj_strtoul(&fmtp->param[i].val);
+ if (mpi<1 || mpi>32)
+ return PJMEDIA_SDP_EINFMTP;
+
+ h263_fmtp->mpi[h263_fmtp->mpi_cnt].size =
+ mpi_resolutions[j].size;
+ h263_fmtp->mpi[h263_fmtp->mpi_cnt].val = mpi;
+ ++h263_fmtp->mpi_cnt;
+ parsed = PJ_TRUE;
+ }
+ }
+ if (parsed)
+ continue;
+
+ /* Custom size MPIs */
+ if (pj_stricmp(&fmtp->param[i].name, &CUSTOM)==0) {
+ pjmedia_rect_size size;
+ unsigned mpi;
+ pj_status_t status;
+
+ status = parse_custom_res_fmtp(&fmtp->param[i].val, &size, &mpi);
+ if (status != PJ_SUCCESS)
+ return PJMEDIA_SDP_EINFMTP;
+
+ h263_fmtp->mpi[h263_fmtp->mpi_cnt].size = size;
+ h263_fmtp->mpi[h263_fmtp->mpi_cnt].val = mpi;
+ ++h263_fmtp->mpi_cnt;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+static unsigned fps_to_mpi(const pjmedia_ratio *fps)
+{
+ unsigned mpi;
+
+ /* Original formula = (fps->denum * 30000) / (fps->num * 1001) */
+ mpi = (fps->denum*30000 + fps->num*1001/2) / (fps->num*1001);
+
+ /* Normalize, should be in the range of 1-32 */
+ if (mpi > 32) mpi = 32;
+ if (mpi < 1) mpi = 1;
+
+ return mpi;
+};
+
+PJ_DEF(pj_status_t) pjmedia_vid_codec_h263_apply_fmtp(
+ pjmedia_vid_codec_param *param)
+{
+ if (param->dir & PJMEDIA_DIR_ENCODING) {
+ pjmedia_vid_codec_h263_fmtp fmtp_loc, fmtp_rem;
+ pjmedia_rect_size size = {0};
+ unsigned mpi = 0;
+ pjmedia_video_format_detail *vfd;
+ pj_status_t status;
+
+ vfd = pjmedia_format_get_video_format_detail(&param->enc_fmt,
+ PJ_TRUE);
+
+ /* Get local param */
+ // Local param should be fetched from "param->enc_fmt" instead of
+ // "param->dec_fmtp".
+ //status = pjmedia_vid_codec_parse_h263_fmtp(&param->dec_fmtp,
+ // &fmtp_loc);
+ //if (status != PJ_SUCCESS)
+ // return status;
+ fmtp_loc.mpi_cnt = 1;
+ fmtp_loc.mpi[0].size = vfd->size;
+ fmtp_loc.mpi[0].val = fps_to_mpi(&vfd->fps);
+
+ /* Get remote param */
+ status = pjmedia_vid_codec_parse_h263_fmtp(&param->enc_fmtp,
+ &fmtp_rem);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Negotiate size & MPI setting */
+ if (fmtp_rem.mpi_cnt == 0) {
+ /* Remote doesn't specify MPI setting, send QCIF=1 */
+ size.w = 176;
+ size.h = 144;
+ mpi = 1;
+ //} else if (fmtp_loc.mpi_cnt == 0) {
+ // /* Local MPI setting not set, just use remote preference. */
+ // size = fmtp_rem.mpi[0].size;
+ // mpi = fmtp_rem.mpi[0].val;
+ } else {
+ /* Both have preferences, let's try to match them */
+ unsigned i, j;
+ pj_bool_t matched = PJ_FALSE;
+ pj_uint32_t min_diff = 0xFFFFFFFF;
+ pj_uint32_t loc_sq, rem_sq, diff;
+
+ /* Find the exact size match or the closest size, then choose
+ * the highest MPI among the match/closest pair.
+ */
+ for (i = 0; i < fmtp_rem.mpi_cnt && !matched; ++i) {
+ rem_sq = fmtp_rem.mpi[i].size.w * fmtp_rem.mpi[i].size.h;
+ for (j = 0; j < fmtp_loc.mpi_cnt; ++j) {
+ /* See if we got exact match */
+ if (fmtp_rem.mpi[i].size.w == fmtp_loc.mpi[j].size.w &&
+ fmtp_rem.mpi[i].size.h == fmtp_loc.mpi[j].size.h)
+ {
+ size = fmtp_rem.mpi[i].size;
+ mpi = PJ_MAX(fmtp_rem.mpi[i].val,
+ fmtp_loc.mpi[j].val);
+ matched = PJ_TRUE;
+ break;
+ }
+
+ /* Otherwise keep looking for the closest match */
+ loc_sq = fmtp_loc.mpi[j].size.w * fmtp_loc.mpi[j].size.h;
+ diff = loc_sq>rem_sq? (loc_sq-rem_sq):(rem_sq-loc_sq);
+ if (diff < min_diff) {
+ size = rem_sq<loc_sq? fmtp_rem.mpi[i].size :
+ fmtp_loc.mpi[j].size;
+ mpi = PJ_MAX(fmtp_rem.mpi[i].val,
+ fmtp_loc.mpi[j].val);
+ }
+ }
+ }
+ }
+
+ /* Apply the negotiation result */
+ vfd->size = size;
+ vfd->fps.num = 30000;
+ vfd->fps.denum = 1001 * mpi;
+ }
+
+ if (param->dir & PJMEDIA_DIR_DECODING) {
+ /* Here we just want to find the highest resolution and the lowest MPI
+ * we support and set it as the decoder param.
+ */
+ pjmedia_vid_codec_h263_fmtp fmtp;
+ pjmedia_video_format_detail *vfd;
+ pj_status_t status;
+
+ status = pjmedia_vid_codec_parse_h263_fmtp(&param->dec_fmtp,
+ &fmtp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ vfd = pjmedia_format_get_video_format_detail(&param->dec_fmt,
+ PJ_TRUE);
+
+ if (fmtp.mpi_cnt == 0) {
+ /* No resolution specified, lets just assume 4CIF=1! */
+ vfd->size.w = 704;
+ vfd->size.h = 576;
+ vfd->fps.num = 30000;
+ vfd->fps.denum = 1001;
+ } else {
+ unsigned i, max_size = 0, max_size_idx = 0, min_mpi = 32;
+
+ /* Get the largest size and the lowest MPI */
+ for (i = 0; i < fmtp.mpi_cnt; ++i) {
+ if (fmtp.mpi[i].size.w * fmtp.mpi[i].size.h > max_size) {
+ max_size = fmtp.mpi[i].size.w * fmtp.mpi[i].size.h;
+ max_size_idx = i;
+ }
+ if (fmtp.mpi[i].val < min_mpi)
+ min_mpi = fmtp.mpi[i].val;
+ }
+
+ vfd->size = fmtp.mpi[max_size_idx].size;
+ vfd->fps.num = 30000;
+ vfd->fps.denum = 1001 * min_mpi;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* H264 fmtp parser */
+PJ_DEF(pj_status_t) pjmedia_vid_codec_h264_parse_fmtp(
+ const pjmedia_codec_fmtp *fmtp,
+ pjmedia_vid_codec_h264_fmtp *h264_fmtp)
+{
+ const pj_str_t PROFILE_LEVEL_ID = {"profile-level-id", 16};
+ const pj_str_t MAX_MBPS = {"max-mbps", 8};
+ const pj_str_t MAX_FS = {"max-fs", 6};
+ const pj_str_t MAX_CPB = {"max-cpb", 7};
+ const pj_str_t MAX_DPB = {"max-dpb", 7};
+ const pj_str_t MAX_BR = {"max-br", 6};
+ const pj_str_t PACKETIZATION_MODE = {"packetization-mode", 18};
+ const pj_str_t SPROP_PARAMETER_SETS = {"sprop-parameter-sets", 20};
+ unsigned i;
+
+ pj_bzero(h264_fmtp, sizeof(*h264_fmtp));
+
+ for (i=0; i<fmtp->cnt; ++i) {
+ unsigned tmp;
+ if (pj_stricmp(&fmtp->param[i].name, &PROFILE_LEVEL_ID)==0) {
+ pj_str_t endst;
+
+ if (fmtp->param[i].val.slen != 6)
+ return PJMEDIA_SDP_EINFMTP;
+
+ tmp = pj_strtoul2(&fmtp->param[i].val, &endst, 16);
+ if (endst.slen)
+ return PJMEDIA_SDP_EINFMTP;
+
+ h264_fmtp->profile_idc = (pj_uint8_t)((tmp >> 16) & 0xFF);
+ h264_fmtp->profile_iop = (pj_uint8_t)((tmp >> 8) & 0xFF);
+ h264_fmtp->level = (pj_uint8_t)(tmp & 0xFF);
+ } else if (pj_stricmp(&fmtp->param[i].name, &PACKETIZATION_MODE)==0) {
+ tmp = pj_strtoul(&fmtp->param[i].val);
+ if (tmp >= 0 && tmp <= 2)
+ h264_fmtp->packetization_mode = (pj_uint8_t)tmp;
+ else
+ return PJMEDIA_SDP_EINFMTP;
+ } else if (pj_stricmp(&fmtp->param[i].name, &MAX_MBPS)==0) {
+ tmp = pj_strtoul(&fmtp->param[i].val);
+ h264_fmtp->max_mbps = tmp;
+ } else if (pj_stricmp(&fmtp->param[i].name, &MAX_FS)==0) {
+ tmp = pj_strtoul(&fmtp->param[i].val);
+ h264_fmtp->max_fs = tmp;
+ } else if (pj_stricmp(&fmtp->param[i].name, &MAX_CPB)==0) {
+ tmp = pj_strtoul(&fmtp->param[i].val);
+ h264_fmtp->max_cpb = tmp;
+ } else if (pj_stricmp(&fmtp->param[i].name, &MAX_DPB)==0) {
+ tmp = pj_strtoul(&fmtp->param[i].val);
+ h264_fmtp->max_dpb = tmp;
+ } else if (pj_stricmp(&fmtp->param[i].name, &MAX_BR)==0) {
+ tmp = pj_strtoul(&fmtp->param[i].val);
+ h264_fmtp->max_br = tmp;
+ } else if (pj_stricmp(&fmtp->param[i].name, &SPROP_PARAMETER_SETS)==0)
+ {
+ pj_str_t sps_st;
+
+ sps_st = fmtp->param[i].val;
+ while (sps_st.slen) {
+ pj_str_t tmp_st;
+ int tmp_len;
+ const pj_uint8_t start_code[3] = {0, 0, 1};
+ char *p;
+ pj_uint8_t *nal;
+ pj_status_t status;
+
+ /* Find field separator ',' */
+ tmp_st = sps_st;
+ p = pj_strchr(&sps_st, ',');
+ if (p) {
+ tmp_st.slen = p - sps_st.ptr;
+ sps_st.ptr = p+1;
+ sps_st.slen -= (tmp_st.slen+1);
+ } else {
+ sps_st.slen = 0;
+ }
+
+ /* Decode field and build NAL unit for this param */
+ nal = &h264_fmtp->sprop_param_sets[
+ h264_fmtp->sprop_param_sets_len];
+ tmp_len = PJ_ARRAY_SIZE(h264_fmtp->sprop_param_sets) -
+ h264_fmtp->sprop_param_sets_len -
+ PJ_ARRAY_SIZE(start_code);
+ status = pj_base64_decode(&tmp_st,
+ nal + PJ_ARRAY_SIZE(start_code),
+ &tmp_len);
+ if (status != PJ_SUCCESS)
+ return PJMEDIA_SDP_EINFMTP;
+
+ tmp_len += PJ_ARRAY_SIZE(start_code);
+ pj_memcpy(nal, start_code, PJ_ARRAY_SIZE(start_code));
+ h264_fmtp->sprop_param_sets_len += tmp_len;
+ }
+ }
+ }
+
+ /* When profile-level-id is not specified, use default value "42000A" */
+ if (h264_fmtp->profile_idc == 0) {
+ h264_fmtp->profile_idc = 0x42;
+ h264_fmtp->profile_iop = 0x00;
+ h264_fmtp->level = 0x0A;
+ }
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_vid_codec_h264_match_sdp(pj_pool_t *pool,
+ pjmedia_sdp_media *offer,
+ unsigned o_fmt_idx,
+ pjmedia_sdp_media *answer,
+ unsigned a_fmt_idx,
+ unsigned option)
+{
+ const pj_str_t PROFILE_LEVEL_ID = {"profile-level-id", 16};
+ const pj_str_t PACKETIZATION_MODE = {"packetization-mode", 18};
+ pjmedia_codec_fmtp o_fmtp_raw, a_fmtp_raw;
+ pjmedia_vid_codec_h264_fmtp o_fmtp, a_fmtp;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(pool);
+
+ /* Parse offer */
+ status = pjmedia_stream_info_parse_fmtp(
+ NULL, offer,
+ pj_strtoul(&offer->desc.fmt[o_fmt_idx]),
+ &o_fmtp_raw);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = pjmedia_vid_codec_h264_parse_fmtp(&o_fmtp_raw, &o_fmtp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Parse answer */
+ status = pjmedia_stream_info_parse_fmtp(
+ NULL, answer,
+ pj_strtoul(&answer->desc.fmt[a_fmt_idx]),
+ &a_fmtp_raw);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = pjmedia_vid_codec_h264_parse_fmtp(&a_fmtp_raw, &a_fmtp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (option & PJMEDIA_SDP_NEG_FMT_MATCH_ALLOW_MODIFY_ANSWER) {
+ unsigned i;
+
+ /* Flexible negotiation, if the answer has higher capability than
+ * the offer, adjust the answer capability to be match to the offer.
+ */
+ if (a_fmtp.profile_idc >= o_fmtp.profile_idc)
+ a_fmtp.profile_idc = o_fmtp.profile_idc;
+ if (a_fmtp.profile_iop != o_fmtp.profile_iop)
+ a_fmtp.profile_iop = o_fmtp.profile_iop;
+ if (a_fmtp.level >= o_fmtp.level)
+ a_fmtp.level = o_fmtp.level;
+ if (a_fmtp.packetization_mode >= o_fmtp.packetization_mode)
+ a_fmtp.packetization_mode = o_fmtp.packetization_mode;
+
+ /* Match them now */
+#if H264_STRICT_SDP_NEGO
+ if (a_fmtp.profile_idc != o_fmtp.profile_idc ||
+ a_fmtp.profile_iop != o_fmtp.profile_iop ||
+ a_fmtp.level != o_fmtp.level ||
+ a_fmtp.packetization_mode != o_fmtp.packetization_mode)
+ {
+ return PJMEDIA_SDP_EFORMATNOTEQUAL;
+ }
+#else
+ if (a_fmtp.profile_idc != o_fmtp.profile_idc)
+ {
+ return PJMEDIA_SDP_EFORMATNOTEQUAL;
+ }
+#endif
+
+ /* Update the answer */
+ for (i = 0; i < a_fmtp_raw.cnt; ++i) {
+ if (pj_stricmp(&a_fmtp_raw.param[i].name, &PROFILE_LEVEL_ID) == 0)
+ {
+ char *p = a_fmtp_raw.param[i].val.ptr;
+ pj_val_to_hex_digit(a_fmtp.profile_idc, p);
+ p += 2;
+ pj_val_to_hex_digit(a_fmtp.profile_iop, p);
+ p += 2;
+ pj_val_to_hex_digit(a_fmtp.level, p);
+ }
+ else if (pj_stricmp(&a_fmtp_raw.param[i].name, &PACKETIZATION_MODE) == 0)
+ {
+ char *p = a_fmtp_raw.param[i].val.ptr;
+ *p = '0' + a_fmtp.packetization_mode;
+ }
+ }
+ } else {
+#if H264_STRICT_SDP_NEGO
+ /* Strict negotiation */
+ if (a_fmtp.profile_idc != o_fmtp.profile_idc ||
+ a_fmtp.profile_iop != o_fmtp.profile_iop ||
+ a_fmtp.level != o_fmtp.level ||
+ a_fmtp.packetization_mode != o_fmtp.packetization_mode)
+ {
+ return PJMEDIA_SDP_EFORMATNOTEQUAL;
+ }
+#else
+ /* Permissive negotiation */
+ if (a_fmtp.profile_idc != o_fmtp.profile_idc)
+ {
+ return PJMEDIA_SDP_EFORMATNOTEQUAL;
+ }
+#endif
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* Declaration of H.264 level info */
+typedef struct h264_level_info_t
+{
+ unsigned id; /* Level id. */
+ unsigned max_mbps; /* Max macroblocks per second. */
+ unsigned max_mb; /* Max macroblocks. */
+ unsigned bitrate; /* Max bitrate (kbps). */
+ unsigned def_w; /* Default width. */
+ unsigned def_h; /* Default height. */
+ unsigned def_fps; /* Default fps. */
+} h264_level_info_t;
+
+
+/* Get H.264 level info from specified level ID */
+static pj_status_t get_h264_level_info(unsigned id, h264_level_info_t *level)
+{
+ unsigned i;
+ const h264_level_info_t level_info[] =
+ {
+ { 10, 1485, 99, 64, 176, 144, 15 },
+ { 9, 1485, 99, 128, 176, 144, 15 }, /*< level 1b */
+ { 11, 3000, 396, 192, 320, 240, 10 },
+ { 12, 6000, 396, 384, 352, 288, 15 },
+ { 13, 11880, 396, 768, 352, 288, 15 },
+ { 20, 11880, 396, 2000, 352, 288, 30 },
+ { 21, 19800, 792, 4000, 352, 288, 30 },
+ { 22, 20250, 1620, 4000, 352, 288, 30 },
+ { 30, 40500, 1620, 10000, 720, 480, 30 },
+ { 31, 108000, 3600, 14000, 1280, 720, 30 },
+ { 32, 216000, 5120, 20000, 1280, 720, 30 },
+ { 40, 245760, 8192, 20000, 1920, 1080, 30 },
+ { 41, 245760, 8192, 50000, 1920, 1080, 30 },
+ { 42, 522240, 8704, 50000, 1920, 1080, 30 },
+ { 50, 589824, 22080, 135000, 1920, 1080, 30 },
+ { 51, 983040, 36864, 240000, 1920, 1080, 30 },
+ };
+
+ for (i = 0; i < PJ_ARRAY_SIZE(level_info); ++i) {
+ if (level_info[i].id == id) {
+ *level = level_info[i];
+ return PJ_SUCCESS;
+ }
+ }
+ return PJ_ENOTFOUND;
+}
+
+
+#define CALC_H264_MB_NUM(size) (((size.w+15)/16)*((size.h+15)/16))
+#define CALC_H264_MBPS(size,fps) CALC_H264_MB_NUM(size)*fps.num/fps.denum
+
+
+PJ_DEF(pj_status_t) pjmedia_vid_codec_h264_apply_fmtp(
+ pjmedia_vid_codec_param *param)
+{
+ const unsigned default_fps = 30;
+
+ if (param->dir & PJMEDIA_DIR_ENCODING) {
+ pjmedia_vid_codec_h264_fmtp fmtp;
+ pjmedia_video_format_detail *vfd;
+ h264_level_info_t level_info;
+ pj_status_t status;
+
+ /* Get remote param */
+ status = pjmedia_vid_codec_h264_parse_fmtp(&param->enc_fmtp,
+ &fmtp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = get_h264_level_info(fmtp.level, &level_info);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Size and fps for encoding direction must conform to H.264 level
+ * specified by remote SDP fmtp.
+ */
+ vfd = pjmedia_format_get_video_format_detail(&param->enc_fmt,
+ PJ_TRUE);
+ if (vfd->size.w && vfd->size.h) {
+ unsigned mb, mbps;
+
+ if (vfd->fps.num == 0 || vfd->fps.denum == 0) {
+ vfd->fps.num = default_fps;
+ vfd->fps.denum = 1;
+ }
+ mb = CALC_H264_MB_NUM(vfd->size);
+ mbps = CALC_H264_MBPS(vfd->size, vfd->fps);
+ if (mb > level_info.max_mb || mbps > level_info.max_mbps) {
+ vfd->size.w = level_info.def_w;
+ vfd->size.h = level_info.def_h;
+ vfd->fps.num = level_info.def_fps;
+ vfd->fps.denum = 1;
+ }
+ } else {
+ vfd->size.w = level_info.def_w;
+ vfd->size.h = level_info.def_h;
+ vfd->fps.num = level_info.def_fps;
+ vfd->fps.denum = 1;
+ }
+ }
+
+ if (param->dir & PJMEDIA_DIR_DECODING) {
+ /* Here we just want to find the highest resolution possible from the
+ * fmtp and set it as the decoder param.
+ */
+ pjmedia_vid_codec_h264_fmtp fmtp;
+ pjmedia_video_format_detail *vfd;
+ h264_level_info_t level_info;
+ pj_status_t status;
+
+ status = pjmedia_vid_codec_h264_parse_fmtp(&param->dec_fmtp,
+ &fmtp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = get_h264_level_info(fmtp.level, &level_info);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ vfd = pjmedia_format_get_video_format_detail(&param->dec_fmt,
+ PJ_TRUE);
+
+ if (vfd->size.w * vfd->size.h < level_info.def_w * level_info.def_h) {
+ vfd->size.w = level_info.def_w;
+ vfd->size.h = level_info.def_h;
+ }
+
+ if (vfd->fps.num == 0 || vfd->fps.denum == 0) {
+ vfd->fps.num = default_fps;
+ vfd->fps.denum = 1;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+#endif /* PJMEDIA_HAS_VIDEO */
diff --git a/pjmedia/src/pjmedia/vid_port.c b/pjmedia/src/pjmedia/vid_port.c
new file mode 100644
index 0000000..289e3e5
--- /dev/null
+++ b/pjmedia/src/pjmedia/vid_port.c
@@ -0,0 +1,971 @@
+/* $Id: vid_port.c 4168 2012-06-18 05:59:08Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia/vid_port.h>
+#include <pjmedia/clock.h>
+#include <pjmedia/converter.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/event.h>
+#include <pjmedia/vid_codec.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+
+#define SIGNATURE PJMEDIA_SIG_VID_PORT
+#define THIS_FILE "vid_port.c"
+
+typedef struct vid_pasv_port vid_pasv_port;
+
+enum role
+{
+ ROLE_NONE,
+ ROLE_ACTIVE,
+ ROLE_PASSIVE
+};
+
+struct pjmedia_vid_port
+{
+ pj_pool_t *pool;
+ pj_str_t dev_name;
+ pjmedia_dir dir;
+// pjmedia_rect_size cap_size;
+ pjmedia_vid_dev_stream *strm;
+ pjmedia_vid_dev_cb strm_cb;
+ void *strm_cb_data;
+ enum role role,
+ stream_role;
+ vid_pasv_port *pasv_port;
+ pjmedia_port *client_port;
+ pj_bool_t destroy_client_port;
+
+ struct {
+ pjmedia_converter *conv;
+ void *conv_buf;
+ pj_size_t conv_buf_size;
+ pjmedia_conversion_param conv_param;
+ unsigned usec_ctr;
+ unsigned usec_src, usec_dst;
+ } conv;
+
+ pjmedia_clock *clock;
+ pjmedia_clock_src 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;
+ } sync_clocksrc;
+
+ pjmedia_frame *frm_buf;
+ pj_size_t frm_buf_size;
+ pj_mutex_t *frm_mutex;
+};
+
+struct vid_pasv_port
+{
+ pjmedia_port base;
+ pjmedia_vid_port *vp;
+};
+
+static pj_status_t vidstream_cap_cb(pjmedia_vid_dev_stream *stream,
+ void *user_data,
+ pjmedia_frame *frame);
+static pj_status_t vidstream_render_cb(pjmedia_vid_dev_stream *stream,
+ void *user_data,
+ pjmedia_frame *frame);
+static pj_status_t vidstream_event_cb(pjmedia_event *event,
+ void *user_data);
+static pj_status_t client_port_event_cb(pjmedia_event *event,
+ void *user_data);
+
+static void enc_clock_cb(const pj_timestamp *ts, void *user_data);
+static void dec_clock_cb(const pj_timestamp *ts, void *user_data);
+
+static pj_status_t vid_pasv_port_put_frame(struct pjmedia_port *this_port,
+ pjmedia_frame *frame);
+
+static pj_status_t vid_pasv_port_get_frame(struct pjmedia_port *this_port,
+ pjmedia_frame *frame);
+
+
+PJ_DEF(void) pjmedia_vid_port_param_default(pjmedia_vid_port_param *prm)
+{
+ pj_bzero(prm, sizeof(*prm));
+ prm->active = PJ_TRUE;
+}
+
+static const char *vid_dir_name(pjmedia_dir dir)
+{
+ switch (dir) {
+ case PJMEDIA_DIR_CAPTURE:
+ return "capture";
+ case PJMEDIA_DIR_RENDER:
+ return "render";
+ default:
+ return "??";
+ }
+}
+
+static pj_status_t create_converter(pjmedia_vid_port *vp)
+{
+ if (vp->conv.conv) {
+ pjmedia_converter_destroy(vp->conv.conv);
+ vp->conv.conv = NULL;
+ }
+
+ /* Instantiate converter if necessary */
+ if (vp->conv.conv_param.src.id != vp->conv.conv_param.dst.id ||
+ (vp->conv.conv_param.src.det.vid.size.w !=
+ vp->conv.conv_param.dst.det.vid.size.w) ||
+ (vp->conv.conv_param.src.det.vid.size.h !=
+ vp->conv.conv_param.dst.det.vid.size.h))
+ {
+ pj_status_t status;
+
+ /* Yes, we need converter */
+ status = pjmedia_converter_create(NULL, vp->pool, &vp->conv.conv_param,
+ &vp->conv.conv);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4,(THIS_FILE, status, "Error creating converter"));
+ return status;
+ }
+ }
+
+ if (vp->conv.conv ||
+ (vp->role==ROLE_ACTIVE && (vp->dir & PJMEDIA_DIR_ENCODING)))
+ {
+ pj_status_t status;
+ const pjmedia_video_format_info *vfi;
+ pjmedia_video_apply_fmt_param vafp;
+
+ /* Allocate buffer for conversion */
+ vfi = pjmedia_get_video_format_info(NULL, vp->conv.conv_param.dst.id);
+ if (!vfi)
+ return PJMEDIA_EBADFMT;
+
+ pj_bzero(&vafp, sizeof(vafp));
+ vafp.size = vp->conv.conv_param.dst.det.vid.size;
+ status = vfi->apply_fmt(vfi, &vafp);
+ if (status != PJ_SUCCESS)
+ return PJMEDIA_EBADFMT;
+
+ if (vafp.framebytes > vp->conv.conv_buf_size) {
+ vp->conv.conv_buf = pj_pool_alloc(vp->pool, vafp.framebytes);
+ vp->conv.conv_buf_size = vafp.framebytes;
+ }
+ }
+
+ vp->conv.usec_ctr = 0;
+ vp->conv.usec_src = PJMEDIA_PTIME(&vp->conv.conv_param.src.det.vid.fps);
+ vp->conv.usec_dst = PJMEDIA_PTIME(&vp->conv.conv_param.dst.det.vid.fps);
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_vid_port_create( pj_pool_t *pool,
+ const pjmedia_vid_port_param *prm,
+ pjmedia_vid_port **p_vid_port)
+{
+ pjmedia_vid_port *vp;
+ const pjmedia_video_format_detail *vfd;
+ char dev_name[64];
+ char fmt_name[5];
+ pjmedia_vid_dev_cb vid_cb;
+ pj_bool_t need_frame_buf = PJ_FALSE;
+ pj_status_t status;
+ unsigned ptime_usec;
+ pjmedia_vid_dev_param vparam;
+ pjmedia_vid_dev_info di;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(pool && prm && p_vid_port, PJ_EINVAL);
+ PJ_ASSERT_RETURN(prm->vidparam.fmt.type == PJMEDIA_TYPE_VIDEO &&
+ prm->vidparam.dir != PJMEDIA_DIR_NONE &&
+ prm->vidparam.dir != PJMEDIA_DIR_CAPTURE_RENDER,
+ PJ_EINVAL);
+
+ /* Retrieve the video format detail */
+ vfd = pjmedia_format_get_video_format_detail(&prm->vidparam.fmt, PJ_TRUE);
+ if (!vfd)
+ return PJ_EINVAL;
+
+ PJ_ASSERT_RETURN(vfd->fps.num, PJ_EINVAL);
+
+ /* Allocate videoport */
+ vp = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_port);
+ vp->pool = pj_pool_create(pool->factory, "video port", 500, 500, NULL);
+ vp->role = prm->active ? ROLE_ACTIVE : ROLE_PASSIVE;
+ vp->dir = prm->vidparam.dir;
+// vp->cap_size = vfd->size;
+
+ vparam = prm->vidparam;
+ dev_name[0] = '\0';
+
+ /* Get device info */
+ if (vp->dir & PJMEDIA_DIR_CAPTURE)
+ status = pjmedia_vid_dev_get_info(prm->vidparam.cap_id, &di);
+ else
+ status = pjmedia_vid_dev_get_info(prm->vidparam.rend_id, &di);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_ansi_snprintf(dev_name, sizeof(dev_name), "%s [%s]",
+ di.name, di.driver);
+
+ for (i = 0; i < di.fmt_cnt; ++i) {
+ if (prm->vidparam.fmt.id == di.fmt[i].id)
+ break;
+ }
+
+ if (i == di.fmt_cnt) {
+ /* The device has no no matching format. Pick one from
+ * the supported formats, and later use converter to
+ * convert it to the required format.
+ */
+ pj_assert(di.fmt_cnt != 0);
+ vparam.fmt.id = di.fmt[0].id;
+ }
+
+ pj_strdup2_with_null(pool, &vp->dev_name, di.name);
+ vp->stream_role = di.has_callback ? ROLE_ACTIVE : ROLE_PASSIVE;
+
+ pjmedia_fourcc_name(vparam.fmt.id, fmt_name);
+
+ PJ_LOG(4,(THIS_FILE,
+ "Opening device %s for %s: format=%s, size=%dx%d @%d:%d fps",
+ dev_name,
+ vid_dir_name(prm->vidparam.dir), fmt_name,
+ vfd->size.w, vfd->size.h,
+ vfd->fps.num, vfd->fps.denum));
+
+ ptime_usec = PJMEDIA_PTIME(&vfd->fps);
+ pjmedia_clock_src_init(&vp->clocksrc, PJMEDIA_TYPE_VIDEO,
+ prm->vidparam.clock_rate, ptime_usec);
+ vp->sync_clocksrc.max_sync_ticks =
+ PJMEDIA_CLOCK_SYNC_MAX_RESYNC_DURATION *
+ 1000 / vp->clocksrc.ptime_usec;
+
+ /* Create the video stream */
+ pj_bzero(&vid_cb, sizeof(vid_cb));
+ vid_cb.capture_cb = &vidstream_cap_cb;
+ vid_cb.render_cb = &vidstream_render_cb;
+
+ status = pjmedia_vid_dev_stream_create(&vparam, &vid_cb, vp,
+ &vp->strm);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ PJ_LOG(4,(THIS_FILE,
+ "Device %s opened: format=%s, size=%dx%d @%d:%d fps",
+ dev_name, fmt_name,
+ vparam.fmt.det.vid.size.w, vparam.fmt.det.vid.size.h,
+ vparam.fmt.det.vid.fps.num, vparam.fmt.det.vid.fps.denum));
+
+ /* Subscribe to device's events */
+ pjmedia_event_subscribe(NULL, &vidstream_event_cb,
+ vp, vp->strm);
+
+ if (vp->dir & PJMEDIA_DIR_CAPTURE) {
+ pjmedia_format_copy(&vp->conv.conv_param.src, &vparam.fmt);
+ pjmedia_format_copy(&vp->conv.conv_param.dst, &prm->vidparam.fmt);
+ } else {
+ pjmedia_format_copy(&vp->conv.conv_param.src, &prm->vidparam.fmt);
+ pjmedia_format_copy(&vp->conv.conv_param.dst, &vparam.fmt);
+ }
+
+ status = create_converter(vp);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ if (vp->role==ROLE_ACTIVE &&
+ ((vp->dir & PJMEDIA_DIR_ENCODING) || vp->stream_role==ROLE_PASSIVE))
+ {
+ pjmedia_clock_param param;
+
+ /* Active role is wanted, but our device is passive, so create
+ * master clocks to run the media flow. For encoding direction,
+ * we also want to create our own clock since the device's clock
+ * may run at a different rate.
+ */
+ need_frame_buf = PJ_TRUE;
+
+ param.usec_interval = PJMEDIA_PTIME(&vfd->fps);
+ param.clock_rate = prm->vidparam.clock_rate;
+ status = pjmedia_clock_create2(pool, &param,
+ PJMEDIA_CLOCK_NO_HIGHEST_PRIO,
+ (vp->dir & PJMEDIA_DIR_ENCODING) ?
+ &enc_clock_cb: &dec_clock_cb,
+ vp, &vp->clock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ } else if (vp->role==ROLE_PASSIVE) {
+ vid_pasv_port *pp;
+
+ /* Always need to create media port for passive role */
+ vp->pasv_port = pp = PJ_POOL_ZALLOC_T(pool, vid_pasv_port);
+ pp->vp = vp;
+ pp->base.get_frame = &vid_pasv_port_get_frame;
+ pp->base.put_frame = &vid_pasv_port_put_frame;
+ pjmedia_port_info_init2(&pp->base.info, &vp->dev_name,
+ PJMEDIA_SIG_VID_PORT,
+ prm->vidparam.dir, &prm->vidparam.fmt);
+
+ if (vp->stream_role == ROLE_ACTIVE) {
+ need_frame_buf = PJ_TRUE;
+ }
+ }
+
+ if (need_frame_buf) {
+ const pjmedia_video_format_info *vfi;
+ pjmedia_video_apply_fmt_param vafp;
+
+ vfi = pjmedia_get_video_format_info(NULL, vparam.fmt.id);
+ if (!vfi) {
+ status = PJ_ENOTFOUND;
+ goto on_error;
+ }
+
+ pj_bzero(&vafp, sizeof(vafp));
+ vafp.size = vparam.fmt.det.vid.size;
+ status = vfi->apply_fmt(vfi, &vafp);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ vp->frm_buf = PJ_POOL_ZALLOC_T(pool, pjmedia_frame);
+ vp->frm_buf_size = vafp.framebytes;
+ vp->frm_buf->buf = pj_pool_alloc(pool, vafp.framebytes);
+ vp->frm_buf->size = vp->frm_buf_size;
+ vp->frm_buf->type = PJMEDIA_FRAME_TYPE_NONE;
+
+ status = pj_mutex_create_simple(pool, vp->dev_name.ptr,
+ &vp->frm_mutex);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ *p_vid_port = vp;
+
+ return PJ_SUCCESS;
+
+on_error:
+ pjmedia_vid_port_destroy(vp);
+ return status;
+}
+
+PJ_DEF(void) pjmedia_vid_port_set_cb(pjmedia_vid_port *vid_port,
+ const pjmedia_vid_dev_cb *cb,
+ void *user_data)
+{
+ pj_assert(vid_port && cb);
+ pj_memcpy(&vid_port->strm_cb, cb, sizeof(*cb));
+ vid_port->strm_cb_data = user_data;
+}
+
+PJ_DEF(pjmedia_vid_dev_stream*)
+pjmedia_vid_port_get_stream(pjmedia_vid_port *vp)
+{
+ PJ_ASSERT_RETURN(vp, NULL);
+ return vp->strm;
+}
+
+
+PJ_DEF(pjmedia_port*)
+pjmedia_vid_port_get_passive_port(pjmedia_vid_port *vp)
+{
+ PJ_ASSERT_RETURN(vp && vp->role==ROLE_PASSIVE, NULL);
+ return &vp->pasv_port->base;
+}
+
+
+PJ_DEF(pjmedia_clock_src *)
+pjmedia_vid_port_get_clock_src( pjmedia_vid_port *vid_port )
+{
+ PJ_ASSERT_RETURN(vid_port, NULL);
+ return &vid_port->clocksrc;
+}
+
+PJ_DECL(pj_status_t)
+pjmedia_vid_port_set_clock_src( pjmedia_vid_port *vid_port,
+ pjmedia_clock_src *clocksrc)
+{
+ PJ_ASSERT_RETURN(vid_port && clocksrc, PJ_EINVAL);
+
+ vid_port->sync_clocksrc.sync_clocksrc = clocksrc;
+ vid_port->sync_clocksrc.sync_delta =
+ pjmedia_clock_src_get_time_msec(&vid_port->clocksrc) -
+ 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,
+ pj_bool_t destroy)
+{
+ PJ_ASSERT_RETURN(vp && vp->role==ROLE_ACTIVE, PJ_EINVAL);
+ vp->destroy_client_port = destroy;
+ vp->client_port = port;
+
+ /* Subscribe to client port's events */
+ pjmedia_event_subscribe(NULL, &client_port_event_cb, vp,
+ vp->client_port);
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_vid_port_disconnect(pjmedia_vid_port *vp)
+{
+ PJ_ASSERT_RETURN(vp && vp->role==ROLE_ACTIVE, PJ_EINVAL);
+
+ pjmedia_event_unsubscribe(NULL, &client_port_event_cb, vp,
+ vp->client_port);
+ vp->client_port = NULL;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pjmedia_port*)
+pjmedia_vid_port_get_connected_port(pjmedia_vid_port *vp)
+{
+ PJ_ASSERT_RETURN(vp && vp->role==ROLE_ACTIVE, NULL);
+ return vp->client_port;
+}
+
+PJ_DEF(pj_status_t) pjmedia_vid_port_start(pjmedia_vid_port *vp)
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(vp, PJ_EINVAL);
+
+ status = pjmedia_vid_dev_stream_start(vp->strm);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ if (vp->clock) {
+ status = pjmedia_clock_start(vp->clock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ return PJ_SUCCESS;
+
+on_error:
+ pjmedia_vid_port_stop(vp);
+ return status;
+}
+
+PJ_DEF(pj_bool_t) pjmedia_vid_port_is_running(pjmedia_vid_port *vp)
+{
+ return pjmedia_vid_dev_stream_is_running(vp->strm);
+}
+
+PJ_DEF(pj_status_t) pjmedia_vid_port_stop(pjmedia_vid_port *vp)
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(vp, PJ_EINVAL);
+
+ if (vp->clock) {
+ status = pjmedia_clock_stop(vp->clock);
+ }
+
+ status = pjmedia_vid_dev_stream_stop(vp->strm);
+
+ return status;
+}
+
+PJ_DEF(void) pjmedia_vid_port_destroy(pjmedia_vid_port *vp)
+{
+ PJ_ASSERT_ON_FAIL(vp, return);
+
+ PJ_LOG(4,(THIS_FILE, "Closing %s..", vp->dev_name.ptr));
+
+ if (vp->clock) {
+ pjmedia_clock_destroy(vp->clock);
+ vp->clock = NULL;
+ }
+ if (vp->strm) {
+ pjmedia_event_unsubscribe(NULL, &vidstream_event_cb, vp, vp->strm);
+ pjmedia_vid_dev_stream_destroy(vp->strm);
+ vp->strm = NULL;
+ }
+ if (vp->client_port) {
+ pjmedia_event_unsubscribe(NULL, &client_port_event_cb, vp,
+ vp->client_port);
+ if (vp->destroy_client_port)
+ pjmedia_port_destroy(vp->client_port);
+ vp->client_port = NULL;
+ }
+ if (vp->frm_mutex) {
+ pj_mutex_destroy(vp->frm_mutex);
+ vp->frm_mutex = NULL;
+ }
+ if (vp->conv.conv) {
+ pjmedia_converter_destroy(vp->conv.conv);
+ vp->conv.conv = NULL;
+ }
+ pj_pool_release(vp->pool);
+}
+
+/*
+static void save_rgb_frame(int width, int height, const pjmedia_frame *frm)
+{
+ static int counter;
+ FILE *pFile;
+ char szFilename[32];
+ const pj_uint8_t *pFrame = (const pj_uint8_t*)frm->buf;
+ int y;
+
+ if (counter > 10)
+ return;
+
+ // Open file
+ sprintf(szFilename, "frame%02d.ppm", counter++);
+ pFile=fopen(szFilename, "wb");
+ if(pFile==NULL)
+ return;
+
+ // Write header
+ fprintf(pFile, "P6\n%d %d\n255\n", width, height);
+
+ // Write pixel data
+ for(y=0; y<height; y++)
+ fwrite(pFrame+y*width*3, 1, width*3, pFile);
+
+ // Close file
+ fclose(pFile);
+}
+*/
+
+/* Handle event from vidstream */
+static pj_status_t vidstream_event_cb(pjmedia_event *event,
+ void *user_data)
+{
+ pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
+
+ /* Just republish the event to our client */
+ return pjmedia_event_publish(NULL, vp, event, 0);
+}
+
+static pj_status_t client_port_event_cb(pjmedia_event *event,
+ void *user_data)
+{
+ pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
+
+ if (event->type == PJMEDIA_EVENT_FMT_CHANGED) {
+ const pjmedia_video_format_detail *vfd;
+ pjmedia_vid_dev_param vid_param;
+ pj_status_t status;
+
+ pjmedia_vid_port_stop(vp);
+
+ /* Retrieve the video format detail */
+ vfd = pjmedia_format_get_video_format_detail(
+ &event->data.fmt_changed.new_fmt, PJ_TRUE);
+ if (!vfd || !vfd->fps.num || !vfd->fps.denum)
+ return PJMEDIA_EVID_BADFORMAT;
+
+ /* Change the destination format to the new format */
+ pjmedia_format_copy(&vp->conv.conv_param.src,
+ &event->data.fmt_changed.new_fmt);
+ /* Only copy the size here */
+ vp->conv.conv_param.dst.det.vid.size =
+ event->data.fmt_changed.new_fmt.det.vid.size;
+
+ status = create_converter(vp);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4,(THIS_FILE, status, "Error recreating converter"));
+ return status;
+ }
+
+ pjmedia_vid_dev_stream_get_param(vp->strm, &vid_param);
+ if (vid_param.fmt.id != vp->conv.conv_param.dst.id ||
+ (vid_param.fmt.det.vid.size.h !=
+ vp->conv.conv_param.dst.det.vid.size.h) ||
+ (vid_param.fmt.det.vid.size.w !=
+ vp->conv.conv_param.dst.det.vid.size.w))
+ {
+ status = pjmedia_vid_dev_stream_set_cap(vp->strm,
+ PJMEDIA_VID_DEV_CAP_FORMAT,
+ &vp->conv.conv_param.dst);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "failure in changing the format of the "
+ "video device"));
+ PJ_LOG(3, (THIS_FILE, "reverting to its original format: %s",
+ status != PJMEDIA_EVID_ERR ? "success" :
+ "failure"));
+ return status;
+ }
+ }
+
+ if (vp->stream_role == ROLE_PASSIVE) {
+ pjmedia_clock_param clock_param;
+
+ /**
+ * Initially, frm_buf was allocated the biggest
+ * supported size, so we do not need to re-allocate
+ * the buffer here.
+ */
+ /* Adjust the clock */
+ clock_param.usec_interval = PJMEDIA_PTIME(&vfd->fps);
+ clock_param.clock_rate = vid_param.clock_rate;
+ pjmedia_clock_modify(vp->clock, &clock_param);
+ }
+
+ pjmedia_vid_port_start(vp);
+ }
+
+ /* Republish the event, post the event to the event manager
+ * to avoid deadlock if vidport is trying to stop the clock.
+ */
+ return pjmedia_event_publish(NULL, vp, event,
+ PJMEDIA_EVENT_PUBLISH_POST_EVENT);
+}
+
+static pj_status_t convert_frame(pjmedia_vid_port *vp,
+ pjmedia_frame *src_frame,
+ pjmedia_frame *dst_frame)
+{
+ pj_status_t status = PJ_SUCCESS;
+
+ if (vp->conv.conv) {
+ dst_frame->buf = vp->conv.conv_buf;
+ dst_frame->size = vp->conv.conv_buf_size;
+ status = pjmedia_converter_convert(vp->conv.conv,
+ src_frame, dst_frame);
+ }
+
+ return status;
+}
+
+/* Copy frame to buffer. */
+static void copy_frame_to_buffer(pjmedia_vid_port *vp,
+ pjmedia_frame *frame)
+{
+ pj_mutex_lock(vp->frm_mutex);
+ pjmedia_frame_copy(vp->frm_buf, frame);
+ pj_mutex_unlock(vp->frm_mutex);
+}
+
+/* Get frame from buffer and convert it if necessary. */
+static pj_status_t get_frame_from_buffer(pjmedia_vid_port *vp,
+ pjmedia_frame *frame)
+{
+ pj_status_t status = PJ_SUCCESS;
+
+ pj_mutex_lock(vp->frm_mutex);
+ if (vp->conv.conv)
+ status = convert_frame(vp, vp->frm_buf, frame);
+ else
+ pjmedia_frame_copy(frame, vp->frm_buf);
+ pj_mutex_unlock(vp->frm_mutex);
+
+ return status;
+}
+
+static void enc_clock_cb(const pj_timestamp *ts, void *user_data)
+{
+ /* We are here because user wants us to be active but the stream is
+ * passive. So get a frame from the stream and push it to user.
+ */
+ pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
+ pjmedia_frame frame_;
+ pj_status_t status = PJ_SUCCESS;
+
+ pj_assert(vp->role==ROLE_ACTIVE);
+
+ PJ_UNUSED_ARG(ts);
+
+ if (!vp->client_port)
+ return;
+
+ if (vp->stream_role == ROLE_PASSIVE) {
+ while (vp->conv.usec_ctr < vp->conv.usec_dst) {
+ vp->frm_buf->size = vp->frm_buf_size;
+ status = pjmedia_vid_dev_stream_get_frame(vp->strm, vp->frm_buf);
+ vp->conv.usec_ctr += vp->conv.usec_src;
+ }
+ vp->conv.usec_ctr -= vp->conv.usec_dst;
+ if (status != PJ_SUCCESS)
+ return;
+ }
+
+ //save_rgb_frame(vp->cap_size.w, vp->cap_size.h, vp->frm_buf);
+
+ frame_.buf = vp->conv.conv_buf;
+ frame_.size = vp->conv.conv_buf_size;
+ status = get_frame_from_buffer(vp, &frame_);
+ if (status != PJ_SUCCESS)
+ return;
+
+ status = pjmedia_port_put_frame(vp->client_port, &frame_);
+ if (status != PJ_SUCCESS)
+ return;
+}
+
+static void dec_clock_cb(const pj_timestamp *ts, void *user_data)
+{
+ /* We are here because user wants us to be active but the stream is
+ * passive. So get a frame from the stream and push it to user.
+ */
+ pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
+ pj_status_t status;
+ pjmedia_frame frame;
+
+ pj_assert(vp->role==ROLE_ACTIVE && vp->stream_role==ROLE_PASSIVE);
+
+ PJ_UNUSED_ARG(ts);
+
+ if (!vp->client_port)
+ return;
+
+ status = vidstream_render_cb(vp->strm, vp, &frame);
+ if (status != PJ_SUCCESS)
+ return;
+
+ if (frame.size > 0)
+ status = pjmedia_vid_dev_stream_put_frame(vp->strm, &frame);
+}
+
+static pj_status_t vidstream_cap_cb(pjmedia_vid_dev_stream *stream,
+ void *user_data,
+ pjmedia_frame *frame)
+{
+ pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
+
+ /* We just store the frame in the buffer. For active role, we let
+ * video port's clock to push the frame buffer to the user.
+ * The decoding counterpart for passive role and active stream is
+ * located in vid_pasv_port_put_frame()
+ */
+ copy_frame_to_buffer(vp, frame);
+
+ /* This is tricky since the frame is still in its original unconverted
+ * format, which may not be what the application expects.
+ */
+ if (vp->strm_cb.capture_cb)
+ return (*vp->strm_cb.capture_cb)(stream, vp->strm_cb_data, frame);
+ return PJ_SUCCESS;
+}
+
+static pj_status_t vidstream_render_cb(pjmedia_vid_dev_stream *stream,
+ void *user_data,
+ pjmedia_frame *frame)
+{
+ pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
+ pj_status_t status = PJ_SUCCESS;
+
+ pj_bzero(frame, sizeof(pjmedia_frame));
+ if (vp->role==ROLE_ACTIVE) {
+ unsigned frame_ts = vp->clocksrc.clock_rate / 1000 *
+ vp->clocksrc.ptime_usec / 1000;
+
+ if (!vp->client_port)
+ return status;
+
+ if (vp->sync_clocksrc.sync_clocksrc) {
+ pjmedia_clock_src *src = vp->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->clocksrc) -
+ pjmedia_clock_src_get_time_msec(src) -
+ vp->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->clocksrc, NULL);
+ vp->sync_clocksrc.sync_delta =
+ pjmedia_clock_src_get_time_msec(src) -
+ pjmedia_clock_src_get_time_msec(&vp->clocksrc);
+ vp->sync_clocksrc.nsync_frame = 0;
+ return status;
+ }
+
+ /* Calculate the difference (in frames) with the sync source */
+ nsync_frame = abs(diff) * 1000 / vp->clocksrc.ptime_usec;
+ if (nsync_frame == 0) {
+ /* Nothing to sync */
+ vp->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->sync_clocksrc.nsync_frame == 0 ||
+ (vp->sync_clocksrc.nsync_frame > 0 &&
+ nsync_frame > vp->sync_clocksrc.nsync_frame))
+ {
+ vp->sync_clocksrc.nsync_frame = nsync_frame;
+ vp->sync_clocksrc.nsync_progress = 0;
+ } else {
+ init_sync_frame = vp->sync_clocksrc.nsync_frame;
+ }
+
+ if (diff >= 0) {
+ unsigned skip_mod;
+
+ /* We are too fast */
+ if (vp->sync_clocksrc.max_sync_ticks > 0) {
+ skip_mod = init_sync_frame /
+ vp->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->sync_clocksrc.nsync_progress % skip_mod > 0) {
+ pjmedia_clock_src_update(&vp->clocksrc, NULL);
+ return status;
+ }
+ } else {
+ unsigned i, ndrop = init_sync_frame;
+
+ /* We are too late, drop the frame */
+ if (vp->sync_clocksrc.max_sync_ticks > 0) {
+ ndrop /= vp->sync_clocksrc.max_sync_ticks;
+ ndrop++;
+ }
+ PJ_LOG(5, (THIS_FILE, "synchronization: late, "
+ "dropping %d frame(s)", ndrop));
+
+ if (ndrop >= nsync_frame) {
+ vp->sync_clocksrc.nsync_frame = 0;
+ ndrop = nsync_frame;
+ } else
+ vp->sync_clocksrc.nsync_progress += ndrop;
+
+ for (i = 0; i < ndrop; i++) {
+ vp->frm_buf->size = vp->frm_buf_size;
+ status = pjmedia_port_get_frame(vp->client_port,
+ vp->frm_buf);
+ if (status != PJ_SUCCESS) {
+ pjmedia_clock_src_update(&vp->clocksrc, NULL);
+ return status;
+ }
+
+ pj_add_timestamp32(&vp->clocksrc.timestamp,
+ frame_ts);
+ }
+ }
+ }
+ }
+
+ vp->frm_buf->size = vp->frm_buf_size;
+ status = pjmedia_port_get_frame(vp->client_port, vp->frm_buf);
+ if (status != PJ_SUCCESS) {
+ pjmedia_clock_src_update(&vp->clocksrc, NULL);
+ return status;
+ }
+ pj_add_timestamp32(&vp->clocksrc.timestamp, frame_ts);
+ pjmedia_clock_src_update(&vp->clocksrc, NULL);
+
+ status = convert_frame(vp, vp->frm_buf, frame);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (!vp->conv.conv)
+ pj_memcpy(frame, vp->frm_buf, sizeof(*frame));
+ } else {
+ /* The stream is active while we are passive so we need to get the
+ * frame from the buffer.
+ * The encoding counterpart is located in vid_pasv_port_get_frame()
+ */
+ get_frame_from_buffer(vp, frame);
+ }
+ if (vp->strm_cb.render_cb)
+ return (*vp->strm_cb.render_cb)(stream, vp->strm_cb_data, frame);
+ return PJ_SUCCESS;
+}
+
+static pj_status_t vid_pasv_port_put_frame(struct pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ struct vid_pasv_port *vpp = (struct vid_pasv_port*)this_port;
+ pjmedia_vid_port *vp = vpp->vp;
+
+ if (vp->stream_role==ROLE_PASSIVE) {
+ /* We are passive and the stream is passive.
+ * The encoding counterpart is in vid_pasv_port_get_frame().
+ */
+ pj_status_t status;
+ pjmedia_frame frame_;
+
+ status = convert_frame(vp, frame, &frame_);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ return pjmedia_vid_dev_stream_put_frame(vp->strm, (vp->conv.conv?
+ &frame_: frame));
+ } else {
+ /* We are passive while the stream is active so we just store the
+ * frame in the buffer.
+ * The encoding counterpart is located in vidstream_cap_cb()
+ */
+ copy_frame_to_buffer(vp, frame);
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t vid_pasv_port_get_frame(struct pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ struct vid_pasv_port *vpp = (struct vid_pasv_port*)this_port;
+ pjmedia_vid_port *vp = vpp->vp;
+ pj_status_t status = PJ_SUCCESS;
+
+ if (vp->stream_role==ROLE_PASSIVE) {
+ /* We are passive and the stream is passive.
+ * The decoding counterpart is in vid_pasv_port_put_frame().
+ */
+ status = pjmedia_vid_dev_stream_get_frame(vp->strm, (vp->conv.conv?
+ vp->frm_buf: frame));
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = convert_frame(vp, vp->frm_buf, frame);
+ } else {
+ /* The stream is active while we are passive so we need to get the
+ * frame from the buffer.
+ * The decoding counterpart is located in vidstream_rend_cb()
+ */
+ get_frame_from_buffer(vp, frame);
+ }
+
+ return status;
+}
+
+
+#endif /* PJMEDIA_HAS_VIDEO */
diff --git a/pjmedia/src/pjmedia/vid_stream.c b/pjmedia/src/pjmedia/vid_stream.c
new file mode 100644
index 0000000..d899408
--- /dev/null
+++ b/pjmedia/src/pjmedia/vid_stream.c
@@ -0,0 +1,1981 @@
+/* $Id: vid_stream.c 4123 2012-05-14 11:17:31Z ming $ */
+/*
+ * Copyright (C) 2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia/vid_stream.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/event.h>
+#include <pjmedia/rtp.h>
+#include <pjmedia/rtcp.h>
+#include <pjmedia/jbuf.h>
+#include <pjmedia/stream_common.h>
+#include <pj/array.h>
+#include <pj/assert.h>
+#include <pj/compat/socket.h>
+#include <pj/errno.h>
+#include <pj/ioqueue.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/rand.h>
+#include <pj/sock_select.h>
+#include <pj/string.h> /* memcpy() */
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+
+#define THIS_FILE "vid_stream.c"
+#define ERRLEVEL 1
+#define LOGERR_(expr) stream_perror expr
+#define TRC_(expr) PJ_LOG(5,expr)
+#define SIGNATURE PJMEDIA_SIG_PORT_VID_STREAM
+
+#define TRACE_RC 0
+
+/* Tracing jitter buffer operations in a stream session to a CSV file.
+ * The trace will contain JB operation timestamp, frame info, RTP info, and
+ * the JB state right after the operation.
+ */
+#define TRACE_JB 0 /* Enable/disable trace. */
+#define TRACE_JB_PATH_PREFIX "" /* Optional path/prefix
+ for the CSV filename. */
+#if TRACE_JB
+# include <pj/file_io.h>
+# define TRACE_JB_INVALID_FD ((pj_oshandle_t)-1)
+# define TRACE_JB_OPENED(s) (s->trace_jb_fd != TRACE_JB_INVALID_FD)
+#endif
+
+#ifndef PJMEDIA_VSTREAM_SIZE
+# define PJMEDIA_VSTREAM_SIZE 1000
+#endif
+
+#ifndef PJMEDIA_VSTREAM_INC
+# define PJMEDIA_VSTREAM_INC 1000
+#endif
+
+/* Video stream keep-alive feature is currently disabled. */
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA != 0
+# undef PJMEDIA_STREAM_ENABLE_KA
+# define PJMEDIA_STREAM_ENABLE_KA 0
+#endif
+
+
+/**
+ * Media channel.
+ */
+typedef struct pjmedia_vid_channel
+{
+ pjmedia_vid_stream *stream; /**< Parent stream. */
+ pjmedia_dir dir; /**< Channel direction. */
+ pjmedia_port port; /**< Port interface. */
+ unsigned pt; /**< Payload type. */
+ pj_bool_t paused; /**< Paused?. */
+ void *buf; /**< Output buffer. */
+ unsigned buf_size; /**< Size of output buffer. */
+ pjmedia_rtp_session rtp; /**< RTP session. */
+} pjmedia_vid_channel;
+
+
+/**
+ * This structure describes media stream.
+ * A media stream is bidirectional media transmission between two endpoints.
+ * It consists of two channels, i.e. encoding and decoding channels.
+ * A media stream corresponds to a single "m=" line in a SDP session
+ * description.
+ */
+struct pjmedia_vid_stream
+{
+ pj_pool_t *own_pool; /**< Internal pool. */
+ pjmedia_endpt *endpt; /**< Media endpoint. */
+ pjmedia_vid_codec_mgr *codec_mgr; /**< Codec manager. */
+ pjmedia_vid_stream_info info; /**< Stream info. */
+
+ pjmedia_vid_channel *enc; /**< Encoding channel. */
+ pjmedia_vid_channel *dec; /**< Decoding channel. */
+
+ pjmedia_dir dir; /**< Stream direction. */
+ void *user_data; /**< User data. */
+ pj_str_t name; /**< Stream name */
+ pj_str_t cname; /**< SDES CNAME */
+
+ pjmedia_transport *transport; /**< Stream transport. */
+ unsigned send_err_cnt; /**< Send error count. */
+
+ pj_mutex_t *jb_mutex;
+ pjmedia_jbuf *jb; /**< Jitter buffer. */
+ char jb_last_frm; /**< Last frame type from jb */
+ unsigned jb_last_frm_cnt;/**< Last JB frame type counter*/
+
+ pjmedia_rtcp_session rtcp; /**< RTCP for incoming RTP. */
+ pj_uint32_t rtcp_last_tx; /**< RTCP tx time in timestamp */
+ pj_uint32_t rtcp_interval; /**< Interval, in timestamp. */
+ pj_bool_t initial_rr; /**< Initial RTCP RR sent */
+ pj_bool_t rtcp_sdes_bye_disabled;/**< Send RTCP SDES/BYE?*/
+ void *out_rtcp_pkt; /**< Outgoing RTCP packet. */
+ unsigned out_rtcp_pkt_size;
+ /**< Outgoing RTCP packet size. */
+
+ unsigned dec_max_size; /**< Size of decoded/raw picture*/
+ pjmedia_ratio dec_max_fps; /**< Max fps of decoding dir. */
+ pjmedia_frame dec_frame; /**< Current decoded frame. */
+ pjmedia_event fmt_event; /**< Buffered fmt_changed event
+ to avoid deadlock */
+ pjmedia_event miss_keyframe_event;
+ /**< Buffered missing keyframe
+ event for delayed republish*/
+
+ unsigned frame_size; /**< Size of encoded base frame.*/
+ unsigned frame_ts_len; /**< Frame length in timestamp. */
+
+ unsigned rx_frame_cnt; /**< # of array in rx_frames */
+ pjmedia_frame *rx_frames; /**< Temp. buffer for incoming
+ frame assembly. */
+
+ pj_bool_t force_keyframe;/**< Forced to encode keyframe? */
+
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
+ pj_bool_t use_ka; /**< Stream keep-alive with non-
+ codec-VAD mechanism is
+ enabled? */
+ pj_timestamp last_frm_ts_sent; /**< Timestamp of last sending
+ packet */
+#endif
+
+#if TRACE_JB
+ pj_oshandle_t trace_jb_fd; /**< Jitter tracing file handle.*/
+ char *trace_jb_buf; /**< Jitter tracing buffer. */
+#endif
+
+ pjmedia_vid_codec *codec; /**< Codec instance being used. */
+ pj_uint32_t last_dec_ts; /**< Last decoded timestamp. */
+ int last_dec_seq; /**< Last decoded sequence. */
+
+
+ pj_timestamp ts_freq; /**< Timestamp frequency. */
+
+#if TRACE_RC
+ unsigned rc_total_sleep;
+ unsigned rc_total_pkt;
+ unsigned rc_total_img;
+ pj_timestamp tx_start;
+ pj_timestamp tx_end;
+#endif
+};
+
+/* Prototypes */
+static pj_status_t decode_frame(pjmedia_vid_stream *stream,
+ pjmedia_frame *frame);
+
+/*
+ * Print error.
+ */
+static void stream_perror(const char *sender, const char *title,
+ pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(sender, "%s: %s [err:%d]", title, errmsg, status));
+}
+
+
+static pj_status_t send_rtcp(pjmedia_vid_stream *stream,
+ pj_bool_t with_sdes,
+ pj_bool_t with_bye);
+
+
+#if TRACE_JB
+
+PJ_INLINE(int) trace_jb_print_timestamp(char **buf, pj_ssize_t len)
+{
+ pj_time_val now;
+ pj_parsed_time ptime;
+ char *p = *buf;
+
+ if (len < 14)
+ return -1;
+
+ pj_gettimeofday(&now);
+ pj_time_decode(&now, &ptime);
+ p += pj_utoa_pad(ptime.hour, p, 2, '0');
+ *p++ = ':';
+ p += pj_utoa_pad(ptime.min, p, 2, '0');
+ *p++ = ':';
+ p += pj_utoa_pad(ptime.sec, p, 2, '0');
+ *p++ = '.';
+ p += pj_utoa_pad(ptime.msec, p, 3, '0');
+ *p++ = ',';
+
+ *buf = p;
+
+ return 0;
+}
+
+PJ_INLINE(int) trace_jb_print_state(pjmedia_vid_stream *stream,
+ char **buf, pj_ssize_t len)
+{
+ char *p = *buf;
+ char *endp = *buf + len;
+ pjmedia_jb_state state;
+
+ pjmedia_jbuf_get_state(stream->jb, &state);
+
+ len = pj_ansi_snprintf(p, endp-p, "%d, %d, %d",
+ state.size, state.burst, state.prefetch);
+ if ((len < 0) || (len >= endp-p))
+ return -1;
+
+ p += len;
+ *buf = p;
+ return 0;
+}
+
+static void trace_jb_get(pjmedia_vid_stream *stream, pjmedia_jb_frame_type ft,
+ pj_size_t fsize)
+{
+ char *p = stream->trace_jb_buf;
+ char *endp = stream->trace_jb_buf + PJ_LOG_MAX_SIZE;
+ pj_ssize_t len = 0;
+ const char* ft_st;
+
+ if (!TRACE_JB_OPENED(stream))
+ return;
+
+ /* Print timestamp. */
+ if (trace_jb_print_timestamp(&p, endp-p))
+ goto on_insuff_buffer;
+
+ /* Print frame type and size */
+ switch(ft) {
+ case PJMEDIA_JB_MISSING_FRAME:
+ ft_st = "missing";
+ break;
+ case PJMEDIA_JB_NORMAL_FRAME:
+ ft_st = "normal";
+ break;
+ case PJMEDIA_JB_ZERO_PREFETCH_FRAME:
+ ft_st = "prefetch";
+ break;
+ case PJMEDIA_JB_ZERO_EMPTY_FRAME:
+ ft_st = "empty";
+ break;
+ default:
+ ft_st = "unknown";
+ break;
+ }
+
+ /* Print operation, size, frame count, frame type */
+ len = pj_ansi_snprintf(p, endp-p, "GET,%d,1,%s,,,,", fsize, ft_st);
+ if ((len < 0) || (len >= endp-p))
+ goto on_insuff_buffer;
+ p += len;
+
+ /* Print JB state */
+ if (trace_jb_print_state(stream, &p, endp-p))
+ goto on_insuff_buffer;
+
+ /* Print end of line */
+ if (endp-p < 2)
+ goto on_insuff_buffer;
+ *p++ = '\n';
+
+ /* Write and flush */
+ len = p - stream->trace_jb_buf;
+ pj_file_write(stream->trace_jb_fd, stream->trace_jb_buf, &len);
+ pj_file_flush(stream->trace_jb_fd);
+ return;
+
+on_insuff_buffer:
+ pj_assert(!"Trace buffer too small, check PJ_LOG_MAX_SIZE!");
+}
+
+static void trace_jb_put(pjmedia_vid_stream *stream,
+ const pjmedia_rtp_hdr *hdr,
+ unsigned payloadlen, unsigned frame_cnt)
+{
+ char *p = stream->trace_jb_buf;
+ char *endp = stream->trace_jb_buf + PJ_LOG_MAX_SIZE;
+ pj_ssize_t len = 0;
+
+ if (!TRACE_JB_OPENED(stream))
+ return;
+
+ /* Print timestamp. */
+ if (trace_jb_print_timestamp(&p, endp-p))
+ goto on_insuff_buffer;
+
+ /* Print operation, size, frame count, RTP info */
+ len = pj_ansi_snprintf(p, endp-p,
+ "PUT,%d,%d,,%d,%d,%d,",
+ payloadlen, frame_cnt,
+ pj_ntohs(hdr->seq), pj_ntohl(hdr->ts), hdr->m);
+ if ((len < 0) || (len >= endp-p))
+ goto on_insuff_buffer;
+ p += len;
+
+ /* Print JB state */
+ if (trace_jb_print_state(stream, &p, endp-p))
+ goto on_insuff_buffer;
+
+ /* Print end of line */
+ if (endp-p < 2)
+ goto on_insuff_buffer;
+ *p++ = '\n';
+
+ /* Write and flush */
+ len = p - stream->trace_jb_buf;
+ pj_file_write(stream->trace_jb_fd, stream->trace_jb_buf, &len);
+ pj_file_flush(stream->trace_jb_fd);
+ return;
+
+on_insuff_buffer:
+ pj_assert(!"Trace buffer too small, check PJ_LOG_MAX_SIZE!");
+}
+
+#endif /* TRACE_JB */
+
+static void dump_port_info(const pjmedia_vid_channel *chan,
+ const char *event_name)
+{
+ const pjmedia_port_info *pi = &chan->port.info;
+ char fourcc_name[5];
+
+ PJ_LOG(5, (pi->name.ptr,
+ " %s format %s: %dx%d %s%s %d/%d(~%d)fps",
+ (chan->dir==PJMEDIA_DIR_DECODING? "Decoding":"Encoding"),
+ event_name,
+ pi->fmt.det.vid.size.w, pi->fmt.det.vid.size.h,
+ pjmedia_fourcc_name(pi->fmt.id, fourcc_name),
+ (chan->dir==PJMEDIA_DIR_ENCODING?"->":"<-"),
+ pi->fmt.det.vid.fps.num, pi->fmt.det.vid.fps.denum,
+ pi->fmt.det.vid.fps.num/pi->fmt.det.vid.fps.denum));
+}
+
+/*
+ * Handle events from stream components.
+ */
+static pj_status_t stream_event_cb(pjmedia_event *event,
+ void *user_data)
+{
+ pjmedia_vid_stream *stream = (pjmedia_vid_stream*)user_data;
+
+ if (event->epub == stream->codec) {
+ /* This is codec event */
+ switch (event->type) {
+ case PJMEDIA_EVENT_FMT_CHANGED:
+ /* Copy the event to avoid deadlock if we publish the event
+ * now. This happens because fmt_event may trigger restart
+ * while we're still holding the jb_mutex.
+ */
+ pj_memcpy(&stream->fmt_event, event, sizeof(*event));
+ return PJ_SUCCESS;
+
+ case PJMEDIA_EVENT_KEYFRAME_MISSING:
+ /* Republish this event later from get_frame(). */
+ pj_memcpy(&stream->miss_keyframe_event, event, sizeof(*event));
+ return PJ_SUCCESS;
+
+ default:
+ break;
+ }
+ }
+
+ return pjmedia_event_publish(NULL, stream, event, 0);
+}
+
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA != 0
+/*
+ * Send keep-alive packet using non-codec frame.
+ */
+static void send_keep_alive_packet(pjmedia_vid_stream *stream)
+{
+#if PJMEDIA_STREAM_ENABLE_KA == PJMEDIA_STREAM_KA_EMPTY_RTP
+
+ /* Keep-alive packet is empty RTP */
+ pjmedia_vid_channel *channel = stream->enc;
+ pj_status_t status;
+ void *pkt;
+ int pkt_len;
+
+ TRC_((channel->port.info.name.ptr,
+ "Sending keep-alive (RTCP and empty RTP)"));
+
+ /* Send RTP */
+ status = pjmedia_rtp_encode_rtp( &stream->enc->rtp,
+ stream->enc->pt, 0,
+ 1,
+ 0,
+ (const void**)&pkt,
+ &pkt_len);
+ pj_assert(status == PJ_SUCCESS);
+
+ pj_memcpy(stream->enc->buf, pkt, pkt_len);
+ pjmedia_transport_send_rtp(stream->transport, stream->enc->buf,
+ pkt_len);
+
+ /* Send RTCP */
+ send_rtcp(stream, PJ_TRUE, PJ_FALSE);
+
+#elif PJMEDIA_STREAM_ENABLE_KA == PJMEDIA_STREAM_KA_USER
+
+ /* Keep-alive packet is defined in PJMEDIA_STREAM_KA_USER_PKT */
+ pjmedia_vid_channel *channel = stream->enc;
+ int pkt_len;
+ const pj_str_t str_ka = PJMEDIA_STREAM_KA_USER_PKT;
+
+ TRC_((channel->port.info.name.ptr,
+ "Sending keep-alive (custom RTP/RTCP packets)"));
+
+ /* Send to RTP port */
+ pj_memcpy(stream->enc->buf, str_ka.ptr, str_ka.slen);
+ pkt_len = str_ka.slen;
+ pjmedia_transport_send_rtp(stream->transport, stream->enc->buf,
+ pkt_len);
+
+ /* Send to RTCP port */
+ pjmedia_transport_send_rtcp(stream->transport, stream->enc->buf,
+ pkt_len);
+
+#else
+
+ PJ_UNUSED_ARG(stream);
+
+#endif
+}
+#endif /* defined(PJMEDIA_STREAM_ENABLE_KA) */
+
+
+static pj_status_t send_rtcp(pjmedia_vid_stream *stream,
+ pj_bool_t with_sdes,
+ pj_bool_t with_bye)
+{
+ void *sr_rr_pkt;
+ pj_uint8_t *pkt;
+ int len, max_len;
+ pj_status_t status;
+
+ /* Build RTCP RR/SR packet */
+ pjmedia_rtcp_build_rtcp(&stream->rtcp, &sr_rr_pkt, &len);
+
+ if (with_sdes || with_bye) {
+ pkt = (pj_uint8_t*) stream->out_rtcp_pkt;
+ pj_memcpy(pkt, sr_rr_pkt, len);
+ max_len = stream->out_rtcp_pkt_size;
+ } else {
+ pkt = (pj_uint8_t*)sr_rr_pkt;
+ max_len = len;
+ }
+
+ /* Build RTCP SDES packet */
+ if (with_sdes) {
+ pjmedia_rtcp_sdes sdes;
+ pj_size_t sdes_len;
+
+ pj_bzero(&sdes, sizeof(sdes));
+ sdes.cname = stream->cname;
+ sdes_len = max_len - len;
+ status = pjmedia_rtcp_build_rtcp_sdes(&stream->rtcp, pkt+len,
+ &sdes_len, &sdes);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4,(stream->name.ptr, status,
+ "Error generating RTCP SDES"));
+ } else {
+ len += sdes_len;
+ }
+ }
+
+ /* Build RTCP BYE packet */
+ if (with_bye) {
+ pj_size_t bye_len;
+
+ bye_len = max_len - len;
+ status = pjmedia_rtcp_build_rtcp_bye(&stream->rtcp, pkt+len,
+ &bye_len, NULL);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4,(stream->name.ptr, status,
+ "Error generating RTCP BYE"));
+ } else {
+ len += bye_len;
+ }
+ }
+
+ /* Send! */
+ status = pjmedia_transport_send_rtcp(stream->transport, pkt, len);
+
+ return status;
+}
+
+
+/**
+ * check_tx_rtcp()
+ *
+ * This function is can be called by either put_frame() or get_frame(),
+ * to transmit periodic RTCP SR/RR report.
+ */
+static void check_tx_rtcp(pjmedia_vid_stream *stream, pj_uint32_t timestamp)
+{
+ /* Note that timestamp may represent local or remote timestamp,
+ * depending on whether this function is called from put_frame()
+ * or get_frame().
+ */
+
+
+ if (stream->rtcp_last_tx == 0) {
+
+ stream->rtcp_last_tx = timestamp;
+
+ } else if (timestamp - stream->rtcp_last_tx >= stream->rtcp_interval) {
+ pj_status_t status;
+
+ status = send_rtcp(stream, !stream->rtcp_sdes_bye_disabled, PJ_FALSE);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4,(stream->name.ptr, status,
+ "Error sending RTCP"));
+ }
+
+ stream->rtcp_last_tx = timestamp;
+ }
+}
+
+
+#if 0
+static void dump_bin(const char *buf, unsigned len)
+{
+ unsigned i;
+
+ PJ_LOG(3,(THIS_FILE, "begin dump"));
+ for (i=0; i<len; ++i) {
+ int j;
+ char bits[9];
+ unsigned val = buf[i] & 0xFF;
+
+ bits[8] = '\0';
+ for (j=0; j<8; ++j) {
+ if (val & (1 << (7-j)))
+ bits[j] = '1';
+ else
+ bits[j] = '0';
+ }
+
+ PJ_LOG(3,(THIS_FILE, "%2d %s [%d]", i, bits, val));
+ }
+ PJ_LOG(3,(THIS_FILE, "end dump"));
+}
+#endif
+
+
+/*
+ * This callback is called by stream transport on receipt of packets
+ * in the RTP socket.
+ */
+static void on_rx_rtp( void *data,
+ void *pkt,
+ pj_ssize_t bytes_read)
+
+{
+ pjmedia_vid_stream *stream = (pjmedia_vid_stream*) data;
+ pjmedia_vid_channel *channel = stream->dec;
+ const pjmedia_rtp_hdr *hdr;
+ const void *payload;
+ unsigned payloadlen;
+ pjmedia_rtp_status seq_st;
+ pj_status_t status;
+ pj_bool_t pkt_discarded = PJ_FALSE;
+
+ /* Check for errors */
+ if (bytes_read < 0) {
+ LOGERR_((channel->port.info.name.ptr, "RTP recv() error", -bytes_read));
+ return;
+ }
+
+ /* Ignore keep-alive packets */
+ if (bytes_read < (pj_ssize_t) sizeof(pjmedia_rtp_hdr))
+ return;
+
+ /* Update RTP and RTCP session. */
+ status = pjmedia_rtp_decode_rtp(&channel->rtp, pkt, bytes_read,
+ &hdr, &payload, &payloadlen);
+ if (status != PJ_SUCCESS) {
+ LOGERR_((channel->port.info.name.ptr, "RTP decode error", status));
+ stream->rtcp.stat.rx.discard++;
+ return;
+ }
+
+ /* Ignore the packet if decoder is paused */
+ if (channel->paused)
+ goto on_return;
+
+ /* Update RTP session (also checks if RTP session can accept
+ * the incoming packet.
+ */
+ pjmedia_rtp_session_update2(&channel->rtp, hdr, &seq_st, PJ_TRUE);
+ if (seq_st.status.value) {
+ TRC_ ((channel->port.info.name.ptr,
+ "RTP status: badpt=%d, badssrc=%d, dup=%d, "
+ "outorder=%d, probation=%d, restart=%d",
+ seq_st.status.flag.badpt,
+ seq_st.status.flag.badssrc,
+ seq_st.status.flag.dup,
+ seq_st.status.flag.outorder,
+ seq_st.status.flag.probation,
+ seq_st.status.flag.restart));
+
+ if (seq_st.status.flag.badpt) {
+ PJ_LOG(4,(channel->port.info.name.ptr,
+ "Bad RTP pt %d (expecting %d)",
+ hdr->pt, channel->rtp.out_pt));
+ }
+
+ if (seq_st.status.flag.badssrc) {
+ PJ_LOG(4,(channel->port.info.name.ptr,
+ "Changed RTP peer SSRC %d (previously %d)",
+ channel->rtp.peer_ssrc, stream->rtcp.peer_ssrc));
+ stream->rtcp.peer_ssrc = channel->rtp.peer_ssrc;
+ }
+
+
+ }
+
+ /* Skip bad RTP packet */
+ if (seq_st.status.flag.bad) {
+ pkt_discarded = PJ_TRUE;
+ goto on_return;
+ }
+
+ /* Ignore if payloadlen is zero */
+ if (payloadlen == 0) {
+ pkt_discarded = PJ_TRUE;
+ goto on_return;
+ }
+
+ pj_mutex_lock( stream->jb_mutex );
+
+ /* Quickly see if there may be a full picture in the jitter buffer, and
+ * decode them if so. More thorough check will be done in decode_frame().
+ */
+ if ((pj_ntohl(hdr->ts) != stream->dec_frame.timestamp.u32.lo) || hdr->m) {
+ if (PJMEDIA_VID_STREAM_SKIP_PACKETS_TO_REDUCE_LATENCY) {
+ /* Always decode whenever we have picture in jb and
+ * overwrite already decoded picture if necessary
+ */
+ pj_size_t old_size = stream->dec_frame.size;
+
+ stream->dec_frame.size = stream->dec_max_size;
+ if (decode_frame(stream, &stream->dec_frame) != PJ_SUCCESS) {
+ stream->dec_frame.size = old_size;
+ }
+ } else {
+ /* Only decode if we don't already have decoded one,
+ * unless the jb is full.
+ */
+ pj_bool_t can_decode = PJ_FALSE;
+
+ if (pjmedia_jbuf_is_full(stream->jb)) {
+ can_decode = PJ_TRUE;
+ }
+ else if (stream->dec_frame.size == 0) {
+ can_decode = PJ_TRUE;
+ }
+
+ if (can_decode) {
+ stream->dec_frame.size = stream->dec_max_size;
+ if (decode_frame(stream, &stream->dec_frame) != PJ_SUCCESS) {
+ stream->dec_frame.size = 0;
+ }
+ }
+ }
+ }
+
+ /* Put "good" packet to jitter buffer, or reset the jitter buffer
+ * when RTP session is restarted.
+ */
+ if (seq_st.status.flag.restart) {
+ status = pjmedia_jbuf_reset(stream->jb);
+ PJ_LOG(4,(channel->port.info.name.ptr, "Jitter buffer reset"));
+ } else {
+ /* Just put the payload into jitter buffer */
+ pjmedia_jbuf_put_frame3(stream->jb, payload, payloadlen, 0,
+ pj_ntohs(hdr->seq), pj_ntohl(hdr->ts), NULL);
+
+#if TRACE_JB
+ trace_jb_put(stream, hdr, payloadlen, count);
+#endif
+
+ }
+ pj_mutex_unlock( stream->jb_mutex );
+
+
+ /* Check if now is the time to transmit RTCP SR/RR report.
+ * We only do this when stream direction is "decoding only",
+ * because otherwise check_tx_rtcp() will be handled by put_frame()
+ */
+ if (stream->dir == PJMEDIA_DIR_DECODING) {
+ check_tx_rtcp(stream, pj_ntohl(hdr->ts));
+ }
+
+ if (status != 0) {
+ LOGERR_((channel->port.info.name.ptr, "Jitter buffer put() error",
+ status));
+ pkt_discarded = PJ_TRUE;
+ goto on_return;
+ }
+
+on_return:
+ /* Update RTCP session */
+ if (stream->rtcp.peer_ssrc == 0)
+ stream->rtcp.peer_ssrc = channel->rtp.peer_ssrc;
+
+ pjmedia_rtcp_rx_rtp2(&stream->rtcp, pj_ntohs(hdr->seq),
+ pj_ntohl(hdr->ts), payloadlen, pkt_discarded);
+
+ /* Send RTCP RR and SDES after we receive some RTP packets */
+ if (stream->rtcp.received >= 10 && !stream->initial_rr) {
+ status = send_rtcp(stream, !stream->rtcp_sdes_bye_disabled,
+ PJ_FALSE);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4,(stream->name.ptr, status,
+ "Error sending initial RTCP RR"));
+ } else {
+ stream->initial_rr = PJ_TRUE;
+ }
+ }
+}
+
+
+/*
+ * This callback is called by stream transport on receipt of packets
+ * in the RTCP socket.
+ */
+static void on_rx_rtcp( void *data,
+ void *pkt,
+ pj_ssize_t bytes_read)
+{
+ pjmedia_vid_stream *stream = (pjmedia_vid_stream*) data;
+
+ /* Check for errors */
+ if (bytes_read < 0) {
+ LOGERR_((stream->cname.ptr, "RTCP recv() error",
+ -bytes_read));
+ return;
+ }
+
+ pjmedia_rtcp_rx_rtcp(&stream->rtcp, pkt, bytes_read);
+}
+
+static pj_status_t put_frame(pjmedia_port *port,
+ pjmedia_frame *frame)
+{
+ pjmedia_vid_stream *stream = (pjmedia_vid_stream*) port->port_data.pdata;
+ pjmedia_vid_channel *channel = stream->enc;
+ pj_status_t status = 0;
+ pjmedia_frame frame_out;
+ unsigned rtp_ts_len;
+ void *rtphdr;
+ int rtphdrlen;
+ pj_bool_t has_more_data = PJ_FALSE;
+ pj_size_t total_sent = 0;
+ pjmedia_vid_encode_opt enc_opt;
+ unsigned pkt_cnt = 0;
+ pj_timestamp initial_time;
+
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA != 0
+ /* If the interval since last sending packet is greater than
+ * PJMEDIA_STREAM_KA_INTERVAL, send keep-alive packet.
+ */
+ if (stream->use_ka)
+ {
+ pj_uint32_t dtx_duration;
+
+ dtx_duration = pj_timestamp_diff32(&stream->last_frm_ts_sent,
+ &frame->timestamp);
+ /* Video stream keep-alive feature is currently disabled. */
+ /*
+ if (dtx_duration >
+ PJMEDIA_STREAM_KA_INTERVAL *
+ PJMEDIA_PIA_SRATE(&channel->port.info))
+ {
+ send_keep_alive_packet(stream);
+ stream->last_frm_ts_sent = frame->timestamp;
+ }
+ */
+ }
+#endif
+
+ /* Don't do anything if stream is paused */
+ if (channel->paused) {
+ return PJ_SUCCESS;
+ }
+
+ /* Get frame length in timestamp unit */
+ rtp_ts_len = stream->frame_ts_len;
+
+ /* Init frame_out buffer. */
+ frame_out.buf = ((char*)channel->buf) + sizeof(pjmedia_rtp_hdr);
+ frame_out.size = 0;
+
+ /* Init encoding option */
+ pj_bzero(&enc_opt, sizeof(enc_opt));
+ if (stream->force_keyframe) {
+ /* Force encoder to generate keyframe */
+ enc_opt.force_keyframe = PJ_TRUE;
+ stream->force_keyframe = PJ_FALSE;
+ TRC_((channel->port.info.name.ptr,
+ "Forcing encoder to generate keyframe"));
+ }
+
+ /* Encode! */
+ status = pjmedia_vid_codec_encode_begin(stream->codec, &enc_opt, frame,
+ channel->buf_size -
+ sizeof(pjmedia_rtp_hdr),
+ &frame_out,
+ &has_more_data);
+ if (status != PJ_SUCCESS) {
+ LOGERR_((channel->port.info.name.ptr,
+ "Codec encode_begin() error", status));
+
+ /* Update RTP timestamp */
+ pjmedia_rtp_encode_rtp(&channel->rtp, channel->pt, 1, 0,
+ rtp_ts_len, (const void**)&rtphdr,
+ &rtphdrlen);
+ return status;
+ }
+
+ pj_get_timestamp(&initial_time);
+
+ /* Loop while we have frame to send */
+ for (;;) {
+ status = pjmedia_rtp_encode_rtp(&channel->rtp,
+ channel->pt,
+ (has_more_data == PJ_FALSE ? 1 : 0),
+ frame_out.size,
+ rtp_ts_len,
+ (const void**)&rtphdr,
+ &rtphdrlen);
+ if (status != PJ_SUCCESS) {
+ LOGERR_((channel->port.info.name.ptr,
+ "RTP encode_rtp() error", status));
+ return status;
+ }
+
+ // Copy RTP header to the beginning of packet
+ pj_memcpy(channel->buf, rtphdr, sizeof(pjmedia_rtp_hdr));
+
+ // Send the RTP packet to the transport.
+ status = pjmedia_transport_send_rtp(stream->transport,
+ (char*)channel->buf,
+ frame_out.size +
+ sizeof(pjmedia_rtp_hdr));
+ if (status != PJ_SUCCESS) {
+ enum { COUNT_TO_REPORT = 20 };
+ if (stream->send_err_cnt++ == 0) {
+ LOGERR_((channel->port.info.name.ptr,
+ "Transport send_rtp() error",
+ status));
+ }
+ if (stream->send_err_cnt > COUNT_TO_REPORT)
+ stream->send_err_cnt = 0;
+ /* Ignore this error */
+ }
+
+ pjmedia_rtcp_tx_rtp(&stream->rtcp, frame_out.size);
+ total_sent += frame_out.size;
+ pkt_cnt++;
+
+ if (!has_more_data)
+ break;
+
+ /* Next packets use same timestamp */
+ rtp_ts_len = 0;
+
+ frame_out.size = 0;
+
+ /* Encode more! */
+ status = pjmedia_vid_codec_encode_more(stream->codec,
+ channel->buf_size -
+ sizeof(pjmedia_rtp_hdr),
+ &frame_out,
+ &has_more_data);
+ if (status != PJ_SUCCESS) {
+ LOGERR_((channel->port.info.name.ptr,
+ "Codec encode_more() error", status));
+ /* Ignore this error (?) */
+ break;
+ }
+
+ /* Send rate control */
+ if (stream->info.rc_cfg.method==PJMEDIA_VID_STREAM_RC_SIMPLE_BLOCKING)
+ {
+ pj_timestamp now, next_send_ts, total_send_ts;
+
+ total_send_ts.u64 = total_sent * stream->ts_freq.u64 * 8 /
+ stream->info.rc_cfg.bandwidth;
+ next_send_ts = initial_time;
+ pj_add_timestamp(&next_send_ts, &total_send_ts);
+
+ pj_get_timestamp(&now);
+ if (pj_cmp_timestamp(&now, &next_send_ts) < 0) {
+ unsigned ms_sleep;
+ ms_sleep = pj_elapsed_msec(&now, &next_send_ts);
+
+ if (ms_sleep > 10)
+ ms_sleep = 10;
+
+ pj_thread_sleep(ms_sleep);
+ }
+ }
+ }
+
+#if TRACE_RC
+ /* Trace log for rate control */
+ {
+ pj_timestamp end_time;
+ unsigned total_sleep;
+
+ pj_get_timestamp(&end_time);
+ total_sleep = pj_elapsed_msec(&initial_time, &end_time);
+ PJ_LOG(5, (stream->name.ptr, "total pkt=%d size=%d sleep=%d",
+ pkt_cnt, total_sent, total_sleep));
+
+ if (stream->tx_start.u64 == 0)
+ stream->tx_start = initial_time;
+ stream->tx_end = end_time;
+ stream->rc_total_pkt += pkt_cnt;
+ stream->rc_total_sleep += total_sleep;
+ stream->rc_total_img++;
+ }
+#endif
+
+ /* Check if now is the time to transmit RTCP SR/RR report.
+ * We only do this when stream direction is not "decoding only", because
+ * when it is, check_tx_rtcp() will be handled by get_frame().
+ */
+ if (stream->dir != PJMEDIA_DIR_DECODING) {
+ check_tx_rtcp(stream, pj_ntohl(channel->rtp.out_hdr.ts));
+ }
+
+ /* Do nothing if we have nothing to transmit */
+ if (total_sent == 0) {
+ return PJ_SUCCESS;
+ }
+
+ /* Update stat */
+ stream->rtcp.stat.rtp_tx_last_ts = pj_ntohl(stream->enc->rtp.out_hdr.ts);
+ stream->rtcp.stat.rtp_tx_last_seq = pj_ntohs(stream->enc->rtp.out_hdr.seq);
+
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
+ /* Update timestamp of last sending packet. */
+ stream->last_frm_ts_sent = frame->timestamp;
+#endif
+
+ return PJ_SUCCESS;
+}
+
+/* Decode one image from jitter buffer */
+static pj_status_t decode_frame(pjmedia_vid_stream *stream,
+ pjmedia_frame *frame)
+{
+ pjmedia_vid_channel *channel = stream->dec;
+ pj_uint32_t last_ts = 0;
+ int frm_first_seq = 0, frm_last_seq = 0;
+ pj_bool_t got_frame = PJ_FALSE;
+ unsigned cnt;
+ pj_status_t status;
+
+ /* Repeat get payload from the jitter buffer until all payloads with same
+ * timestamp are collected.
+ */
+
+ /* Check if we got a decodable frame */
+ for (cnt=0; ; ++cnt) {
+ char ptype;
+ pj_uint32_t ts;
+ int seq;
+
+ /* Peek frame from jitter buffer. */
+ pjmedia_jbuf_peek_frame(stream->jb, cnt, NULL, NULL,
+ &ptype, NULL, &ts, &seq);
+ if (ptype == PJMEDIA_JB_NORMAL_FRAME) {
+ if (last_ts == 0) {
+ last_ts = ts;
+ frm_first_seq = seq;
+ }
+ if (ts != last_ts) {
+ got_frame = PJ_TRUE;
+ break;
+ }
+ frm_last_seq = seq;
+ } else if (ptype == PJMEDIA_JB_ZERO_EMPTY_FRAME) {
+ /* No more packet in the jitter buffer */
+ break;
+ }
+ }
+
+ if (got_frame) {
+ unsigned i;
+
+ /* Generate frame bitstream from the payload */
+ if (cnt > stream->rx_frame_cnt) {
+ PJ_LOG(1,(channel->port.info.name.ptr,
+ "Discarding %u frames because array is full!",
+ cnt - stream->rx_frame_cnt));
+ pjmedia_jbuf_remove_frame(stream->jb, cnt - stream->rx_frame_cnt);
+ cnt = stream->rx_frame_cnt;
+ }
+
+ for (i = 0; i < cnt; ++i) {
+ char ptype;
+
+ stream->rx_frames[i].type = PJMEDIA_FRAME_TYPE_VIDEO;
+ stream->rx_frames[i].timestamp.u64 = last_ts;
+ stream->rx_frames[i].bit_info = 0;
+
+ /* We use jbuf_peek_frame() as it will returns the pointer of
+ * the payload (no buffer and memcpy needed), just as we need.
+ */
+ pjmedia_jbuf_peek_frame(stream->jb, i,
+ (const void**)&stream->rx_frames[i].buf,
+ &stream->rx_frames[i].size, &ptype,
+ NULL, NULL, NULL);
+
+ if (ptype != PJMEDIA_JB_NORMAL_FRAME) {
+ /* Packet lost, must set payload to NULL and keep going */
+ stream->rx_frames[i].buf = NULL;
+ stream->rx_frames[i].size = 0;
+ stream->rx_frames[i].type = PJMEDIA_FRAME_TYPE_NONE;
+ continue;
+ }
+ }
+
+ /* Decode */
+ status = pjmedia_vid_codec_decode(stream->codec, cnt,
+ stream->rx_frames,
+ frame->size, frame);
+ if (status != PJ_SUCCESS) {
+ LOGERR_((channel->port.info.name.ptr, "codec decode() error",
+ status));
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+ frame->size = 0;
+ }
+
+ pjmedia_jbuf_remove_frame(stream->jb, cnt);
+ }
+
+ /* Learn remote frame rate after successful decoding */
+ if (frame->type == PJMEDIA_FRAME_TYPE_VIDEO && frame->size)
+ {
+ /* Only check remote frame rate when timestamp is not wrapping and
+ * sequence is increased by 1.
+ */
+ if (last_ts > stream->last_dec_ts &&
+ frm_first_seq - stream->last_dec_seq == 1)
+ {
+ pj_uint32_t ts_diff;
+ pjmedia_video_format_detail *vfd;
+
+ ts_diff = last_ts - stream->last_dec_ts;
+ vfd = pjmedia_format_get_video_format_detail(
+ &channel->port.info.fmt, PJ_TRUE);
+ if (stream->info.codec_info.clock_rate * vfd->fps.denum !=
+ vfd->fps.num * ts_diff)
+ {
+ /* Frame rate changed, update decoding port info */
+ if (stream->info.codec_info.clock_rate % ts_diff == 0) {
+ vfd->fps.num = stream->info.codec_info.clock_rate/ts_diff;
+ vfd->fps.denum = 1;
+ } else {
+ vfd->fps.num = stream->info.codec_info.clock_rate;
+ vfd->fps.denum = ts_diff;
+ }
+
+ /* Update stream info */
+ stream->info.codec_param->dec_fmt.det.vid.fps = vfd->fps;
+
+ /* Publish PJMEDIA_EVENT_FMT_CHANGED event if frame rate
+ * increased and not exceeding 100fps.
+ */
+ if (vfd->fps.num/vfd->fps.denum <= 100 &&
+ vfd->fps.num * stream->dec_max_fps.denum >
+ stream->dec_max_fps.num * vfd->fps.denum)
+ {
+ pjmedia_event *event = &stream->fmt_event;
+
+ /* Update max fps of decoding dir */
+ stream->dec_max_fps = vfd->fps;
+
+ /* Use the buffered format changed event:
+ * - just update the framerate if there is pending event,
+ * - otherwise, init the whole event.
+ */
+ if (stream->fmt_event.type != PJMEDIA_EVENT_NONE) {
+ event->data.fmt_changed.new_fmt.det.vid.fps = vfd->fps;
+ } else {
+ pjmedia_event_init(event, PJMEDIA_EVENT_FMT_CHANGED,
+ &frame->timestamp, stream);
+ event->data.fmt_changed.dir = PJMEDIA_DIR_DECODING;
+ pj_memcpy(&event->data.fmt_changed.new_fmt,
+ &stream->info.codec_param->dec_fmt,
+ sizeof(pjmedia_format));
+ }
+ }
+ }
+ }
+
+ /* Update last frame seq and timestamp */
+ stream->last_dec_seq = frm_last_seq;
+ stream->last_dec_ts = last_ts;
+ }
+
+ return got_frame ? PJ_SUCCESS : PJ_ENOTFOUND;
+}
+
+
+static pj_status_t get_frame(pjmedia_port *port,
+ pjmedia_frame *frame)
+{
+ pjmedia_vid_stream *stream = (pjmedia_vid_stream*) port->port_data.pdata;
+ pjmedia_vid_channel *channel = stream->dec;
+
+ /* Return no frame is channel is paused */
+ if (channel->paused) {
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+ frame->size = 0;
+ return PJ_SUCCESS;
+ }
+
+ /* Report pending events. Do not publish the event while holding the
+ * jb_mutex as that would lead to deadlock. It should be safe to
+ * operate on fmt_event without the mutex because format change normally
+ * would only occur once during the start of the media.
+ */
+ if (stream->fmt_event.type != PJMEDIA_EVENT_NONE) {
+ pjmedia_event_fmt_changed_data *fmt_chg_data;
+
+ fmt_chg_data = &stream->fmt_event.data.fmt_changed;
+
+ /* Update stream info and decoding channel port info */
+ if (fmt_chg_data->dir == PJMEDIA_DIR_DECODING) {
+ pjmedia_format_copy(&stream->info.codec_param->dec_fmt,
+ &fmt_chg_data->new_fmt);
+ pjmedia_format_copy(&stream->dec->port.info.fmt,
+ &fmt_chg_data->new_fmt);
+
+ /* Override the framerate to be 1.5x higher in the event
+ * for the renderer.
+ */
+ fmt_chg_data->new_fmt.det.vid.fps.num *= 3;
+ fmt_chg_data->new_fmt.det.vid.fps.num /= 2;
+ } else {
+ pjmedia_format_copy(&stream->info.codec_param->enc_fmt,
+ &fmt_chg_data->new_fmt);
+ pjmedia_format_copy(&stream->enc->port.info.fmt,
+ &fmt_chg_data->new_fmt);
+ }
+
+ dump_port_info(fmt_chg_data->dir==PJMEDIA_DIR_DECODING ?
+ stream->dec : stream->enc,
+ "changed");
+
+ pjmedia_event_publish(NULL, port, &stream->fmt_event, 0);
+
+ stream->fmt_event.type = PJMEDIA_EVENT_NONE;
+ }
+
+ if (stream->miss_keyframe_event.type != PJMEDIA_EVENT_NONE) {
+ pjmedia_event_publish(NULL, port, &stream->miss_keyframe_event,
+ PJMEDIA_EVENT_PUBLISH_POST_EVENT);
+ stream->miss_keyframe_event.type = PJMEDIA_EVENT_NONE;
+ }
+
+ pj_mutex_lock( stream->jb_mutex );
+
+ if (stream->dec_frame.size == 0) {
+ /* Don't have frame in buffer, try to decode one */
+ if (decode_frame(stream, frame) != PJ_SUCCESS) {
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+ frame->size = 0;
+ }
+ } else {
+ if (frame->size < stream->dec_frame.size) {
+ PJ_LOG(4,(stream->dec->port.info.name.ptr,
+ "Error: not enough buffer for decoded frame "
+ "(supplied=%d, required=%d)",
+ (int)frame->size, (int)stream->dec_frame.size));
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+ frame->size = 0;
+ } else {
+ frame->type = stream->dec_frame.type;
+ frame->timestamp = stream->dec_frame.timestamp;
+ frame->size = stream->dec_frame.size;
+ pj_memcpy(frame->buf, stream->dec_frame.buf, frame->size);
+ }
+
+ stream->dec_frame.size = 0;
+ }
+
+ pj_mutex_unlock( stream->jb_mutex );
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Create media channel.
+ */
+static pj_status_t create_channel( pj_pool_t *pool,
+ pjmedia_vid_stream *stream,
+ pjmedia_dir dir,
+ unsigned pt,
+ const pjmedia_vid_stream_info *info,
+ pjmedia_vid_channel **p_channel)
+{
+ enum { M = 32 };
+ pjmedia_vid_channel *channel;
+ pj_status_t status;
+ unsigned min_out_pkt_size;
+ pj_str_t name;
+ const char *type_name;
+ pjmedia_format *fmt;
+ char fourcc_name[5];
+ pjmedia_port_info *pi;
+
+ pj_assert(info->type == PJMEDIA_TYPE_VIDEO);
+ pj_assert(dir == PJMEDIA_DIR_DECODING || dir == PJMEDIA_DIR_ENCODING);
+
+ /* Allocate memory for channel descriptor */
+ channel = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_channel);
+ PJ_ASSERT_RETURN(channel != NULL, PJ_ENOMEM);
+
+ /* Init vars */
+ if (dir==PJMEDIA_DIR_DECODING) {
+ type_name = "vstdec";
+ fmt = &info->codec_param->dec_fmt;
+ } else {
+ type_name = "vstenc";
+ fmt = &info->codec_param->enc_fmt;
+ }
+ name.ptr = (char*) pj_pool_alloc(pool, M);
+ name.slen = pj_ansi_snprintf(name.ptr, M, "%s%p", type_name, stream);
+ pi = &channel->port.info;
+
+ /* Init channel info. */
+ channel->stream = stream;
+ channel->dir = dir;
+ channel->paused = 1;
+ channel->pt = pt;
+
+ /* Allocate buffer for outgoing packet. */
+ if (dir == PJMEDIA_DIR_ENCODING) {
+ channel->buf_size = sizeof(pjmedia_rtp_hdr) + stream->frame_size;
+
+ /* It should big enough to hold (minimally) RTCP SR with an SDES. */
+ min_out_pkt_size = sizeof(pjmedia_rtcp_sr_pkt) +
+ sizeof(pjmedia_rtcp_common) +
+ (4 + stream->cname.slen) +
+ 32;
+
+ if (channel->buf_size < min_out_pkt_size)
+ channel->buf_size = min_out_pkt_size;
+
+ channel->buf = pj_pool_alloc(pool, channel->buf_size);
+ PJ_ASSERT_RETURN(channel->buf != NULL, PJ_ENOMEM);
+ }
+
+ /* Create RTP and RTCP sessions: */
+ if (info->rtp_seq_ts_set == 0) {
+ status = pjmedia_rtp_session_init(&channel->rtp, pt, info->ssrc);
+ } else {
+ pjmedia_rtp_session_setting settings;
+
+ settings.flags = (pj_uint8_t)((info->rtp_seq_ts_set << 2) | 3);
+ settings.default_pt = pt;
+ settings.sender_ssrc = info->ssrc;
+ settings.seq = info->rtp_seq;
+ settings.ts = info->rtp_ts;
+ status = pjmedia_rtp_session_init2(&channel->rtp, settings);
+ }
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Init port. */
+ pjmedia_port_info_init2(pi, &name, SIGNATURE, dir, fmt);
+ if (dir == PJMEDIA_DIR_DECODING) {
+ channel->port.get_frame = &get_frame;
+ } else {
+ pi->fmt.id = info->codec_param->dec_fmt.id;
+ channel->port.put_frame = &put_frame;
+ }
+
+ /* Init port. */
+ channel->port.port_data.pdata = stream;
+
+ PJ_LOG(5, (name.ptr,
+ "%s channel created %dx%d %s%s%.*s %d/%d(~%d)fps",
+ (dir==PJMEDIA_DIR_ENCODING?"Encoding":"Decoding"),
+ pi->fmt.det.vid.size.w, pi->fmt.det.vid.size.h,
+ pjmedia_fourcc_name(pi->fmt.id, fourcc_name),
+ (dir==PJMEDIA_DIR_ENCODING?"->":"<-"),
+ info->codec_info.encoding_name.slen,
+ info->codec_info.encoding_name.ptr,
+ pi->fmt.det.vid.fps.num, pi->fmt.det.vid.fps.denum,
+ pi->fmt.det.vid.fps.num/pi->fmt.det.vid.fps.denum));
+
+ /* Done. */
+ *p_channel = channel;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create stream.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_stream_create(
+ pjmedia_endpt *endpt,
+ pj_pool_t *pool,
+ pjmedia_vid_stream_info *info,
+ pjmedia_transport *tp,
+ void *user_data,
+ pjmedia_vid_stream **p_stream)
+{
+ enum { M = 32 };
+ pj_pool_t *own_pool = NULL;
+ pjmedia_vid_stream *stream;
+ unsigned jb_init, jb_max, jb_min_pre, jb_max_pre;
+ int frm_ptime, chunks_per_frm;
+ pjmedia_video_format_detail *vfd_enc, *vfd_dec;
+ char *p;
+ unsigned dec_mtu;
+ pj_status_t status;
+
+ if (!pool) {
+ own_pool = pjmedia_endpt_create_pool( endpt, "vstrm%p",
+ PJMEDIA_VSTREAM_SIZE,
+ PJMEDIA_VSTREAM_INC);
+ PJ_ASSERT_RETURN(own_pool != NULL, PJ_ENOMEM);
+ pool = own_pool;
+ }
+
+ /* Allocate stream */
+ stream = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_stream);
+ PJ_ASSERT_RETURN(stream != NULL, PJ_ENOMEM);
+ stream->own_pool = own_pool;
+
+ /* Get codec manager */
+ stream->codec_mgr = pjmedia_vid_codec_mgr_instance();
+ PJ_ASSERT_RETURN(stream->codec_mgr, PJMEDIA_CODEC_EFAILED);
+
+ /* Init stream/port name */
+ stream->name.ptr = (char*) pj_pool_alloc(pool, M);
+ stream->name.slen = pj_ansi_snprintf(stream->name.ptr, M,
+ "vstrm%p", stream);
+
+ /* Create and initialize codec: */
+ status = pjmedia_vid_codec_mgr_alloc_codec(stream->codec_mgr,
+ &info->codec_info,
+ &stream->codec);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Get codec param: */
+ if (!info->codec_param) {
+ pjmedia_vid_codec_param def_param;
+
+ status = pjmedia_vid_codec_mgr_get_default_param(stream->codec_mgr,
+ &info->codec_info,
+ &def_param);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ info->codec_param = pjmedia_vid_codec_param_clone(pool, &def_param);
+ pj_assert(info->codec_param);
+ }
+
+ /* Init codec param and adjust MTU */
+ info->codec_param->dir = info->dir;
+ info->codec_param->enc_mtu -= (sizeof(pjmedia_rtp_hdr) +
+ PJMEDIA_STREAM_RESV_PAYLOAD_LEN);
+ if (info->codec_param->enc_mtu > PJMEDIA_MAX_MTU)
+ info->codec_param->enc_mtu = PJMEDIA_MAX_MTU;
+
+ /* MTU estimation for decoding direction */
+ dec_mtu = PJMEDIA_MAX_MTU;
+
+ vfd_enc = pjmedia_format_get_video_format_detail(
+ &info->codec_param->enc_fmt, PJ_TRUE);
+ vfd_dec = pjmedia_format_get_video_format_detail(
+ &info->codec_param->dec_fmt, PJ_TRUE);
+
+ /* Init stream: */
+ stream->endpt = endpt;
+ stream->dir = info->dir;
+ stream->user_data = user_data;
+ stream->rtcp_interval = (PJMEDIA_RTCP_INTERVAL-500 + (pj_rand()%1000)) *
+ info->codec_info.clock_rate / 1000;
+ stream->rtcp_sdes_bye_disabled = info->rtcp_sdes_bye_disabled;
+
+ stream->jb_last_frm = PJMEDIA_JB_NORMAL_FRAME;
+
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
+ stream->use_ka = info->use_ka;
+#endif
+
+ /* Build random RTCP CNAME. CNAME has user@host format */
+ stream->cname.ptr = p = (char*) pj_pool_alloc(pool, 20);
+ pj_create_random_string(p, 5);
+ p += 5;
+ *p++ = '@'; *p++ = 'p'; *p++ = 'j';
+ pj_create_random_string(p, 6);
+ p += 6;
+ *p++ = '.'; *p++ = 'o'; *p++ = 'r'; *p++ = 'g';
+ stream->cname.slen = p - stream->cname.ptr;
+
+
+ /* Create mutex to protect jitter buffer: */
+
+ status = pj_mutex_create_simple(pool, NULL, &stream->jb_mutex);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Init and open the codec. */
+ status = pjmedia_vid_codec_init(stream->codec, pool);
+ if (status != PJ_SUCCESS)
+ return status;
+ status = pjmedia_vid_codec_open(stream->codec, info->codec_param);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Subscribe to codec events */
+ pjmedia_event_subscribe(NULL, &stream_event_cb, stream,
+ stream->codec);
+
+ /* Estimate the maximum frame size */
+ stream->frame_size = vfd_enc->size.w * vfd_enc->size.h * 4;
+
+#if 0
+ stream->frame_size = vfd_enc->max_bps/8 * vfd_enc->fps.denum /
+ vfd_enc->fps.num;
+
+ /* As the maximum frame_size is not represented directly by maximum bps
+ * (which includes intra and predicted frames), let's increase the
+ * frame size value for safety.
+ */
+ stream->frame_size <<= 4;
+#endif
+
+ /* Validate the frame size */
+ if (stream->frame_size == 0 ||
+ stream->frame_size > PJMEDIA_MAX_VIDEO_ENC_FRAME_SIZE)
+ {
+ stream->frame_size = PJMEDIA_MAX_VIDEO_ENC_FRAME_SIZE;
+ }
+
+ /* Get frame length in timestamp unit */
+ stream->frame_ts_len = info->codec_info.clock_rate *
+ vfd_enc->fps.denum / vfd_enc->fps.num;
+
+ /* Initialize send rate states */
+ pj_get_timestamp_freq(&stream->ts_freq);
+ if (info->rc_cfg.bandwidth == 0)
+ info->rc_cfg.bandwidth = vfd_enc->max_bps;
+
+ /* For simple blocking, need to have bandwidth large enough, otherwise
+ * we can slow down the transmission too much
+ */
+ if (info->rc_cfg.method==PJMEDIA_VID_STREAM_RC_SIMPLE_BLOCKING &&
+ info->rc_cfg.bandwidth < vfd_enc->avg_bps * 3)
+ {
+ info->rc_cfg.bandwidth = vfd_enc->avg_bps * 3;
+ }
+
+ /* Override the initial framerate in the decoding direction. This initial
+ * value will be used by the renderer to configure its clock, and setting
+ * it to a bit higher value can avoid the possibility of high latency
+ * caused by clock drift (remote encoder clock runs slightly faster than
+ * local renderer clock) or video setup lag. Note that the actual framerate
+ * will be continuously calculated based on the incoming RTP timestamps.
+ */
+ vfd_dec->fps.num = vfd_dec->fps.num * 3 / 2;
+ stream->dec_max_fps = vfd_dec->fps;
+
+ /* Create decoder channel */
+ status = create_channel( pool, stream, PJMEDIA_DIR_DECODING,
+ info->rx_pt, info, &stream->dec);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Create encoder channel */
+ status = create_channel( pool, stream, PJMEDIA_DIR_ENCODING,
+ info->tx_pt, info, &stream->enc);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Create temporary buffer for immediate decoding */
+ stream->dec_max_size = vfd_dec->size.w * vfd_dec->size.h * 4;
+ stream->dec_frame.buf = pj_pool_alloc(pool, stream->dec_max_size);
+
+ /* Init jitter buffer parameters: */
+ frm_ptime = 1000 * vfd_enc->fps.denum / vfd_enc->fps.num;
+ chunks_per_frm = stream->frame_size / dec_mtu;
+ if (chunks_per_frm == 0) chunks_per_frm = 1;
+
+ /* JB max count, default 500ms */
+ if (info->jb_max >= frm_ptime)
+ jb_max = info->jb_max * chunks_per_frm / frm_ptime;
+ else
+ jb_max = 500 * chunks_per_frm / frm_ptime;
+
+ /* JB min prefetch, default 1 frame */
+ if (info->jb_min_pre >= frm_ptime)
+ jb_min_pre = info->jb_min_pre * chunks_per_frm / frm_ptime;
+ else
+ jb_min_pre = 1;
+
+ /* JB max prefetch, default 4/5 JB max count */
+ if (info->jb_max_pre >= frm_ptime)
+ jb_max_pre = info->jb_max_pre * chunks_per_frm / frm_ptime;
+ else
+ jb_max_pre = jb_max * 4 / 5;
+
+ /* JB init prefetch, default 0 */
+ if (info->jb_init >= frm_ptime)
+ jb_init = info->jb_init * chunks_per_frm / frm_ptime;
+ else
+ jb_init = 0;
+
+ /* Allocate array for temporary storage for assembly of incoming
+ * frames. Add more just in case.
+ */
+ stream->rx_frame_cnt = chunks_per_frm * 2;
+ stream->rx_frames = pj_pool_calloc(pool, stream->rx_frame_cnt,
+ sizeof(stream->rx_frames[0]));
+
+ /* Create jitter buffer */
+ status = pjmedia_jbuf_create(pool, &stream->dec->port.info.name,
+ dec_mtu + PJMEDIA_STREAM_RESV_PAYLOAD_LEN,
+ 1000 * vfd_enc->fps.denum / vfd_enc->fps.num,
+ jb_max, &stream->jb);
+ if (status != PJ_SUCCESS)
+ return status;
+
+
+ /* Set up jitter buffer */
+ pjmedia_jbuf_set_adaptive(stream->jb, jb_init, jb_min_pre, jb_max_pre);
+ pjmedia_jbuf_set_discard(stream->jb, PJMEDIA_JB_DISCARD_NONE);
+
+ /* Init RTCP session: */
+ {
+ pjmedia_rtcp_session_setting rtcp_setting;
+
+ pjmedia_rtcp_session_setting_default(&rtcp_setting);
+ rtcp_setting.name = stream->name.ptr;
+ rtcp_setting.ssrc = info->ssrc;
+ rtcp_setting.rtp_ts_base = pj_ntohl(stream->enc->rtp.out_hdr.ts);
+ rtcp_setting.clock_rate = info->codec_info.clock_rate;
+ rtcp_setting.samples_per_frame = 1;
+
+ pjmedia_rtcp_init2(&stream->rtcp, &rtcp_setting);
+ }
+
+ /* Allocate outgoing RTCP buffer, should be enough to hold SR/RR, SDES,
+ * BYE, and XR.
+ */
+ stream->out_rtcp_pkt_size = sizeof(pjmedia_rtcp_sr_pkt) +
+ sizeof(pjmedia_rtcp_common) +
+ (4 + stream->cname.slen) +
+ 32;
+ if (stream->out_rtcp_pkt_size > PJMEDIA_MAX_MTU)
+ stream->out_rtcp_pkt_size = PJMEDIA_MAX_MTU;
+
+ stream->out_rtcp_pkt = pj_pool_alloc(pool, stream->out_rtcp_pkt_size);
+
+ /* Only attach transport when stream is ready. */
+ status = pjmedia_transport_attach(tp, stream, &info->rem_addr,
+ &info->rem_rtcp,
+ pj_sockaddr_get_len(&info->rem_addr),
+ &on_rx_rtp, &on_rx_rtcp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ stream->transport = tp;
+
+ /* Send RTCP SDES */
+ if (!stream->rtcp_sdes_bye_disabled) {
+ pjmedia_vid_stream_send_rtcp_sdes(stream);
+ }
+
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
+ /* NAT hole punching by sending KA packet via RTP transport. */
+ if (stream->use_ka)
+ send_keep_alive_packet(stream);
+#endif
+
+#if TRACE_JB
+ {
+ char trace_name[PJ_MAXPATH];
+ pj_ssize_t len;
+
+ pj_ansi_snprintf(trace_name, sizeof(trace_name),
+ TRACE_JB_PATH_PREFIX "%s.csv",
+ channel->port.info.name.ptr);
+ status = pj_file_open(pool, trace_name, PJ_O_RDWR,
+ &stream->trace_jb_fd);
+ if (status != PJ_SUCCESS) {
+ stream->trace_jb_fd = TRACE_JB_INVALID_FD;
+ PJ_LOG(3,(THIS_FILE, "Failed creating RTP trace file '%s'",
+ trace_name));
+ } else {
+ stream->trace_jb_buf = (char*)pj_pool_alloc(pool, PJ_LOG_MAX_SIZE);
+
+ /* Print column header */
+ len = pj_ansi_snprintf(stream->trace_jb_buf, PJ_LOG_MAX_SIZE,
+ "Time, Operation, Size, Frame Count, "
+ "Frame type, RTP Seq, RTP TS, RTP M, "
+ "JB size, JB burst level, JB prefetch\n");
+ pj_file_write(stream->trace_jb_fd, stream->trace_jb_buf, &len);
+ pj_file_flush(stream->trace_jb_fd);
+ }
+ }
+#endif
+
+ /* Save the stream info */
+ pj_memcpy(&stream->info, info, sizeof(*info));
+ stream->info.codec_param = pjmedia_vid_codec_param_clone(
+ pool, info->codec_param);
+
+ /* Success! */
+ *p_stream = stream;
+
+ PJ_LOG(5,(THIS_FILE, "Video stream %s created", stream->name.ptr));
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy stream.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_stream_destroy( pjmedia_vid_stream *stream )
+{
+ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+#if TRACE_RC
+ {
+ unsigned total_time;
+
+ total_time = pj_elapsed_msec(&stream->tx_start, &stream->tx_end);
+ PJ_LOG(5, (stream->name.ptr,
+ "RC stat: pkt_cnt=%.2f/image, sleep=%.2fms/s, fps=%.2f",
+ stream->rc_total_pkt*1.0/stream->rc_total_img,
+ stream->rc_total_sleep*1000.0/total_time,
+ stream->rc_total_img*1000.0/total_time));
+ }
+#endif
+
+ /* Send RTCP BYE (also SDES) */
+ if (!stream->rtcp_sdes_bye_disabled) {
+ send_rtcp(stream, PJ_TRUE, PJ_TRUE);
+ }
+
+ /* Detach from transport
+ * MUST NOT hold stream mutex while detaching from transport, as
+ * it may cause deadlock. See ticket #460 for the details.
+ */
+ if (stream->transport) {
+ pjmedia_transport_detach(stream->transport, stream);
+ stream->transport = NULL;
+ }
+
+ /* This function may be called when stream is partly initialized. */
+ if (stream->jb_mutex)
+ pj_mutex_lock(stream->jb_mutex);
+
+
+ /* Free codec. */
+ if (stream->codec) {
+ pjmedia_event_unsubscribe(NULL, &stream_event_cb, stream,
+ stream->codec);
+ pjmedia_vid_codec_close(stream->codec);
+ pjmedia_vid_codec_mgr_dealloc_codec(stream->codec_mgr, stream->codec);
+ stream->codec = NULL;
+ }
+
+ /* Free mutex */
+
+ if (stream->jb_mutex) {
+ pj_mutex_destroy(stream->jb_mutex);
+ stream->jb_mutex = NULL;
+ }
+
+ /* Destroy jitter buffer */
+ if (stream->jb) {
+ pjmedia_jbuf_destroy(stream->jb);
+ stream->jb = NULL;
+ }
+
+#if TRACE_JB
+ if (TRACE_JB_OPENED(stream)) {
+ pj_file_close(stream->trace_jb_fd);
+ stream->trace_jb_fd = TRACE_JB_INVALID_FD;
+ }
+#endif
+
+ if (stream->own_pool) {
+ pj_pool_t *pool = stream->own_pool;
+ stream->own_pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the port interface.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_stream_get_port(pjmedia_vid_stream *stream,
+ pjmedia_dir dir,
+ pjmedia_port **p_port )
+{
+ PJ_ASSERT_RETURN(dir==PJMEDIA_DIR_ENCODING || dir==PJMEDIA_DIR_DECODING,
+ PJ_EINVAL);
+
+ if (dir == PJMEDIA_DIR_ENCODING)
+ *p_port = &stream->enc->port;
+ else
+ *p_port = &stream->dec->port;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the transport object
+ */
+PJ_DEF(pjmedia_transport*) pjmedia_vid_stream_get_transport(
+ pjmedia_vid_stream *st)
+{
+ return st->transport;
+}
+
+
+/*
+ * Get stream statistics.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_stream_get_stat(
+ const pjmedia_vid_stream *stream,
+ pjmedia_rtcp_stat *stat)
+{
+ PJ_ASSERT_RETURN(stream && stat, PJ_EINVAL);
+
+ pj_memcpy(stat, &stream->rtcp.stat, sizeof(pjmedia_rtcp_stat));
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Reset the stream statistics in the middle of a stream session.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_stream_reset_stat(pjmedia_vid_stream *stream)
+{
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ pjmedia_rtcp_init_stat(&stream->rtcp.stat);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get jitter buffer state.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_stream_get_stat_jbuf(
+ const pjmedia_vid_stream *stream,
+ pjmedia_jb_state *state)
+{
+ PJ_ASSERT_RETURN(stream && state, PJ_EINVAL);
+ return pjmedia_jbuf_get_state(stream->jb, state);
+}
+
+
+/*
+ * Get the stream info.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_stream_get_info(
+ const pjmedia_vid_stream *stream,
+ pjmedia_vid_stream_info *info)
+{
+ PJ_ASSERT_RETURN(stream && info, PJ_EINVAL);
+ pj_memcpy(info, &stream->info, sizeof(*info));
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Start stream.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_stream_start(pjmedia_vid_stream *stream)
+{
+
+ PJ_ASSERT_RETURN(stream && stream->enc && stream->dec, PJ_EINVALIDOP);
+
+ if (stream->enc && (stream->dir & PJMEDIA_DIR_ENCODING)) {
+ stream->enc->paused = 0;
+ //pjmedia_snd_stream_start(stream->enc->snd_stream);
+ PJ_LOG(4,(stream->enc->port.info.name.ptr, "Encoder stream started"));
+ } else {
+ PJ_LOG(4,(stream->enc->port.info.name.ptr, "Encoder stream paused"));
+ }
+
+ if (stream->dec && (stream->dir & PJMEDIA_DIR_DECODING)) {
+ stream->dec->paused = 0;
+ //pjmedia_snd_stream_start(stream->dec->snd_stream);
+ PJ_LOG(4,(stream->dec->port.info.name.ptr, "Decoder stream started"));
+ } else {
+ PJ_LOG(4,(stream->dec->port.info.name.ptr, "Decoder stream paused"));
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Check status.
+ */
+PJ_DEF(pj_bool_t) pjmedia_vid_stream_is_running(pjmedia_vid_stream *stream,
+ pjmedia_dir dir)
+{
+ pj_bool_t is_running = PJ_TRUE;
+
+ PJ_ASSERT_RETURN(stream, PJ_FALSE);
+
+ if (dir & PJMEDIA_DIR_ENCODING) {
+ is_running &= (stream->enc && !stream->enc->paused);
+ }
+
+ if (dir & PJMEDIA_DIR_DECODING) {
+ is_running &= (stream->dec && !stream->dec->paused);
+ }
+
+ return is_running;
+}
+
+/*
+ * Pause stream.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_stream_pause(pjmedia_vid_stream *stream,
+ pjmedia_dir dir)
+{
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ if ((dir & PJMEDIA_DIR_ENCODING) && stream->enc) {
+ stream->enc->paused = 1;
+ PJ_LOG(4,(stream->enc->port.info.name.ptr, "Encoder stream paused"));
+ }
+
+ if ((dir & PJMEDIA_DIR_DECODING) && stream->dec) {
+ stream->dec->paused = 1;
+
+ /* Also reset jitter buffer */
+ pj_mutex_lock( stream->jb_mutex );
+ pjmedia_jbuf_reset(stream->jb);
+ pj_mutex_unlock( stream->jb_mutex );
+
+ PJ_LOG(4,(stream->dec->port.info.name.ptr, "Decoder stream paused"));
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Resume stream
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_stream_resume(pjmedia_vid_stream *stream,
+ pjmedia_dir dir)
+{
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ if ((dir & PJMEDIA_DIR_ENCODING) && stream->enc) {
+ stream->enc->paused = 0;
+ PJ_LOG(4,(stream->enc->port.info.name.ptr, "Encoder stream resumed"));
+ }
+
+ if ((dir & PJMEDIA_DIR_DECODING) && stream->dec) {
+ stream->dec->paused = 0;
+ PJ_LOG(4,(stream->dec->port.info.name.ptr, "Decoder stream resumed"));
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Force stream to send video keyframe.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_stream_send_keyframe(
+ pjmedia_vid_stream *stream)
+{
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ if (!pjmedia_vid_stream_is_running(stream, PJMEDIA_DIR_ENCODING))
+ return PJ_EINVALIDOP;
+
+ stream->force_keyframe = PJ_TRUE;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Send RTCP SDES.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_stream_send_rtcp_sdes(
+ pjmedia_vid_stream *stream)
+{
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ return send_rtcp(stream, PJ_TRUE, PJ_FALSE);
+}
+
+
+/*
+ * Send RTCP BYE.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_stream_send_rtcp_bye(
+ pjmedia_vid_stream *stream)
+{
+ PJ_ASSERT_RETURN(stream, PJ_EINVAL);
+
+ if (stream->enc && stream->transport) {
+ return send_rtcp(stream, PJ_TRUE, PJ_TRUE);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Initialize the video stream rate control with default settings.
+ */
+PJ_DEF(void)
+pjmedia_vid_stream_rc_config_default(pjmedia_vid_stream_rc_config *cfg)
+{
+ pj_bzero(cfg, sizeof(*cfg));
+ cfg->method = PJMEDIA_VID_STREAM_RC_SIMPLE_BLOCKING;
+}
+
+
+#endif /* PJMEDIA_HAS_VIDEO */
diff --git a/pjmedia/src/pjmedia/vid_stream_info.c b/pjmedia/src/pjmedia/vid_stream_info.c
new file mode 100644
index 0000000..51b688f
--- /dev/null
+++ b/pjmedia/src/pjmedia/vid_stream_info.c
@@ -0,0 +1,385 @@
+/* $Id: vid_stream_info.c 3982 2012-03-22 09:56:52Z bennylp $ */
+/*
+ * Copyright (C) 2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia/vid_stream.h>
+#include <pjmedia/sdp_neg.h>
+#include <pjmedia/stream_common.h>
+#include <pj/ctype.h>
+#include <pj/rand.h>
+
+
+static const pj_str_t ID_VIDEO = { "video", 5};
+static const pj_str_t ID_IN = { "IN", 2 };
+static const pj_str_t ID_IP4 = { "IP4", 3};
+static const pj_str_t ID_IP6 = { "IP6", 3};
+static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 };
+static const pj_str_t ID_RTP_SAVP = { "RTP/SAVP", 8 };
+//static const pj_str_t ID_SDP_NAME = { "pjmedia", 7 };
+static const pj_str_t ID_RTPMAP = { "rtpmap", 6 };
+
+static const pj_str_t STR_INACTIVE = { "inactive", 8 };
+static const pj_str_t STR_SENDRECV = { "sendrecv", 8 };
+static const pj_str_t STR_SENDONLY = { "sendonly", 8 };
+static const pj_str_t STR_RECVONLY = { "recvonly", 8 };
+
+
+/*
+ * Internal function for collecting codec info and param from the SDP media.
+ */
+static pj_status_t get_video_codec_info_param(pjmedia_vid_stream_info *si,
+ pj_pool_t *pool,
+ pjmedia_vid_codec_mgr *mgr,
+ const pjmedia_sdp_media *local_m,
+ const pjmedia_sdp_media *rem_m)
+{
+ unsigned pt = 0;
+ const pjmedia_vid_codec_info *p_info;
+ pj_status_t status;
+
+ pt = pj_strtoul(&local_m->desc.fmt[0]);
+
+ /* Get payload type for receiving direction */
+ si->rx_pt = pt;
+
+ /* Get codec info and payload type for transmitting direction. */
+ if (pt < 96) {
+ /* For static payload types, get the codec info from codec manager. */
+ status = pjmedia_vid_codec_mgr_get_codec_info(mgr, pt, &p_info);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ si->codec_info = *p_info;
+
+ /* Get payload type for transmitting direction.
+ * For static payload type, pt's are symetric.
+ */
+ si->tx_pt = pt;
+ } else {
+ const pjmedia_sdp_attr *attr;
+ pjmedia_sdp_rtpmap *rtpmap;
+ pjmedia_codec_id codec_id;
+ pj_str_t codec_id_st;
+ unsigned i;
+
+ /* Determine payload type for outgoing channel, by finding
+ * dynamic payload type in remote SDP that matches the answer.
+ */
+ si->tx_pt = 0xFFFF;
+ for (i=0; i<rem_m->desc.fmt_count; ++i) {
+ if (pjmedia_sdp_neg_fmt_match(NULL,
+ (pjmedia_sdp_media*)local_m, 0,
+ (pjmedia_sdp_media*)rem_m, i, 0) ==
+ PJ_SUCCESS)
+ {
+ /* Found matched codec. */
+ si->tx_pt = pj_strtoul(&rem_m->desc.fmt[i]);
+ break;
+ }
+ }
+
+ if (si->tx_pt == 0xFFFF)
+ return PJMEDIA_EMISSINGRTPMAP;
+
+ /* For dynamic payload types, get codec name from the rtpmap */
+ attr = pjmedia_sdp_media_find_attr(local_m, &ID_RTPMAP,
+ &local_m->desc.fmt[0]);
+ if (attr == NULL)
+ return PJMEDIA_EMISSINGRTPMAP;
+
+ status = pjmedia_sdp_attr_to_rtpmap(pool, attr, &rtpmap);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Then get the codec info from the codec manager */
+ pj_ansi_snprintf(codec_id, sizeof(codec_id), "%.*s/",
+ (int)rtpmap->enc_name.slen, rtpmap->enc_name.ptr);
+ codec_id_st = pj_str(codec_id);
+ i = 1;
+ status = pjmedia_vid_codec_mgr_find_codecs_by_id(mgr, &codec_id_st,
+ &i, &p_info, NULL);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ si->codec_info = *p_info;
+ }
+
+
+ /* Request for codec with the correct packing for streaming */
+ si->codec_info.packings = PJMEDIA_VID_PACKING_PACKETS;
+
+ /* Now that we have codec info, get the codec param. */
+ si->codec_param = PJ_POOL_ALLOC_T(pool, pjmedia_vid_codec_param);
+ status = pjmedia_vid_codec_mgr_get_default_param(mgr,
+ &si->codec_info,
+ si->codec_param);
+
+ /* Adjust encoding bitrate, if higher than remote preference. The remote
+ * bitrate preference is read from SDP "b=TIAS" line in media level.
+ */
+ if ((si->dir & PJMEDIA_DIR_ENCODING) && rem_m->bandw_count) {
+ unsigned i, bandw = 0;
+
+ for (i = 0; i < rem_m->bandw_count; ++i) {
+ const pj_str_t STR_BANDW_MODIFIER_TIAS = { "TIAS", 4 };
+ if (!pj_stricmp(&rem_m->bandw[i]->modifier,
+ &STR_BANDW_MODIFIER_TIAS))
+ {
+ bandw = rem_m->bandw[i]->value;
+ break;
+ }
+ }
+
+ if (bandw) {
+ pjmedia_video_format_detail *enc_vfd;
+ enc_vfd = pjmedia_format_get_video_format_detail(
+ &si->codec_param->enc_fmt, PJ_TRUE);
+ if (!enc_vfd->avg_bps || enc_vfd->avg_bps > bandw)
+ enc_vfd->avg_bps = bandw * 3 / 4;
+ if (!enc_vfd->max_bps || enc_vfd->max_bps > bandw)
+ enc_vfd->max_bps = bandw;
+ }
+ }
+
+ /* Get remote fmtp for our encoder. */
+ pjmedia_stream_info_parse_fmtp(pool, rem_m, si->tx_pt,
+ &si->codec_param->enc_fmtp);
+
+ /* Get local fmtp for our decoder. */
+ pjmedia_stream_info_parse_fmtp(pool, local_m, si->rx_pt,
+ &si->codec_param->dec_fmtp);
+
+ /* When direction is NONE (it means SDP negotiation has failed) we don't
+ * need to return a failure here, as returning failure will cause
+ * the whole SDP to be rejected. See ticket #:
+ * http://
+ *
+ * Thanks Alain Totouom
+ */
+ if (status != PJ_SUCCESS && si->dir != PJMEDIA_DIR_NONE)
+ return status;
+
+ return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Create stream info from SDP media line.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_stream_info_from_sdp(
+ pjmedia_vid_stream_info *si,
+ pj_pool_t *pool,
+ pjmedia_endpt *endpt,
+ const pjmedia_sdp_session *local,
+ const pjmedia_sdp_session *remote,
+ unsigned stream_idx)
+{
+ const pjmedia_sdp_attr *attr;
+ const pjmedia_sdp_media *local_m;
+ const pjmedia_sdp_media *rem_m;
+ const pjmedia_sdp_conn *local_conn;
+ const pjmedia_sdp_conn *rem_conn;
+ int rem_af, local_af;
+ pj_sockaddr local_addr;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(endpt);
+
+ /* Validate arguments: */
+ PJ_ASSERT_RETURN(pool && si && local && remote, PJ_EINVAL);
+ PJ_ASSERT_RETURN(stream_idx < local->media_count, PJ_EINVAL);
+ PJ_ASSERT_RETURN(stream_idx < remote->media_count, PJ_EINVAL);
+
+ /* Keep SDP shortcuts */
+ local_m = local->media[stream_idx];
+ rem_m = remote->media[stream_idx];
+
+ local_conn = local_m->conn ? local_m->conn : local->conn;
+ if (local_conn == NULL)
+ return PJMEDIA_SDP_EMISSINGCONN;
+
+ rem_conn = rem_m->conn ? rem_m->conn : remote->conn;
+ if (rem_conn == NULL)
+ return PJMEDIA_SDP_EMISSINGCONN;
+
+ /* Media type must be video */
+ if (pj_stricmp(&local_m->desc.media, &ID_VIDEO) != 0)
+ return PJMEDIA_EINVALIMEDIATYPE;
+
+
+ /* Reset: */
+
+ pj_bzero(si, sizeof(*si));
+
+ /* Media type: */
+ si->type = PJMEDIA_TYPE_VIDEO;
+
+ /* Transport protocol */
+
+ /* At this point, transport type must be compatible,
+ * the transport instance will do more validation later.
+ */
+ status = pjmedia_sdp_transport_cmp(&rem_m->desc.transport,
+ &local_m->desc.transport);
+ if (status != PJ_SUCCESS)
+ return PJMEDIA_SDPNEG_EINVANSTP;
+
+ if (pj_stricmp(&local_m->desc.transport, &ID_RTP_AVP) == 0) {
+
+ si->proto = PJMEDIA_TP_PROTO_RTP_AVP;
+
+ } else if (pj_stricmp(&local_m->desc.transport, &ID_RTP_SAVP) == 0) {
+
+ si->proto = PJMEDIA_TP_PROTO_RTP_SAVP;
+
+ } else {
+
+ si->proto = PJMEDIA_TP_PROTO_UNKNOWN;
+ return PJ_SUCCESS;
+ }
+
+
+ /* Check address family in remote SDP */
+ rem_af = pj_AF_UNSPEC();
+ if (pj_stricmp(&rem_conn->net_type, &ID_IN)==0) {
+ if (pj_stricmp(&rem_conn->addr_type, &ID_IP4)==0) {
+ rem_af = pj_AF_INET();
+ } else if (pj_stricmp(&rem_conn->addr_type, &ID_IP6)==0) {
+ rem_af = pj_AF_INET6();
+ }
+ }
+
+ if (rem_af==pj_AF_UNSPEC()) {
+ /* Unsupported address family */
+ return PJ_EAFNOTSUP;
+ }
+
+ /* Set remote address: */
+ status = pj_sockaddr_init(rem_af, &si->rem_addr, &rem_conn->addr,
+ rem_m->desc.port);
+ if (status != PJ_SUCCESS) {
+ /* Invalid IP address. */
+ return PJMEDIA_EINVALIDIP;
+ }
+
+ /* Check address family of local info */
+ local_af = pj_AF_UNSPEC();
+ if (pj_stricmp(&local_conn->net_type, &ID_IN)==0) {
+ if (pj_stricmp(&local_conn->addr_type, &ID_IP4)==0) {
+ local_af = pj_AF_INET();
+ } else if (pj_stricmp(&local_conn->addr_type, &ID_IP6)==0) {
+ local_af = pj_AF_INET6();
+ }
+ }
+
+ if (local_af==pj_AF_UNSPEC()) {
+ /* Unsupported address family */
+ return PJ_SUCCESS;
+ }
+
+ /* Set remote address: */
+ status = pj_sockaddr_init(local_af, &local_addr, &local_conn->addr,
+ local_m->desc.port);
+ if (status != PJ_SUCCESS) {
+ /* Invalid IP address. */
+ return PJMEDIA_EINVALIDIP;
+ }
+
+ /* Local and remote address family must match */
+ if (local_af != rem_af)
+ return PJ_EAFNOTSUP;
+
+ /* Media direction: */
+
+ if (local_m->desc.port == 0 ||
+ pj_sockaddr_has_addr(&local_addr)==PJ_FALSE ||
+ pj_sockaddr_has_addr(&si->rem_addr)==PJ_FALSE ||
+ pjmedia_sdp_media_find_attr(local_m, &STR_INACTIVE, NULL)!=NULL)
+ {
+ /* Inactive stream. */
+
+ si->dir = PJMEDIA_DIR_NONE;
+
+ } else if (pjmedia_sdp_media_find_attr(local_m, &STR_SENDONLY, NULL)!=NULL) {
+
+ /* Send only stream. */
+
+ si->dir = PJMEDIA_DIR_ENCODING;
+
+ } else if (pjmedia_sdp_media_find_attr(local_m, &STR_RECVONLY, NULL)!=NULL) {
+
+ /* Recv only stream. */
+
+ si->dir = PJMEDIA_DIR_DECODING;
+
+ } else {
+
+ /* Send and receive stream. */
+
+ si->dir = PJMEDIA_DIR_ENCODING_DECODING;
+
+ }
+
+ /* No need to do anything else if stream is rejected */
+ if (local_m->desc.port == 0) {
+ return PJ_SUCCESS;
+ }
+
+ /* If "rtcp" attribute is present in the SDP, set the RTCP address
+ * from that attribute. Otherwise, calculate from RTP address.
+ */
+ attr = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
+ "rtcp", NULL);
+ if (attr) {
+ pjmedia_sdp_rtcp_attr rtcp;
+ status = pjmedia_sdp_attr_get_rtcp(attr, &rtcp);
+ if (status == PJ_SUCCESS) {
+ if (rtcp.addr.slen) {
+ status = pj_sockaddr_init(rem_af, &si->rem_rtcp, &rtcp.addr,
+ (pj_uint16_t)rtcp.port);
+ } else {
+ pj_sockaddr_init(rem_af, &si->rem_rtcp, NULL,
+ (pj_uint16_t)rtcp.port);
+ pj_memcpy(pj_sockaddr_get_addr(&si->rem_rtcp),
+ pj_sockaddr_get_addr(&si->rem_addr),
+ pj_sockaddr_get_addr_len(&si->rem_addr));
+ }
+ }
+ }
+
+ if (!pj_sockaddr_has_addr(&si->rem_rtcp)) {
+ int rtcp_port;
+
+ pj_memcpy(&si->rem_rtcp, &si->rem_addr, sizeof(pj_sockaddr));
+ rtcp_port = pj_sockaddr_get_port(&si->rem_addr) + 1;
+ pj_sockaddr_set_port(&si->rem_rtcp, (pj_uint16_t)rtcp_port);
+ }
+
+ /* Get codec info and param */
+ status = get_video_codec_info_param(si, pool, NULL, local_m, rem_m);
+
+ /* Leave SSRC to random. */
+ si->ssrc = pj_rand();
+
+ /* Set default jitter buffer parameter. */
+ si->jb_init = si->jb_max = si->jb_min_pre = si->jb_max_pre = -1;
+
+ return status;
+}
+
+
diff --git a/pjmedia/src/pjmedia/vid_tee.c b/pjmedia/src/pjmedia/vid_tee.c
new file mode 100644
index 0000000..dce4c5c
--- /dev/null
+++ b/pjmedia/src/pjmedia/vid_tee.c
@@ -0,0 +1,396 @@
+/* $Id: vid_tee.c 3773 2011-09-23 04:06:01Z nanang $ */
+/*
+ * Copyright (C) 2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia/vid_tee.h>
+#include <pjmedia/converter.h>
+#include <pjmedia/errno.h>
+#include <pj/array.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+
+#define TEE_PORT_NAME "vid_tee"
+#define TEE_PORT_SIGN PJMEDIA_SIG_PORT_VID_TEE
+
+#define THIS_FILE "vid_tee.c"
+
+typedef struct vid_tee_dst_port
+{
+ pjmedia_port *dst;
+ unsigned option;
+} vid_tee_dst_port;
+
+
+typedef struct vid_tee_port
+{
+ pjmedia_port base;
+ pj_pool_t *pool;
+ pj_pool_factory *pf;
+ pj_pool_t *buf_pool;
+ void *buf[2];
+ unsigned buf_cnt;
+ pj_size_t buf_size;
+ unsigned dst_port_maxcnt;
+ unsigned dst_port_cnt;
+ vid_tee_dst_port *dst_ports;
+ pj_uint8_t *put_frm_flag;
+
+ struct vid_tee_conv_t {
+ pjmedia_converter *conv;
+ pj_size_t conv_buf_size;
+ } *tee_conv;
+} vid_tee_port;
+
+
+static pj_status_t tee_put_frame(pjmedia_port *port, pjmedia_frame *frame);
+static pj_status_t tee_get_frame(pjmedia_port *port, pjmedia_frame *frame);
+static pj_status_t tee_destroy(pjmedia_port *port);
+
+/*
+ * Create a video tee port with the specified source media port.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_tee_create( pj_pool_t *pool,
+ const pjmedia_format *fmt,
+ unsigned max_dst_cnt,
+ pjmedia_port **p_vid_tee)
+{
+ vid_tee_port *tee;
+ pj_str_t name_st;
+ const pjmedia_video_format_info *vfi;
+ pjmedia_video_apply_fmt_param vafp;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pool && fmt && p_vid_tee, PJ_EINVAL);
+ PJ_ASSERT_RETURN(fmt->type == PJMEDIA_TYPE_VIDEO, PJ_EINVAL);
+
+ /* Allocate video tee structure */
+ tee = PJ_POOL_ZALLOC_T(pool, vid_tee_port);
+ tee->pf = pool->factory;
+ tee->pool = pj_pool_create(tee->pf, "video tee", 500, 500, NULL);
+
+ /* Initialize video tee structure */
+ tee->dst_port_maxcnt = max_dst_cnt;
+ tee->dst_ports = (vid_tee_dst_port*)
+ pj_pool_calloc(pool, max_dst_cnt,
+ sizeof(vid_tee_dst_port));
+ tee->tee_conv = (struct vid_tee_conv_t *)
+ pj_pool_calloc(pool, max_dst_cnt,
+ sizeof(struct vid_tee_conv_t));
+ tee->put_frm_flag = (pj_uint8_t*)
+ pj_pool_calloc(pool, max_dst_cnt,
+ sizeof(tee->put_frm_flag[0]));
+
+ /* Initialize video tee buffer, its size is one frame */
+ vfi = pjmedia_get_video_format_info(NULL, fmt->id);
+ if (vfi == NULL)
+ return PJMEDIA_EBADFMT;
+
+ pj_bzero(&vafp, sizeof(vafp));
+ vafp.size = fmt->det.vid.size;
+ status = vfi->apply_fmt(vfi, &vafp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ tee->buf_size = vafp.framebytes;
+
+ /* Initialize video tee port */
+ status = pjmedia_port_info_init2(&tee->base.info,
+ pj_strset2(&name_st, (char*)TEE_PORT_NAME),
+ TEE_PORT_SIGN,
+ PJMEDIA_DIR_ENCODING,
+ fmt);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ tee->base.get_frame = &tee_get_frame;
+ tee->base.put_frame = &tee_put_frame;
+ tee->base.on_destroy = &tee_destroy;
+
+ /* Done */
+ *p_vid_tee = &tee->base;
+
+ return PJ_SUCCESS;
+}
+
+static void realloc_buf(vid_tee_port *vid_tee,
+ unsigned buf_cnt, pj_size_t buf_size)
+{
+ unsigned i;
+
+ if (buf_cnt > vid_tee->buf_cnt)
+ vid_tee->buf_cnt = buf_cnt;
+
+ if (buf_size > vid_tee->buf_size) {
+ /* We need a larger buffer here. */
+ vid_tee->buf_size = buf_size;
+ if (vid_tee->buf_pool) {
+ pj_pool_release(vid_tee->buf_pool);
+ vid_tee->buf_pool = NULL;
+ }
+ vid_tee->buf[0] = vid_tee->buf[1] = NULL;
+ }
+
+ if (!vid_tee->buf_pool) {
+ vid_tee->buf_pool = pj_pool_create(vid_tee->pf, "video tee buffer",
+ 1000, 1000, NULL);
+ }
+
+ for (i = 0; i < vid_tee->buf_cnt; i++) {
+ if (!vid_tee->buf[i])
+ vid_tee->buf[i] = pj_pool_alloc(vid_tee->buf_pool,
+ vid_tee->buf_size);
+ }
+}
+
+/*
+ * Add a destination media port to the video tee.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_tee_add_dst_port(pjmedia_port *vid_tee,
+ unsigned option,
+ pjmedia_port *port)
+{
+ vid_tee_port *tee = (vid_tee_port*)vid_tee;
+ pjmedia_video_format_detail *vfd;
+
+ PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN,
+ PJ_EINVAL);
+
+ if (tee->dst_port_cnt >= tee->dst_port_maxcnt)
+ return PJ_ETOOMANY;
+
+ if (vid_tee->info.fmt.id != port->info.fmt.id)
+ return PJMEDIA_EBADFMT;
+
+ vfd = pjmedia_format_get_video_format_detail(&port->info.fmt, PJ_TRUE);
+ if (vfd->size.w != vid_tee->info.fmt.det.vid.size.w ||
+ vfd->size.h != vid_tee->info.fmt.det.vid.size.h)
+ {
+ return PJMEDIA_EBADFMT;
+ }
+
+ realloc_buf(tee, (option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)?
+ 1: 0, tee->buf_size);
+
+ pj_bzero(&tee->tee_conv[tee->dst_port_cnt], sizeof(tee->tee_conv[0]));
+ tee->dst_ports[tee->dst_port_cnt].dst = port;
+ tee->dst_ports[tee->dst_port_cnt].option = option;
+ ++tee->dst_port_cnt;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Add a destination media port to the video tee. Create a converter if
+ * necessary.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_tee_add_dst_port2(pjmedia_port *vid_tee,
+ unsigned option,
+ pjmedia_port *port)
+{
+ vid_tee_port *tee = (vid_tee_port*)vid_tee;
+ pjmedia_video_format_detail *vfd;
+
+ PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN,
+ PJ_EINVAL);
+
+ if (tee->dst_port_cnt >= tee->dst_port_maxcnt)
+ return PJ_ETOOMANY;
+
+ pj_bzero(&tee->tee_conv[tee->dst_port_cnt], sizeof(tee->tee_conv[0]));
+
+ /* Check if we need to create a converter. */
+ vfd = pjmedia_format_get_video_format_detail(&port->info.fmt, PJ_TRUE);
+ if (vid_tee->info.fmt.id != port->info.fmt.id ||
+ vfd->size.w != vid_tee->info.fmt.det.vid.size.w ||
+ vfd->size.h != vid_tee->info.fmt.det.vid.size.h)
+ {
+ const pjmedia_video_format_info *vfi;
+ pjmedia_video_apply_fmt_param vafp;
+ pjmedia_conversion_param conv_param;
+ pj_status_t status;
+
+ vfi = pjmedia_get_video_format_info(NULL, port->info.fmt.id);
+ if (vfi == NULL)
+ return PJMEDIA_EBADFMT;
+
+ pj_bzero(&vafp, sizeof(vafp));
+ vafp.size = port->info.fmt.det.vid.size;
+ status = vfi->apply_fmt(vfi, &vafp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ realloc_buf(tee, (option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)?
+ 2: 1, vafp.framebytes);
+
+ pjmedia_format_copy(&conv_param.src, &vid_tee->info.fmt);
+ pjmedia_format_copy(&conv_param.dst, &port->info.fmt);
+
+ status = pjmedia_converter_create(
+ NULL, tee->pool, &conv_param,
+ &tee->tee_conv[tee->dst_port_cnt].conv);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ tee->tee_conv[tee->dst_port_cnt].conv_buf_size = vafp.framebytes;
+ } else {
+ realloc_buf(tee, (option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)?
+ 1: 0, tee->buf_size);
+ }
+
+ tee->dst_ports[tee->dst_port_cnt].dst = port;
+ tee->dst_ports[tee->dst_port_cnt].option = option;
+ ++tee->dst_port_cnt;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Remove a destination media port from the video tee.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_tee_remove_dst_port(pjmedia_port *vid_tee,
+ pjmedia_port *port)
+{
+ vid_tee_port *tee = (vid_tee_port*)vid_tee;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN,
+ PJ_EINVAL);
+
+ for (i = 0; i < tee->dst_port_cnt; ++i) {
+ if (tee->dst_ports[i].dst == port) {
+ if (tee->tee_conv[i].conv)
+ pjmedia_converter_destroy(tee->tee_conv[i].conv);
+
+ pj_array_erase(tee->dst_ports, sizeof(tee->dst_ports[0]),
+ tee->dst_port_cnt, i);
+ pj_array_erase(tee->tee_conv, sizeof(tee->tee_conv[0]),
+ tee->dst_port_cnt, i);
+ --tee->dst_port_cnt;
+ return PJ_SUCCESS;
+ }
+ }
+
+ return PJ_ENOTFOUND;
+}
+
+
+static pj_status_t tee_put_frame(pjmedia_port *port, pjmedia_frame *frame)
+{
+ vid_tee_port *tee = (vid_tee_port*)port;
+ unsigned i, j;
+ const pj_uint8_t PUT_FRM_DONE = 1;
+
+ pj_bzero(tee->put_frm_flag, tee->dst_port_cnt *
+ sizeof(tee->put_frm_flag[0]));
+
+ for (i = 0; i < tee->dst_port_cnt; ++i) {
+ pjmedia_frame frame_ = *frame;
+
+ if (tee->put_frm_flag[i])
+ continue;
+
+ if (tee->tee_conv[i].conv) {
+ pj_status_t status;
+
+ frame_.buf = tee->buf[0];
+ frame_.size = tee->tee_conv[i].conv_buf_size;
+ status = pjmedia_converter_convert(tee->tee_conv[i].conv,
+ frame, &frame_);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE,
+ "Failed to convert frame for destination"
+ " port %d (%.*s)", i,
+ tee->dst_ports[i].dst->info.name.slen,
+ tee->dst_ports[i].dst->info.name.ptr));
+ continue;
+ }
+ }
+
+ /* Find other destination ports which has the same format so
+ * we don't need to do the same conversion twice.
+ */
+ for (j = i; j < tee->dst_port_cnt; ++j) {
+ pjmedia_frame framep;
+
+ if (tee->put_frm_flag[j] ||
+ (tee->dst_ports[j].dst->info.fmt.id !=
+ tee->dst_ports[i].dst->info.fmt.id) ||
+ (tee->dst_ports[j].dst->info.fmt.det.vid.size.w !=
+ tee->dst_ports[i].dst->info.fmt.det.vid.size.w) ||
+ (tee->dst_ports[j].dst->info.fmt.det.vid.size.h !=
+ tee->dst_ports[i].dst->info.fmt.det.vid.size.h))
+ {
+ continue;
+ }
+
+ framep = frame_;
+ /* For dst_ports that do in-place processing, we need to duplicate
+ * the data source first.
+ */
+ if (tee->dst_ports[j].option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)
+ {
+ PJ_ASSERT_RETURN(tee->buf_size <= frame_.size, PJ_ETOOBIG);
+ framep.buf = tee->buf[tee->buf_cnt-1];
+ framep.size = frame_.size;
+ pj_memcpy(framep.buf, frame_.buf, frame_.size);
+ }
+
+ /* Deliver the data */
+ pjmedia_port_put_frame(tee->dst_ports[j].dst, &framep);
+ tee->put_frm_flag[j] = PUT_FRM_DONE;
+
+ if (!tee->tee_conv[i].conv)
+ break;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t tee_get_frame(pjmedia_port *port, pjmedia_frame *frame)
+{
+ PJ_UNUSED_ARG(port);
+ PJ_UNUSED_ARG(frame);
+
+ pj_assert(!"Bug! Tee port get_frame() shouldn't be called.");
+
+ return PJ_EBUG;
+}
+
+static pj_status_t tee_destroy(pjmedia_port *port)
+{
+ vid_tee_port *tee = (vid_tee_port*)port;
+
+ PJ_ASSERT_RETURN(port && port->info.signature==TEE_PORT_SIGN, PJ_EINVAL);
+
+ pj_pool_release(tee->pool);
+ if (tee->buf_pool)
+ pj_pool_release(tee->buf_pool);
+
+ pj_bzero(tee, sizeof(*tee));
+
+ return PJ_SUCCESS;
+}
+
+
+#endif /* PJMEDIA_HAS_VIDEO */
diff --git a/pjmedia/src/pjmedia/wav_player.c b/pjmedia/src/pjmedia/wav_player.c
new file mode 100644
index 0000000..e906a26
--- /dev/null
+++ b/pjmedia/src/pjmedia/wav_player.c
@@ -0,0 +1,691 @@
+/* $Id: wav_player.c 4122 2012-05-14 11:04:46Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/wav_port.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/wave.h>
+#include <pj/assert.h>
+#include <pj/file_access.h>
+#include <pj/file_io.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#define THIS_FILE "wav_player.c"
+
+
+#define SIGNATURE PJMEDIA_SIG_PORT_WAV_PLAYER
+#define BITS_PER_SAMPLE 16
+
+#if 1
+# define TRACE_(x) PJ_LOG(4,x)
+#else
+# define TRACE_(x)
+#endif
+
+#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0
+ static void samples_to_host(pj_int16_t *samples, unsigned count)
+ {
+ unsigned i;
+ for (i=0; i<count; ++i) {
+ samples[i] = pj_swap16(samples[i]);
+ }
+ }
+#else
+# define samples_to_host(samples,count)
+#endif
+
+struct file_reader_port
+{
+ pjmedia_port base;
+ unsigned options;
+ pjmedia_wave_fmt_tag fmt_tag;
+ pj_uint16_t bytes_per_sample;
+ pj_bool_t eof;
+ pj_size_t bufsize;
+ char *buf;
+ char *readpos;
+ char *eofpos;
+
+ pj_off_t fsize;
+ unsigned start_data;
+ unsigned data_len;
+ unsigned data_left;
+ pj_off_t fpos;
+ pj_oshandle_t fd;
+
+ pj_status_t (*cb)(pjmedia_port*, void*);
+};
+
+
+static pj_status_t file_get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t file_on_destroy(pjmedia_port *this_port);
+
+static struct file_reader_port *create_file_port(pj_pool_t *pool)
+{
+ const pj_str_t name = pj_str("file");
+ struct file_reader_port *port;
+
+ port = PJ_POOL_ZALLOC_T(pool, struct file_reader_port);
+ if (!port)
+ return NULL;
+
+ /* Put in default values.
+ * These will be overriden once the file is read.
+ */
+ pjmedia_port_info_init(&port->base.info, &name, SIGNATURE,
+ 8000, 1, 16, 80);
+
+ port->base.get_frame = &file_get_frame;
+ port->base.on_destroy = &file_on_destroy;
+
+
+ return port;
+}
+
+/*
+ * Fill buffer.
+ */
+static pj_status_t fill_buffer(struct file_reader_port *fport)
+{
+ pj_ssize_t size_left = fport->bufsize;
+ unsigned size_to_read;
+ pj_ssize_t size;
+ pj_status_t status;
+
+ fport->eofpos = NULL;
+
+ while (size_left > 0) {
+
+ /* Calculate how many bytes to read in this run. */
+ size = size_to_read = size_left;
+ status = pj_file_read(fport->fd,
+ &fport->buf[fport->bufsize-size_left],
+ &size);
+ if (status != PJ_SUCCESS)
+ return status;
+ if (size < 0) {
+ /* Should return more appropriate error code here.. */
+ return PJ_ECANCELLED;
+ }
+
+ if (size > (pj_ssize_t)fport->data_left) {
+ /* We passed the end of the data chunk,
+ * only count the portion read from the data chunk.
+ */
+ size = (pj_ssize_t)fport->data_left;
+ }
+
+ size_left -= size;
+ fport->data_left -= size;
+ fport->fpos += size;
+
+ /* If size is less than size_to_read, it indicates that we've
+ * encountered EOF. Rewind the file.
+ */
+ if (size < (pj_ssize_t)size_to_read) {
+ fport->eof = PJ_TRUE;
+ fport->eofpos = fport->buf + fport->bufsize - size_left;
+
+ if (fport->options & PJMEDIA_FILE_NO_LOOP) {
+ /* Zero remaining buffer */
+ if (fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_PCM) {
+ pj_bzero(fport->eofpos, size_left);
+ } else if (fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_ULAW) {
+ int val = pjmedia_linear2ulaw(0);
+ pj_memset(fport->eofpos, val, size_left);
+ } else if (fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_ALAW) {
+ int val = pjmedia_linear2alaw(0);
+ pj_memset(fport->eofpos, val, size_left);
+ }
+ size_left = 0;
+ }
+
+ /* Rewind file */
+ fport->fpos = fport->start_data;
+ pj_file_setpos( fport->fd, fport->fpos, PJ_SEEK_SET);
+ fport->data_left = fport->data_len;
+ }
+ }
+
+ /* Convert samples to host rep */
+ samples_to_host((pj_int16_t*)fport->buf,
+ fport->bufsize/fport->bytes_per_sample);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create WAVE player port.
+ */
+PJ_DEF(pj_status_t) pjmedia_wav_player_port_create( pj_pool_t *pool,
+ const char *filename,
+ unsigned ptime,
+ unsigned options,
+ pj_ssize_t buff_size,
+ pjmedia_port **p_port )
+{
+ pjmedia_wave_hdr wave_hdr;
+ pj_ssize_t size_to_read, size_read;
+ struct file_reader_port *fport;
+ pjmedia_audio_format_detail *ad;
+ pj_off_t pos;
+ pj_str_t name;
+ unsigned samples_per_frame;
+ pj_status_t status = PJ_SUCCESS;
+
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(pool && filename && p_port, PJ_EINVAL);
+
+ /* Check the file really exists. */
+ if (!pj_file_exists(filename)) {
+ return PJ_ENOTFOUND;
+ }
+
+ /* Normalize ptime */
+ if (ptime == 0)
+ ptime = 20;
+
+ /* Normalize buff_size */
+ if (buff_size < 1) buff_size = PJMEDIA_FILE_PORT_BUFSIZE;
+
+
+ /* Create fport instance. */
+ fport = create_file_port(pool);
+ if (!fport) {
+ return PJ_ENOMEM;
+ }
+
+
+ /* Get the file size. */
+ fport->fsize = pj_file_size(filename);
+
+ /* Size must be more than WAVE header size */
+ if (fport->fsize <= sizeof(pjmedia_wave_hdr)) {
+ return PJMEDIA_ENOTVALIDWAVE;
+ }
+
+ /* Open file. */
+ status = pj_file_open( pool, filename, PJ_O_RDONLY, &fport->fd);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Read the file header plus fmt header only. */
+ size_read = size_to_read = sizeof(wave_hdr) - 8;
+ status = pj_file_read( fport->fd, &wave_hdr, &size_read);
+ if (status != PJ_SUCCESS) {
+ pj_file_close(fport->fd);
+ return status;
+ }
+ if (size_read != size_to_read) {
+ pj_file_close(fport->fd);
+ return PJMEDIA_ENOTVALIDWAVE;
+ }
+
+ /* Normalize WAVE header fields values from little-endian to host
+ * byte order.
+ */
+ pjmedia_wave_hdr_file_to_host(&wave_hdr);
+
+ /* Validate WAVE file. */
+ if (wave_hdr.riff_hdr.riff != PJMEDIA_RIFF_TAG ||
+ wave_hdr.riff_hdr.wave != PJMEDIA_WAVE_TAG ||
+ wave_hdr.fmt_hdr.fmt != PJMEDIA_FMT_TAG)
+ {
+ pj_file_close(fport->fd);
+ TRACE_((THIS_FILE,
+ "actual value|expected riff=%x|%x, wave=%x|%x fmt=%x|%x",
+ wave_hdr.riff_hdr.riff, PJMEDIA_RIFF_TAG,
+ wave_hdr.riff_hdr.wave, PJMEDIA_WAVE_TAG,
+ wave_hdr.fmt_hdr.fmt, PJMEDIA_FMT_TAG));
+ return PJMEDIA_ENOTVALIDWAVE;
+ }
+
+ /* Validate format and its attributes (i.e: bits per sample, block align) */
+ switch (wave_hdr.fmt_hdr.fmt_tag) {
+ case PJMEDIA_WAVE_FMT_TAG_PCM:
+ if (wave_hdr.fmt_hdr.bits_per_sample != 16 ||
+ wave_hdr.fmt_hdr.block_align != 2 * wave_hdr.fmt_hdr.nchan)
+ status = PJMEDIA_EWAVEUNSUPP;
+ break;
+
+ case PJMEDIA_WAVE_FMT_TAG_ALAW:
+ case PJMEDIA_WAVE_FMT_TAG_ULAW:
+ if (wave_hdr.fmt_hdr.bits_per_sample != 8 ||
+ wave_hdr.fmt_hdr.block_align != wave_hdr.fmt_hdr.nchan)
+ status = PJMEDIA_ENOTVALIDWAVE;
+ break;
+
+ default:
+ status = PJMEDIA_EWAVEUNSUPP;
+ break;
+ }
+
+ if (status != PJ_SUCCESS) {
+ pj_file_close(fport->fd);
+ return status;
+ }
+
+ fport->fmt_tag = (pjmedia_wave_fmt_tag)wave_hdr.fmt_hdr.fmt_tag;
+ fport->bytes_per_sample = (pj_uint16_t)
+ (wave_hdr.fmt_hdr.bits_per_sample / 8);
+
+ /* If length of fmt_header is greater than 16, skip the remaining
+ * fmt header data.
+ */
+ if (wave_hdr.fmt_hdr.len > 16) {
+ size_to_read = wave_hdr.fmt_hdr.len - 16;
+ status = pj_file_setpos(fport->fd, size_to_read, PJ_SEEK_CUR);
+ if (status != PJ_SUCCESS) {
+ pj_file_close(fport->fd);
+ return status;
+ }
+ }
+
+ /* Repeat reading the WAVE file until we have 'data' chunk */
+ for (;;) {
+ pjmedia_wave_subchunk subchunk;
+ size_read = 8;
+ status = pj_file_read(fport->fd, &subchunk, &size_read);
+ if (status != PJ_SUCCESS || size_read != 8) {
+ pj_file_close(fport->fd);
+ return PJMEDIA_EWAVETOOSHORT;
+ }
+
+ /* Normalize endianness */
+ PJMEDIA_WAVE_NORMALIZE_SUBCHUNK(&subchunk);
+
+ /* Break if this is "data" chunk */
+ if (subchunk.id == PJMEDIA_DATA_TAG) {
+ wave_hdr.data_hdr.data = PJMEDIA_DATA_TAG;
+ wave_hdr.data_hdr.len = subchunk.len;
+ break;
+ }
+
+ /* Otherwise skip the chunk contents */
+ size_to_read = subchunk.len;
+ status = pj_file_setpos(fport->fd, size_to_read, PJ_SEEK_CUR);
+ if (status != PJ_SUCCESS) {
+ pj_file_close(fport->fd);
+ return status;
+ }
+ }
+
+ /* Current file position now points to start of data */
+ status = pj_file_getpos(fport->fd, &pos);
+ fport->start_data = (unsigned)pos;
+ fport->data_len = wave_hdr.data_hdr.len;
+ fport->data_left = wave_hdr.data_hdr.len;
+
+ /* Validate length. */
+ if (wave_hdr.data_hdr.len > fport->fsize - fport->start_data) {
+ pj_file_close(fport->fd);
+ return PJMEDIA_EWAVEUNSUPP;
+ }
+ if (wave_hdr.data_hdr.len < ptime * wave_hdr.fmt_hdr.sample_rate *
+ wave_hdr.fmt_hdr.nchan / 1000)
+ {
+ pj_file_close(fport->fd);
+ return PJMEDIA_EWAVETOOSHORT;
+ }
+
+ /* It seems like we have a valid WAVE file. */
+
+ /* Initialize */
+ fport->options = options;
+
+ /* Update port info. */
+ ad = pjmedia_format_get_audio_format_detail(&fport->base.info.fmt, 1);
+ pj_strdup2(pool, &name, filename);
+ samples_per_frame = ptime * wave_hdr.fmt_hdr.sample_rate *
+ wave_hdr.fmt_hdr.nchan / 1000;
+ pjmedia_port_info_init(&fport->base.info, &name, SIGNATURE,
+ wave_hdr.fmt_hdr.sample_rate,
+ wave_hdr.fmt_hdr.nchan,
+ BITS_PER_SAMPLE,
+ samples_per_frame);
+
+ /* If file is shorter than buffer size, adjust buffer size to file
+ * size. Otherwise EOF callback will be called multiple times when
+ * fill_buffer() is called.
+ */
+ if (wave_hdr.data_hdr.len < (unsigned)buff_size)
+ buff_size = wave_hdr.data_hdr.len;
+
+ /* Create file buffer.
+ */
+ fport->bufsize = buff_size;
+
+
+ /* samples_per_frame must be smaller than bufsize (because get_frame()
+ * doesn't handle this case).
+ */
+ if (samples_per_frame * fport->bytes_per_sample >= fport->bufsize) {
+ pj_file_close(fport->fd);
+ return PJ_EINVAL;
+ }
+
+ /* Create buffer. */
+ fport->buf = (char*) pj_pool_alloc(pool, fport->bufsize);
+ if (!fport->buf) {
+ pj_file_close(fport->fd);
+ return PJ_ENOMEM;
+ }
+
+ fport->readpos = fport->buf;
+
+ /* Set initial position of the file. */
+ fport->fpos = fport->start_data;
+
+ /* Fill up the buffer. */
+ status = fill_buffer(fport);
+ if (status != PJ_SUCCESS) {
+ pj_file_close(fport->fd);
+ return status;
+ }
+
+ /* Done. */
+
+ *p_port = &fport->base;
+
+
+ PJ_LOG(4,(THIS_FILE,
+ "File player '%.*s' created: samp.rate=%d, ch=%d, bufsize=%uKB, "
+ "filesize=%luKB",
+ (int)fport->base.info.name.slen,
+ fport->base.info.name.ptr,
+ ad->clock_rate,
+ ad->channel_count,
+ fport->bufsize / 1000,
+ (unsigned long)(fport->fsize / 1000)));
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the data length, in bytes.
+ */
+PJ_DEF(pj_ssize_t) pjmedia_wav_player_get_len(pjmedia_port *port)
+{
+ struct file_reader_port *fport;
+ pj_ssize_t size;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(port, -PJ_EINVAL);
+
+ /* Check that this is really a player port */
+ PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, -PJ_EINVALIDOP);
+
+ fport = (struct file_reader_port*) port;
+
+ size = (pj_ssize_t) fport->fsize;
+ return size - fport->start_data;
+}
+
+
+/*
+ * Set position.
+ */
+PJ_DEF(pj_status_t) pjmedia_wav_player_port_set_pos(pjmedia_port *port,
+ pj_uint32_t bytes )
+{
+ struct file_reader_port *fport;
+ pj_status_t status;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(port, PJ_EINVAL);
+
+ /* Check that this is really a player port */
+ PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVALIDOP);
+
+
+ fport = (struct file_reader_port*) port;
+
+ /* Check that this offset does not pass the audio-data (in case of
+ * extra chunk after audio data chunk
+ */
+ PJ_ASSERT_RETURN(bytes < fport->data_len, PJ_EINVAL);
+
+ fport->fpos = fport->start_data + bytes;
+ fport->data_left = fport->data_len - bytes;
+ pj_file_setpos( fport->fd, fport->fpos, PJ_SEEK_SET);
+
+ fport->eof = PJ_FALSE;
+ status = fill_buffer(fport);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ fport->readpos = fport->buf;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the file play position of WAV player.
+ */
+PJ_DEF(pj_ssize_t) pjmedia_wav_player_port_get_pos( pjmedia_port *port )
+{
+ struct file_reader_port *fport;
+ pj_size_t payload_pos;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(port, -PJ_EINVAL);
+
+ /* Check that this is really a player port */
+ PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, -PJ_EINVALIDOP);
+
+ fport = (struct file_reader_port*) port;
+
+ payload_pos = (pj_size_t)(fport->fpos - fport->start_data);
+ if (payload_pos >= fport->bufsize)
+ return payload_pos - fport->bufsize + (fport->readpos - fport->buf);
+ else
+ return (fport->readpos - fport->buf) % payload_pos;
+}
+
+
+
+/*
+ * Register a callback to be called when the file reading has reached the
+ * end of file.
+ */
+PJ_DEF(pj_status_t) pjmedia_wav_player_set_eof_cb( pjmedia_port *port,
+ void *user_data,
+ pj_status_t (*cb)(pjmedia_port *port,
+ void *usr_data))
+{
+ struct file_reader_port *fport;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(port, -PJ_EINVAL);
+
+ /* Check that this is really a player port */
+ PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, -PJ_EINVALIDOP);
+
+ fport = (struct file_reader_port*) port;
+
+ fport->base.port_data.pdata = user_data;
+ fport->cb = cb;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get frame from file.
+ */
+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;
+ unsigned frame_size;
+ pj_status_t status;
+
+ 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_status_t status = PJ_SUCCESS;
+
+ 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->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;
+ }
+
+ 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 = (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;
+}
+
+/*
+ * Destroy port.
+ */
+static pj_status_t file_on_destroy(pjmedia_port *this_port)
+{
+ struct file_reader_port *fport = (struct file_reader_port*) this_port;
+
+ pj_assert(this_port->info.signature == SIGNATURE);
+
+ pj_file_close(fport->fd);
+ return PJ_SUCCESS;
+}
+
diff --git a/pjmedia/src/pjmedia/wav_playlist.c b/pjmedia/src/pjmedia/wav_playlist.c
new file mode 100644
index 0000000..658b35b
--- /dev/null
+++ b/pjmedia/src/pjmedia/wav_playlist.c
@@ -0,0 +1,642 @@
+/* $Id: wav_playlist.c 3917 2011-12-20 10:01:35Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * Original author:
+ * David Clark <vdc1048 @ tx.rr.com>
+ *
+ * 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 <pjmedia/wav_playlist.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/wave.h>
+#include <pj/assert.h>
+#include <pj/file_access.h>
+#include <pj/file_io.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#define THIS_FILE "wav_playlist.c"
+
+#define SIGNATURE PJMEDIA_SIG_PORT_WAV_PLAYLIST
+#define BYTES_PER_SAMPLE 2
+
+
+#if 1
+# define TRACE_(x) PJ_LOG(4,x)
+#else
+# define TRACE_(x)
+#endif
+
+#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0
+ static void samples_to_host(pj_int16_t *samples, unsigned count)
+ {
+ unsigned i;
+ for (i=0; i<count; ++i) {
+ samples[i] = pj_swap16(samples[i]);
+ }
+ }
+#else
+# define samples_to_host(samples,count)
+#endif
+
+
+struct playlist_port
+{
+ pjmedia_port base;
+ unsigned options;
+ pj_bool_t eof;
+ pj_size_t bufsize;
+ char *buf;
+ char *readpos;
+
+ pj_off_t *fsize_list;
+ unsigned *start_data_list;
+ pj_off_t *fpos_list;
+ pj_oshandle_t *fd_list; /* list of file descriptors */
+ int current_file; /* index of current file. */
+ int max_file; /* how many files. */
+
+ pj_status_t (*cb)(pjmedia_port*, void*);
+};
+
+
+static pj_status_t file_list_get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t file_list_on_destroy(pjmedia_port *this_port);
+
+
+static struct playlist_port *create_file_list_port(pj_pool_t *pool,
+ const pj_str_t *name)
+{
+ struct playlist_port *port;
+
+ port = PJ_POOL_ZALLOC_T(pool, struct playlist_port);
+ if (!port)
+ return NULL;
+
+ /* Put in default values.
+ * These will be overriden once the file is read.
+ */
+ pjmedia_port_info_init(&port->base.info, name, SIGNATURE,
+ 8000, 1, 16, 80);
+
+ port->base.get_frame = &file_list_get_frame;
+ port->base.on_destroy = &file_list_on_destroy;
+
+ return port;
+}
+
+
+/*
+ * Fill buffer for file_list operations.
+ */
+static pj_status_t file_fill_buffer(struct playlist_port *fport)
+{
+ pj_ssize_t size_left = fport->bufsize;
+ unsigned size_to_read;
+ pj_ssize_t size;
+ pj_status_t status;
+ int current_file = fport->current_file;
+
+ /* Can't read file if EOF and loop flag is disabled */
+ if (fport->eof)
+ return PJ_EEOF;
+
+ while (size_left > 0)
+ {
+ /* Calculate how many bytes to read in this run. */
+ size = size_to_read = size_left;
+ status = pj_file_read(fport->fd_list[current_file],
+ &fport->buf[fport->bufsize-size_left],
+ &size);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (size < 0)
+ {
+ /* Should return more appropriate error code here.. */
+ return PJ_ECANCELLED;
+ }
+
+ size_left -= size;
+ fport->fpos_list[current_file] += size;
+
+ /* If size is less than size_to_read, it indicates that we've
+ * encountered EOF. Rewind the file.
+ */
+ if (size < (pj_ssize_t)size_to_read)
+ {
+ /* Rewind the file for the next iteration */
+ fport->fpos_list[current_file] =
+ fport->start_data_list[current_file];
+ pj_file_setpos(fport->fd_list[current_file],
+ fport->fpos_list[current_file], PJ_SEEK_SET);
+
+ /* Move to next file */
+ current_file++;
+ fport->current_file = current_file;
+
+ if (fport->current_file == fport->max_file)
+ {
+ /* Clear the remaining part of the buffer first, to prevent
+ * old samples from being played. If the playback restarts,
+ * this will be overwritten by new reading.
+ */
+ if (size_left > 0) {
+ pj_bzero(&fport->buf[fport->bufsize-size_left],
+ size_left);
+ }
+
+ /* All files have been played. Call callback, if any. */
+ if (fport->cb)
+ {
+ PJ_LOG(5,(THIS_FILE,
+ "File port %.*s EOF, calling callback",
+ (int)fport->base.info.name.slen,
+ fport->base.info.name.ptr));
+
+ fport->eof = PJ_TRUE;
+
+ status = (*fport->cb)(&fport->base,
+ fport->base.port_data.pdata);
+
+ if (status != PJ_SUCCESS)
+ {
+ /* This will crash if file port is destroyed in the
+ * callback, that's why we set the eof flag before
+ * calling the callback:
+ fport->eof = PJ_TRUE;
+ */
+ return status;
+ }
+
+ fport->eof = PJ_FALSE;
+ }
+
+
+ if (fport->options & PJMEDIA_FILE_NO_LOOP)
+ {
+ PJ_LOG(5,(THIS_FILE, "File port %.*s EOF, stopping..",
+ (int)fport->base.info.name.slen,
+ fport->base.info.name.ptr));
+ fport->eof = PJ_TRUE;
+ return PJ_EEOF;
+ }
+ else
+ {
+ PJ_LOG(5,(THIS_FILE, "File port %.*s EOF, rewinding..",
+ (int)fport->base.info.name.slen,
+ fport->base.info.name.ptr));
+
+ /* start with first file again. */
+ fport->current_file = current_file = 0;
+ fport->fpos_list[0] = fport->start_data_list[0];
+ pj_file_setpos(fport->fd_list[0], fport->fpos_list[0],
+ PJ_SEEK_SET);
+ }
+
+ } /* if current_file == max_file */
+
+ } /* size < size_to_read */
+
+ } /* while () */
+
+ /* Convert samples to host rep */
+ samples_to_host((pj_int16_t*)fport->buf, fport->bufsize/BYTES_PER_SAMPLE);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create wave list player.
+ */
+PJ_DEF(pj_status_t) pjmedia_wav_playlist_create(pj_pool_t *pool,
+ const pj_str_t *port_label,
+ const pj_str_t file_list[],
+ int file_count,
+ unsigned ptime,
+ unsigned options,
+ pj_ssize_t buff_size,
+ pjmedia_port **p_port)
+{
+ struct playlist_port *fport;
+ pjmedia_audio_format_detail *afd;
+ pj_off_t pos;
+ pj_status_t status;
+ int index;
+ pj_bool_t has_wave_info = PJ_FALSE;
+ pj_str_t tmp_port_label;
+ char filename[PJ_MAXPATH]; /* filename for open operations. */
+
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(pool && file_list && file_count && p_port, PJ_EINVAL);
+
+ /* Normalize port_label */
+ if (port_label == NULL || port_label->slen == 0) {
+ tmp_port_label = pj_str("WAV playlist");
+ port_label = &tmp_port_label;
+ }
+
+ /* Be sure all files exist */
+ for (index=0; index<file_count; index++) {
+
+ PJ_ASSERT_RETURN(file_list[index].slen < PJ_MAXPATH, PJ_ENAMETOOLONG);
+
+ pj_memcpy(filename, file_list[index].ptr, file_list[index].slen);
+ filename[file_list[index].slen] = '\0';
+
+ /* Check the file really exists. */
+ if (!pj_file_exists(filename)) {
+ PJ_LOG(4,(THIS_FILE,
+ "WAV playlist error: file '%s' not found",
+ filename));
+ return PJ_ENOTFOUND;
+ }
+ }
+
+ /* Normalize ptime */
+ if (ptime == 0)
+ ptime = 20;
+
+ /* Create fport instance. */
+ fport = create_file_list_port(pool, port_label);
+ if (!fport) {
+ return PJ_ENOMEM;
+ }
+
+ afd = pjmedia_format_get_audio_format_detail(&fport->base.info.fmt, 1);
+
+ /* start with the first file. */
+ fport->current_file = 0;
+ fport->max_file = file_count;
+
+ /* Create file descriptor list */
+ fport->fd_list = (pj_oshandle_t*)
+ pj_pool_zalloc(pool, sizeof(pj_oshandle_t)*file_count);
+ if (!fport->fd_list) {
+ return PJ_ENOMEM;
+ }
+
+ /* Create file size list */
+ fport->fsize_list = (pj_off_t*)
+ pj_pool_alloc(pool, sizeof(pj_off_t)*file_count);
+ if (!fport->fsize_list) {
+ return PJ_ENOMEM;
+ }
+
+ /* Create start of WAVE data list */
+ fport->start_data_list = (unsigned*)
+ pj_pool_alloc(pool, sizeof(unsigned)*file_count);
+ if (!fport->start_data_list) {
+ return PJ_ENOMEM;
+ }
+
+ /* Create file position list */
+ fport->fpos_list = (pj_off_t*)
+ pj_pool_alloc(pool, sizeof(pj_off_t)*file_count);
+ if (!fport->fpos_list) {
+ return PJ_ENOMEM;
+ }
+
+ /* Create file buffer once for this operation.
+ */
+ if (buff_size < 1) buff_size = PJMEDIA_FILE_PORT_BUFSIZE;
+ fport->bufsize = buff_size;
+
+
+ /* Create buffer. */
+ fport->buf = (char*) pj_pool_alloc(pool, fport->bufsize);
+ if (!fport->buf) {
+ return PJ_ENOMEM;
+ }
+
+ /* Initialize port */
+ fport->options = options;
+ fport->readpos = fport->buf;
+
+
+ /* ok run this for all files to be sure all are good for playback. */
+ for (index=file_count-1; index>=0; index--) {
+
+ pjmedia_wave_hdr wavehdr;
+ pj_ssize_t size_to_read, size_read;
+
+ /* we end with the last one so we are good to go if still in function*/
+ pj_memcpy(filename, file_list[index].ptr, file_list[index].slen);
+ filename[file_list[index].slen] = '\0';
+
+ /* Get the file size. */
+ fport->current_file = index;
+ fport->fsize_list[index] = pj_file_size(filename);
+
+ /* Size must be more than WAVE header size */
+ if (fport->fsize_list[index] <= sizeof(pjmedia_wave_hdr)) {
+ status = PJMEDIA_ENOTVALIDWAVE;
+ goto on_error;
+ }
+
+ /* Open file. */
+ status = pj_file_open( pool, filename, PJ_O_RDONLY,
+ &fport->fd_list[index]);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Read the file header plus fmt header only. */
+ size_read = size_to_read = sizeof(wavehdr) - 8;
+ status = pj_file_read( fport->fd_list[index], &wavehdr, &size_read);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ if (size_read != size_to_read) {
+ status = PJMEDIA_ENOTVALIDWAVE;
+ goto on_error;
+ }
+
+ /* Normalize WAVE header fields values from little-endian to host
+ * byte order.
+ */
+ pjmedia_wave_hdr_file_to_host(&wavehdr);
+
+ /* Validate WAVE file. */
+ if (wavehdr.riff_hdr.riff != PJMEDIA_RIFF_TAG ||
+ wavehdr.riff_hdr.wave != PJMEDIA_WAVE_TAG ||
+ wavehdr.fmt_hdr.fmt != PJMEDIA_FMT_TAG)
+ {
+ TRACE_((THIS_FILE,
+ "actual value|expected riff=%x|%x, wave=%x|%x fmt=%x|%x",
+ wavehdr.riff_hdr.riff, PJMEDIA_RIFF_TAG,
+ wavehdr.riff_hdr.wave, PJMEDIA_WAVE_TAG,
+ wavehdr.fmt_hdr.fmt, PJMEDIA_FMT_TAG));
+ status = PJMEDIA_ENOTVALIDWAVE;
+ goto on_error;
+ }
+
+ /* Must be PCM with 16bits per sample */
+ if (wavehdr.fmt_hdr.fmt_tag != 1 ||
+ wavehdr.fmt_hdr.bits_per_sample != 16)
+ {
+ status = PJMEDIA_EWAVEUNSUPP;
+ goto on_error;
+ }
+
+ /* Block align must be 2*nchannels */
+ if (wavehdr.fmt_hdr.block_align !=
+ wavehdr.fmt_hdr.nchan * BYTES_PER_SAMPLE)
+ {
+ status = PJMEDIA_EWAVEUNSUPP;
+ goto on_error;
+ }
+
+ /* If length of fmt_header is greater than 16, skip the remaining
+ * fmt header data.
+ */
+ if (wavehdr.fmt_hdr.len > 16) {
+ size_to_read = wavehdr.fmt_hdr.len - 16;
+ status = pj_file_setpos(fport->fd_list[index], size_to_read,
+ PJ_SEEK_CUR);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+ }
+
+ /* Repeat reading the WAVE file until we have 'data' chunk */
+ for (;;) {
+ pjmedia_wave_subchunk subchunk;
+ size_read = 8;
+ status = pj_file_read(fport->fd_list[index], &subchunk,
+ &size_read);
+ if (status != PJ_SUCCESS || size_read != 8) {
+ status = PJMEDIA_EWAVETOOSHORT;
+ goto on_error;
+ }
+
+ /* Normalize endianness */
+ PJMEDIA_WAVE_NORMALIZE_SUBCHUNK(&subchunk);
+
+ /* Break if this is "data" chunk */
+ if (subchunk.id == PJMEDIA_DATA_TAG) {
+ wavehdr.data_hdr.data = PJMEDIA_DATA_TAG;
+ wavehdr.data_hdr.len = subchunk.len;
+ break;
+ }
+
+ /* Otherwise skip the chunk contents */
+ size_to_read = subchunk.len;
+ status = pj_file_setpos(fport->fd_list[index], size_to_read,
+ PJ_SEEK_CUR);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+ }
+
+ /* Current file position now points to start of data */
+ status = pj_file_getpos(fport->fd_list[index], &pos);
+ fport->start_data_list[index] = (unsigned)pos;
+
+ /* Validate length. */
+ if (wavehdr.data_hdr.len != fport->fsize_list[index] -
+ fport->start_data_list[index])
+ {
+ status = PJMEDIA_EWAVEUNSUPP;
+ goto on_error;
+ }
+ if (wavehdr.data_hdr.len < 400) {
+ status = PJMEDIA_EWAVETOOSHORT;
+ goto on_error;
+ }
+
+ /* It seems like we have a valid WAVE file. */
+
+ /* Update port info if we don't have one, otherwise check
+ * that the WAV file has the same attributes as previous files.
+ */
+ if (!has_wave_info) {
+ afd->channel_count = wavehdr.fmt_hdr.nchan;
+ afd->clock_rate = wavehdr.fmt_hdr.sample_rate;
+ afd->bits_per_sample = wavehdr.fmt_hdr.bits_per_sample;
+ afd->frame_time_usec = ptime * 1000;
+ afd->avg_bps = afd->max_bps = afd->clock_rate *
+ afd->channel_count *
+ afd->bits_per_sample;
+
+ has_wave_info = PJ_TRUE;
+
+ } else {
+
+ /* Check that this file has the same characteristics as the other
+ * files.
+ */
+ if (wavehdr.fmt_hdr.nchan != afd->channel_count ||
+ wavehdr.fmt_hdr.sample_rate != afd->clock_rate ||
+ wavehdr.fmt_hdr.bits_per_sample != afd->bits_per_sample)
+ {
+ /* This file has different characteristics than the other
+ * files.
+ */
+ PJ_LOG(4,(THIS_FILE,
+ "WAV playlist error: file '%s' has differrent number"
+ " of channels, sample rate, or bits per sample",
+ filename));
+ status = PJMEDIA_EWAVEUNSUPP;
+ goto on_error;
+ }
+
+ }
+
+
+ /* Set initial position of the file. */
+ fport->fpos_list[index] = fport->start_data_list[index];
+ }
+
+ /* Fill up the buffer. */
+ status = file_fill_buffer(fport);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ /* Done. */
+
+ *p_port = &fport->base;
+
+ PJ_LOG(4,(THIS_FILE,
+ "WAV playlist '%.*s' created: samp.rate=%d, ch=%d, bufsize=%uKB",
+ (int)port_label->slen,
+ port_label->ptr,
+ afd->clock_rate,
+ afd->channel_count,
+ fport->bufsize / 1000));
+
+ return PJ_SUCCESS;
+
+on_error:
+ for (index=0; index<file_count; ++index) {
+ if (fport->fd_list[index] != 0)
+ pj_file_close(fport->fd_list[index]);
+ }
+
+ return status;
+}
+
+
+/*
+ * Register a callback to be called when the file reading has reached the
+ * end of the last file.
+ */
+PJ_DEF(pj_status_t) pjmedia_wav_playlist_set_eof_cb(pjmedia_port *port,
+ void *user_data,
+ pj_status_t (*cb)(pjmedia_port *port,
+ void *usr_data))
+{
+ struct playlist_port *fport;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(port, PJ_EINVAL);
+
+ /* Check that this is really a playlist port */
+ PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVALIDOP);
+
+ fport = (struct playlist_port*) port;
+
+ fport->base.port_data.pdata = user_data;
+ fport->cb = cb;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get frame from file for file_list operation
+ */
+static pj_status_t file_list_get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ struct playlist_port *fport = (struct playlist_port*)this_port;
+ unsigned frame_size;
+ pj_status_t status;
+
+ pj_assert(fport->base.info.signature == SIGNATURE);
+
+ //frame_size = fport->base.info.bytes_per_frame;
+ //pj_assert(frame->size == frame_size);
+ frame_size = frame->size;
+
+ /* Copy frame from buffer. */
+ frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame->size = frame_size;
+ 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 = file_fill_buffer(fport);
+ if (status != PJ_SUCCESS) {
+ frame->type = PJMEDIA_FRAME_TYPE_NONE;
+ frame->size = 0;
+ return status;
+ }
+ }
+ } else {
+ unsigned endread;
+
+ /* Split read.
+ * First stage: read until end of buffer.
+ */
+ endread = (fport->buf+fport->bufsize) - fport->readpos;
+ pj_memcpy(frame->buf, fport->readpos, endread);
+
+ /* Second stage: fill up buffer, and read from the start of buffer. */
+ status = file_fill_buffer(fport);
+ if (status != PJ_SUCCESS) {
+ pj_bzero(((char*)frame->buf)+endread, frame_size-endread);
+ return status;
+ }
+
+ pj_memcpy(((char*)frame->buf)+endread, fport->buf, frame_size-endread);
+ fport->readpos = fport->buf + (frame_size - endread);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy port.
+ */
+static pj_status_t file_list_on_destroy(pjmedia_port *this_port)
+{
+ struct playlist_port *fport = (struct playlist_port*) this_port;
+ int index;
+
+ pj_assert(this_port->info.signature == SIGNATURE);
+
+ for (index=0; index<fport->max_file; index++)
+ pj_file_close(fport->fd_list[index]);
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjmedia/src/pjmedia/wav_writer.c b/pjmedia/src/pjmedia/wav_writer.c
new file mode 100644
index 0000000..76290b7
--- /dev/null
+++ b/pjmedia/src/pjmedia/wav_writer.c
@@ -0,0 +1,457 @@
+/* $Id: wav_writer.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/wav_port.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/wave.h>
+#include <pj/assert.h>
+#include <pj/file_access.h>
+#include <pj/file_io.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#define THIS_FILE "wav_writer.c"
+#define SIGNATURE PJMEDIA_SIG_PORT_WAV_WRITER
+
+
+struct file_port
+{
+ pjmedia_port base;
+ pjmedia_wave_fmt_tag fmt_tag;
+ pj_uint16_t bytes_per_sample;
+
+ pj_size_t bufsize;
+ char *buf;
+ char *writepos;
+ pj_size_t total;
+
+ pj_oshandle_t fd;
+
+ pj_size_t cb_size;
+ pj_status_t (*cb)(pjmedia_port*, void*);
+};
+
+static pj_status_t file_put_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t file_get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame);
+static pj_status_t file_on_destroy(pjmedia_port *this_port);
+
+
+/*
+ * 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;
+}
+
+
+
+/*
+ * Get current writing position.
+ */
+PJ_DEF(pj_ssize_t) pjmedia_wav_writer_port_get_pos( pjmedia_port *port )
+{
+ struct file_port *fport;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(port, -PJ_EINVAL);
+
+ /* Check that this is really a writer port */
+ PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, -PJ_EINVALIDOP);
+
+ fport = (struct file_port*) port;
+
+ return fport->total;
+}
+
+
+/*
+ * Register callback.
+ */
+PJ_DEF(pj_status_t) pjmedia_wav_writer_port_set_cb( pjmedia_port *port,
+ pj_size_t pos,
+ void *user_data,
+ pj_status_t (*cb)(pjmedia_port *port,
+ void *usr_data))
+{
+ struct file_port *fport;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(port && cb, PJ_EINVAL);
+
+ /* Check that this is really a writer port */
+ PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVALIDOP);
+
+ fport = (struct file_port*) port;
+
+ fport->cb_size = pos;
+ fport->base.port_data.pdata = user_data;
+ fport->cb = cb;
+
+ return PJ_SUCCESS;
+}
+
+
+#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0
+ static void swap_samples(pj_int16_t *samples, unsigned count)
+ {
+ unsigned i;
+ for (i=0; i<count; ++i) {
+ samples[i] = pj_swap16(samples[i]);
+ }
+ }
+#else
+# define swap_samples(samples,count)
+#endif
+
+/*
+ * Flush the contents of the buffer to the file.
+ */
+static pj_status_t flush_buffer(struct file_port *fport)
+{
+ pj_ssize_t bytes = fport->writepos - fport->buf;
+ pj_status_t status;
+
+ /* Convert samples to little endian */
+ swap_samples((pj_int16_t*)fport->buf, bytes/fport->bytes_per_sample);
+
+ /* Write to file. */
+ status = pj_file_write(fport->fd, fport->buf, &bytes);
+
+ /* Reset writepos */
+ fport->writepos = fport->buf;
+
+ return status;
+}
+
+/*
+ * Put a frame into the buffer. When the buffer is full, flush the buffer
+ * to the file.
+ */
+static pj_status_t file_put_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ struct file_port *fport = (struct file_port *)this_port;
+ unsigned frame_size;
+
+ if (fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_PCM)
+ frame_size = frame->size;
+ else
+ frame_size = frame->size >> 1;
+
+ /* Flush buffer if we don't have enough room for the frame. */
+ if (fport->writepos + frame_size > fport->buf + fport->bufsize) {
+ pj_status_t status;
+ status = flush_buffer(fport);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /* Check if frame is not too large. */
+ PJ_ASSERT_RETURN(fport->writepos+frame_size <= fport->buf+fport->bufsize,
+ PJMEDIA_EFRMFILETOOBIG);
+
+ /* Copy frame to buffer. */
+ if (fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_PCM) {
+ pj_memcpy(fport->writepos, frame->buf, frame->size);
+ } else {
+ unsigned i;
+ pj_int16_t *src = (pj_int16_t*)frame->buf;
+ pj_uint8_t *dst = (pj_uint8_t*)fport->writepos;
+
+ if (fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_ULAW) {
+ for (i = 0; i < frame_size; ++i) {
+ *dst++ = pjmedia_linear2ulaw(*src++);
+ }
+ } else {
+ for (i = 0; i < frame_size; ++i) {
+ *dst++ = pjmedia_linear2alaw(*src++);
+ }
+ }
+
+ }
+ fport->writepos += frame_size;
+
+ /* Increment total written, and check if we need to call callback */
+ fport->total += frame_size;
+ if (fport->cb && fport->total >= fport->cb_size) {
+ pj_status_t (*cb)(pjmedia_port*, void*);
+ pj_status_t status;
+
+ cb = fport->cb;
+ fport->cb = NULL;
+
+ status = (*cb)(this_port, this_port->port_data.pdata);
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get frame, basicy is a no-op operation.
+ */
+static pj_status_t file_get_frame(pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ PJ_UNUSED_ARG(this_port);
+ PJ_UNUSED_ARG(frame);
+ return PJ_EINVALIDOP;
+}
+
+/*
+ * Close the port, modify file header with updated file length.
+ */
+static pj_status_t file_on_destroy(pjmedia_port *this_port)
+{
+ enum { FILE_LEN_POS = 4, DATA_LEN_POS = 40 };
+ struct file_port *fport = (struct file_port *)this_port;
+ pj_off_t file_size;
+ pj_ssize_t bytes;
+ pj_uint32_t wave_file_len;
+ pj_uint32_t wave_data_len;
+ pj_status_t status;
+ pj_uint32_t data_len_pos = DATA_LEN_POS;
+
+ /* Flush remaining buffers. */
+ if (fport->writepos != fport->buf)
+ flush_buffer(fport);
+
+ /* Get file size. */
+ status = pj_file_getpos(fport->fd, &file_size);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Calculate wave fields */
+ wave_file_len = (pj_uint32_t)(file_size - 8);
+ wave_data_len = (pj_uint32_t)(file_size - sizeof(pjmedia_wave_hdr));
+
+#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0
+ wave_file_len = pj_swap32(wave_file_len);
+ wave_data_len = pj_swap32(wave_data_len);
+#endif
+
+ /* Seek to the file_len field. */
+ status = pj_file_setpos(fport->fd, FILE_LEN_POS, PJ_SEEK_SET);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Write file_len */
+ bytes = sizeof(wave_file_len);
+ status = pj_file_write(fport->fd, &wave_file_len, &bytes);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Write samples_len in FACT chunk */
+ if (fport->fmt_tag != PJMEDIA_WAVE_FMT_TAG_PCM) {
+ enum { SAMPLES_LEN_POS = 44};
+ pj_uint32_t wav_samples_len;
+
+ /* Adjust wave_data_len & data_len_pos since there is FACT chunk */
+ wave_data_len -= 12;
+ data_len_pos += 12;
+ wav_samples_len = wave_data_len;
+
+ /* Seek to samples_len field. */
+ status = pj_file_setpos(fport->fd, SAMPLES_LEN_POS, PJ_SEEK_SET);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Write samples_len */
+ bytes = sizeof(wav_samples_len);
+ status = pj_file_write(fport->fd, &wav_samples_len, &bytes);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ }
+
+ /* Seek to data_len field. */
+ status = pj_file_setpos(fport->fd, data_len_pos, PJ_SEEK_SET);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Write file_len */
+ bytes = sizeof(wave_data_len);
+ status = pj_file_write(fport->fd, &wave_data_len, &bytes);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Close file */
+ status = pj_file_close(fport->fd);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Done. */
+ return PJ_SUCCESS;
+}
+
diff --git a/pjmedia/src/pjmedia/wave.c b/pjmedia/src/pjmedia/wave.c
new file mode 100644
index 0000000..4d6f780
--- /dev/null
+++ b/pjmedia/src/pjmedia/wave.c
@@ -0,0 +1,58 @@
+/* $Id: wave.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/wave.h>
+
+/*
+ * Change the endianness of WAVE header fields.
+ */
+static void wave_hdr_swap_bytes( pjmedia_wave_hdr *hdr )
+{
+#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0
+ hdr->riff_hdr.riff = pj_swap32(hdr->riff_hdr.riff);
+ hdr->riff_hdr.file_len = pj_swap32(hdr->riff_hdr.file_len);
+ hdr->riff_hdr.wave = pj_swap32(hdr->riff_hdr.wave);
+
+ hdr->fmt_hdr.fmt = pj_swap32(hdr->fmt_hdr.fmt);
+ hdr->fmt_hdr.len = pj_swap32(hdr->fmt_hdr.len);
+ hdr->fmt_hdr.fmt_tag = pj_swap16(hdr->fmt_hdr.fmt_tag);
+ hdr->fmt_hdr.nchan = pj_swap16(hdr->fmt_hdr.nchan);
+ hdr->fmt_hdr.sample_rate = pj_swap32(hdr->fmt_hdr.sample_rate);
+ hdr->fmt_hdr.bytes_per_sec = pj_swap32(hdr->fmt_hdr.bytes_per_sec);
+ hdr->fmt_hdr.block_align = pj_swap16(hdr->fmt_hdr.block_align);
+ hdr->fmt_hdr.bits_per_sample = pj_swap16(hdr->fmt_hdr.bits_per_sample);
+
+ hdr->data_hdr.data = pj_swap32(hdr->data_hdr.data);
+ hdr->data_hdr.len = pj_swap32(hdr->data_hdr.len);
+#else
+ PJ_UNUSED_ARG(hdr);
+#endif
+}
+
+
+PJ_DEF(void) pjmedia_wave_hdr_file_to_host( pjmedia_wave_hdr *hdr )
+{
+ wave_hdr_swap_bytes(hdr);
+}
+
+PJ_DEF(void) pjmedia_wave_hdr_host_to_file( pjmedia_wave_hdr *hdr )
+{
+ wave_hdr_swap_bytes(hdr);
+}
+
diff --git a/pjmedia/src/pjmedia/wsola.c b/pjmedia/src/pjmedia/wsola.c
new file mode 100644
index 0000000..ccdd514
--- /dev/null
+++ b/pjmedia/src/pjmedia/wsola.c
@@ -0,0 +1,1139 @@
+/* $Id: wsola.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/wsola.h>
+#include <pjmedia/circbuf.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/pool.h>
+
+/*
+ * This file contains implementation of WSOLA using PJMEDIA_WSOLA_IMP_WSOLA
+ * or PJMEDIA_WSOLA_IMP_NULL
+ */
+#define THIS_FILE "wsola.c"
+
+/*
+ * http://trac.pjsip.org/repos/ticket/683:
+ * Workaround for segfault problem in the fixed point version of create_win()
+ * on ARM9 platform, possibly due to gcc optimization bug.
+ *
+ * For now, we will use linear window when floating point is disabled.
+ */
+#ifndef PJMEDIA_WSOLA_LINEAR_WIN
+# define PJMEDIA_WSOLA_LINEAR_WIN (!PJ_HAS_FLOATING_POINT)
+#endif
+
+
+#if 0
+# define TRACE_(x) PJ_LOG(4,x)
+#else
+# define TRACE_(x)
+#endif
+
+#if 0
+# define CHECK_(x) pj_assert(x)
+#else
+# define CHECK_(x)
+#endif
+
+
+#if (PJMEDIA_WSOLA_IMP==PJMEDIA_WSOLA_IMP_WSOLA) || \
+ (PJMEDIA_WSOLA_IMP==PJMEDIA_WSOLA_IMP_WSOLA_LITE)
+
+/*
+ * WSOLA implementation using WSOLA
+ */
+
+/* Buffer size including history, in frames */
+#define FRAME_CNT 6
+
+/* Number of history frames in buffer */
+#define HIST_CNT 1.5
+
+/* Template size, in msec */
+#define TEMPLATE_PTIME PJMEDIA_WSOLA_TEMPLATE_LENGTH_MSEC
+
+/* Hanning window size, in msec */
+#define HANNING_PTIME PJMEDIA_WSOLA_DELAY_MSEC
+
+/* Number of frames in erase buffer */
+#define ERASE_CNT ((unsigned)3)
+
+/* Minimum distance from template for find_pitch() of expansion, in frames */
+#define EXP_MIN_DIST 0.5
+
+/* Maximum distance from template for find_pitch() of expansion, in frames */
+#define EXP_MAX_DIST HIST_CNT
+
+/* Duration of a continuous synthetic frames after which the volume
+ * of the synthetic frame will be set to zero with fading-out effect.
+ */
+#define MAX_EXPAND_MSEC PJMEDIA_WSOLA_MAX_EXPAND_MSEC
+
+
+/* Buffer content:
+ *
+ * +---------+-----------+--------------------+
+ * | history | min_extra | more extra / empty |
+ * +---------+-----------+--------------------+
+ * ^ ^ ^ ^
+ * buf hist_size min_extra buf_size
+ *
+ * History size (hist_size) is a constant value, initialized upon creation.
+ *
+ * min_extra size is equal to HANNING_PTIME, this samples is useful for
+ * smoothening samples transition between generated frame & history
+ * (when PLC is invoked), or between generated samples & normal frame
+ * (after lost/PLC). Since min_extra samples need to be available at
+ * any time, this will introduce delay of HANNING_PTIME ms.
+ *
+ * More extra is excess samples produced by PLC (PLC frame generation may
+ * produce more than exact one frame).
+ *
+ * At any particular time, the buffer will contain at least (hist_size +
+ * min_extra) samples.
+ *
+ * A "save" operation will append the new frame to the end of the buffer,
+ * return the frame from samples right after history and shift the buffer
+ * by one frame.
+ *
+ */
+
+/* WSOLA structure */
+struct pjmedia_wsola
+{
+ unsigned clock_rate; /* Sampling rate. */
+ pj_uint16_t samples_per_frame; /* Samples per frame (const) */
+ pj_uint16_t channel_count; /* Channel countt (const) */
+ pj_uint16_t options; /* Options. */
+
+ pjmedia_circ_buf *buf; /* The buffer. */
+ pj_int16_t *erase_buf; /* Temporary erase buffer. */
+ pj_int16_t *merge_buf; /* Temporary merge buffer. */
+
+ pj_uint16_t buf_size; /* Total buffer size (const) */
+ pj_uint16_t hanning_size; /* Hanning window size (const) */
+ pj_uint16_t templ_size; /* Template size (const) */
+ pj_uint16_t hist_size; /* History size (const) */
+
+ pj_uint16_t min_extra; /* Minimum extra (const) */
+ unsigned max_expand_cnt; /* Max # of synthetic samples */
+ unsigned fade_out_pos; /* Last fade-out position */
+ pj_uint16_t expand_sr_min_dist;/* Minimum distance from template
+ for find_pitch() on expansion
+ (const) */
+ pj_uint16_t expand_sr_max_dist;/* Maximum distance from template
+ for find_pitch() on expansion
+ (const) */
+
+#if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT!=0
+ float *hanning; /* Hanning window. */
+#else
+ pj_uint16_t *hanning; /* Hanning window. */
+#endif
+
+ pj_timestamp ts; /* Running timestamp. */
+
+};
+
+#if (PJMEDIA_WSOLA_IMP==PJMEDIA_WSOLA_IMP_WSOLA_LITE)
+
+/* In this implementation, waveform similarity comparison is done by calculating
+ * the difference of total level between template frame and the target buffer
+ * for each template_cnt samples. The smallest difference value assumed to be
+ * the most similar block. This seems to be naive, however some tests show
+ * acceptable results and the processing speed is amazing.
+ *
+ * diff level = (template[1]+..+template[n]) - (target[1]+..+target[n])
+ */
+static pj_int16_t *find_pitch(pj_int16_t *frm, pj_int16_t *beg, pj_int16_t *end,
+ unsigned template_cnt, int first)
+{
+ pj_int16_t *sr, *best=beg;
+ int best_corr = 0x7FFFFFFF;
+ int frm_sum = 0;
+ unsigned i;
+
+ for (i = 0; i<template_cnt; ++i)
+ frm_sum += frm[i];
+
+ for (sr=beg; sr!=end; ++sr) {
+ int corr = frm_sum;
+ int abs_corr = 0;
+
+ /* Do calculation on 8 samples at once */
+ for (i = 0; i<template_cnt-8; i+=8) {
+ corr -= (int)sr[i+0] +
+ (int)sr[i+1] +
+ (int)sr[i+2] +
+ (int)sr[i+3] +
+ (int)sr[i+4] +
+ (int)sr[i+5] +
+ (int)sr[i+6] +
+ (int)sr[i+7];
+ }
+
+ /* Process remaining samples */
+ for (; i<template_cnt; ++i)
+ corr -= (int)sr[i];
+
+ abs_corr = corr > 0? corr : -corr;
+
+ if (first) {
+ if (abs_corr < best_corr) {
+ best_corr = abs_corr;
+ best = sr;
+ }
+ } else {
+ if (abs_corr <= best_corr) {
+ best_corr = abs_corr;
+ best = sr;
+ }
+ }
+ }
+
+ /*TRACE_((THIS_FILE, "found pitch at %u", best-beg));*/
+ return best;
+}
+
+#endif
+
+#if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT!=0
+/*
+ * Floating point version.
+ */
+
+#if (PJMEDIA_WSOLA_IMP==PJMEDIA_WSOLA_IMP_WSOLA)
+
+static pj_int16_t *find_pitch(pj_int16_t *frm, pj_int16_t *beg, pj_int16_t *end,
+ unsigned template_cnt, int first)
+{
+ pj_int16_t *sr, *best=beg;
+ double best_corr = 0;
+
+ for (sr=beg; sr!=end; ++sr) {
+ double corr = 0;
+ unsigned i;
+
+ /* Do calculation on 8 samples at once */
+ for (i=0; i<template_cnt-8; i += 8) {
+ corr += ((float)frm[i+0]) * ((float)sr[i+0]) +
+ ((float)frm[i+1]) * ((float)sr[i+1]) +
+ ((float)frm[i+2]) * ((float)sr[i+2]) +
+ ((float)frm[i+3]) * ((float)sr[i+3]) +
+ ((float)frm[i+4]) * ((float)sr[i+4]) +
+ ((float)frm[i+5]) * ((float)sr[i+5]) +
+ ((float)frm[i+6]) * ((float)sr[i+6]) +
+ ((float)frm[i+7]) * ((float)sr[i+7]);
+ }
+
+ /* Process remaining samples. */
+ for (; i<template_cnt; ++i) {
+ corr += ((float)frm[i]) * ((float)sr[i]);
+ }
+
+ if (first) {
+ if (corr > best_corr) {
+ best_corr = corr;
+ best = sr;
+ }
+ } else {
+ if (corr >= best_corr) {
+ best_corr = corr;
+ best = sr;
+ }
+ }
+ }
+
+ /*TRACE_((THIS_FILE, "found pitch at %u", best-beg));*/
+ return best;
+}
+
+#endif
+
+static void overlapp_add(pj_int16_t dst[], unsigned count,
+ pj_int16_t l[], pj_int16_t r[],
+ float w[])
+{
+ unsigned i;
+
+ for (i=0; i<count; ++i) {
+ dst[i] = (pj_int16_t)(l[i] * w[count-1-i] + r[i] * w[i]);
+ }
+}
+
+static void overlapp_add_simple(pj_int16_t dst[], unsigned count,
+ pj_int16_t l[], pj_int16_t r[])
+{
+ float step = (float)(1.0 / count), stepdown = 1.0;
+ unsigned i;
+
+ for (i=0; i<count; ++i) {
+ dst[i] = (pj_int16_t)(l[i] * stepdown + r[i] * (1-stepdown));
+ stepdown -= step;
+ }
+}
+
+static void create_win(pj_pool_t *pool, float **pw, unsigned count)
+{
+ unsigned i;
+ float *w = (float*)pj_pool_calloc(pool, count, sizeof(float));
+
+ *pw = w;
+
+ for (i=0;i<count; i++) {
+ w[i] = (float)(0.5 - 0.5 * cos(2.0 * PJ_PI * i / (count*2-1)) );
+ }
+}
+
+#else /* PJ_HAS_FLOATING_POINT */
+/*
+ * Fixed point version.
+ */
+#define WINDOW_BITS 15
+enum { WINDOW_MAX_VAL = (1 << WINDOW_BITS)-1 };
+
+#if (PJMEDIA_WSOLA_IMP==PJMEDIA_WSOLA_IMP_WSOLA)
+
+static pj_int16_t *find_pitch(pj_int16_t *frm, pj_int16_t *beg, pj_int16_t *end,
+ unsigned template_cnt, int first)
+{
+ pj_int16_t *sr, *best=beg;
+ pj_int64_t best_corr = 0;
+
+
+ for (sr=beg; sr!=end; ++sr) {
+ pj_int64_t corr = 0;
+ unsigned i;
+
+ /* Do calculation on 8 samples at once */
+ for (i=0; i<template_cnt-8; i+=8) {
+ corr += ((int)frm[i+0]) * ((int)sr[i+0]) +
+ ((int)frm[i+1]) * ((int)sr[i+1]) +
+ ((int)frm[i+2]) * ((int)sr[i+2]) +
+ ((int)frm[i+3]) * ((int)sr[i+3]) +
+ ((int)frm[i+4]) * ((int)sr[i+4]) +
+ ((int)frm[i+5]) * ((int)sr[i+5]) +
+ ((int)frm[i+6]) * ((int)sr[i+6]) +
+ ((int)frm[i+7]) * ((int)sr[i+7]);
+ }
+
+ /* Process remaining samples. */
+ for (; i<template_cnt; ++i) {
+ corr += ((int)frm[i]) * ((int)sr[i]);
+ }
+
+ if (first) {
+ if (corr > best_corr) {
+ best_corr = corr;
+ best = sr;
+ }
+ } else {
+ if (corr >= best_corr) {
+ best_corr = corr;
+ best = sr;
+ }
+ }
+ }
+
+ /*TRACE_((THIS_FILE, "found pitch at %u", best-beg));*/
+ return best;
+}
+
+#endif
+
+
+static void overlapp_add(pj_int16_t dst[], unsigned count,
+ pj_int16_t l[], pj_int16_t r[],
+ pj_uint16_t w[])
+{
+ unsigned i;
+
+ for (i=0; i<count; ++i) {
+ dst[i] = (pj_int16_t)(((int)(l[i]) * (int)(w[count-1-i]) +
+ (int)(r[i]) * (int)(w[i])) >> WINDOW_BITS);
+ }
+}
+
+static void overlapp_add_simple(pj_int16_t dst[], unsigned count,
+ pj_int16_t l[], pj_int16_t r[])
+{
+ int step = ((WINDOW_MAX_VAL+1) / count),
+ stepdown = WINDOW_MAX_VAL;
+ unsigned i;
+
+ for (i=0; i<count; ++i) {
+ dst[i]=(pj_int16_t)((l[i] * stepdown + r[i] * (1-stepdown)) >> WINDOW_BITS);
+ stepdown -= step;
+ }
+}
+
+#if PJ_HAS_INT64 && !PJMEDIA_WSOLA_LINEAR_WIN
+/* approx_cos():
+ * see: http://www.audiomulch.com/~rossb/code/sinusoids/
+ */
+static pj_uint32_t approx_cos( pj_uint32_t x )
+{
+ pj_uint32_t i,j,k;
+
+ if( x == 0 )
+ return 0xFFFFFFFF;
+
+ i = x << 1;
+ k = ((x + 0xBFFFFFFD) & 0x80000000) >> 30;
+ j = i - i * ((i & 0x80000000)>>30);
+ j = j >> 15;
+ j = (j * j + j) >> 1;
+ j = j - j * k;
+
+ return j;
+}
+#endif /* PJ_HAS_INT64 && .. */
+
+static void create_win(pj_pool_t *pool, pj_uint16_t **pw, unsigned count)
+{
+
+ unsigned i;
+ pj_uint16_t *w = (pj_uint16_t*)pj_pool_calloc(pool, count,
+ sizeof(pj_uint16_t));
+
+ *pw = w;
+
+ for (i=0; i<count; i++) {
+#if PJ_HAS_INT64 && !PJMEDIA_WSOLA_LINEAR_WIN
+ pj_uint32_t phase;
+ pj_uint64_t cos_val;
+
+ /* w[i] = (float)(0.5 - 0.5 * cos(2.0 * PJ_PI * i / (count*2-1)) ); */
+
+ phase = (pj_uint32_t)(PJ_INT64(0xFFFFFFFF) * i / (count*2-1));
+ cos_val = approx_cos(phase);
+
+ w[i] = (pj_uint16_t)(WINDOW_MAX_VAL -
+ (WINDOW_MAX_VAL * cos_val) / 0xFFFFFFFF);
+#else
+ /* Revert to linear */
+ w[i] = (pj_uint16_t)(i * WINDOW_MAX_VAL / count);
+#endif
+ }
+}
+
+#endif /* PJ_HAS_FLOATING_POINT */
+
+/* Apply fade-in to the buffer.
+ * - fade_cnt is the number of samples on which the volume
+ * will go from zero to 100%
+ * - fade_pos is current sample position within fade_cnt range.
+ * It is zero for the first sample, so the first sample will
+ * have zero volume. This value is increasing.
+ */
+static void fade_in(pj_int16_t buf[], int count,
+ int fade_in_pos, int fade_cnt)
+{
+#if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT!=0
+ float fade_pos = (float)fade_in_pos;
+#else
+ int fade_pos = fade_in_pos;
+#endif
+
+ if (fade_cnt - fade_pos < count) {
+ for (; fade_pos < fade_cnt; ++fade_pos, ++buf) {
+ *buf = (pj_int16_t)(*buf * fade_pos / fade_cnt);
+ }
+ /* Leave the remaining samples as is */
+ } else {
+ pj_int16_t *end = buf + count;
+ for (; buf != end; ++fade_pos, ++buf) {
+ *buf = (pj_int16_t)(*buf * fade_pos / fade_cnt);
+ }
+ }
+}
+
+/* Apply fade-out to the buffer. */
+static void wsola_fade_out(pjmedia_wsola *wsola,
+ pj_int16_t buf[], int count)
+{
+ pj_int16_t *end = buf + count;
+ int fade_cnt = wsola->max_expand_cnt;
+#if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT!=0
+ float fade_pos = (float)wsola->fade_out_pos;
+#else
+ int fade_pos = wsola->fade_out_pos;
+#endif
+
+ if (wsola->fade_out_pos == 0) {
+ pjmedia_zero_samples(buf, count);
+ } else if (fade_pos < count) {
+ for (; fade_pos; --fade_pos, ++buf) {
+ *buf = (pj_int16_t)(*buf * fade_pos / fade_cnt);
+ }
+ if (buf != end)
+ pjmedia_zero_samples(buf, end - buf);
+ wsola->fade_out_pos = 0;
+ } else {
+ for (; buf != end; --fade_pos, ++buf) {
+ *buf = (pj_int16_t)(*buf * fade_pos / fade_cnt);
+ }
+ wsola->fade_out_pos -= count;
+ }
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_wsola_create( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned samples_per_frame,
+ unsigned channel_count,
+ unsigned options,
+ pjmedia_wsola **p_wsola)
+{
+ pjmedia_wsola *wsola;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pool && clock_rate && samples_per_frame && p_wsola,
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(clock_rate <= 65535, PJ_EINVAL);
+ PJ_ASSERT_RETURN(samples_per_frame < clock_rate, PJ_EINVAL);
+ PJ_ASSERT_RETURN(channel_count > 0, PJ_EINVAL);
+
+ /* Allocate wsola and initialize vars */
+ wsola = PJ_POOL_ZALLOC_T(pool, pjmedia_wsola);
+ wsola->clock_rate= (pj_uint16_t) clock_rate;
+ wsola->samples_per_frame = (pj_uint16_t) samples_per_frame;
+ wsola->channel_count = (pj_uint16_t) channel_count;
+ wsola->options = (pj_uint16_t) options;
+ wsola->max_expand_cnt = clock_rate * MAX_EXPAND_MSEC / 1000;
+ wsola->fade_out_pos = wsola->max_expand_cnt;
+
+ /* Create circular buffer */
+ wsola->buf_size = (pj_uint16_t) (samples_per_frame * FRAME_CNT);
+ status = pjmedia_circ_buf_create(pool, wsola->buf_size, &wsola->buf);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Failed to create circular buf"));
+ return status;
+ }
+
+ /* Calculate history size */
+ wsola->hist_size = (pj_uint16_t)(HIST_CNT * samples_per_frame);
+
+ /* Calculate template size */
+ wsola->templ_size = (pj_uint16_t)(TEMPLATE_PTIME * clock_rate *
+ channel_count / 1000);
+ if (wsola->templ_size > samples_per_frame)
+ wsola->templ_size = wsola->samples_per_frame;
+
+ /* Calculate hanning window size */
+ wsola->hanning_size = (pj_uint16_t)(HANNING_PTIME * clock_rate *
+ channel_count / 1000);
+ if (wsola->hanning_size > wsola->samples_per_frame)
+ wsola->hanning_size = wsola->samples_per_frame;
+
+ pj_assert(wsola->templ_size <= wsola->hanning_size);
+
+ /* Create merge buffer */
+ wsola->merge_buf = (pj_int16_t*) pj_pool_calloc(pool,
+ wsola->hanning_size,
+ sizeof(pj_int16_t));
+
+ /* Setup with PLC */
+ if ((options & PJMEDIA_WSOLA_NO_PLC) == 0) {
+ wsola->min_extra = wsola->hanning_size;
+ wsola->expand_sr_min_dist = (pj_uint16_t)
+ (EXP_MIN_DIST * wsola->samples_per_frame);
+ wsola->expand_sr_max_dist = (pj_uint16_t)
+ (EXP_MAX_DIST * wsola->samples_per_frame);
+ }
+
+ /* Setup with hanning */
+ if ((options & PJMEDIA_WSOLA_NO_HANNING) == 0) {
+ create_win(pool, &wsola->hanning, wsola->hanning_size);
+ }
+
+ /* Setup with discard */
+ if ((options & PJMEDIA_WSOLA_NO_DISCARD) == 0) {
+ wsola->erase_buf = (pj_int16_t*)pj_pool_calloc(pool, samples_per_frame *
+ ERASE_CNT,
+ sizeof(pj_int16_t));
+ }
+
+ /* Generate dummy extra */
+ pjmedia_circ_buf_set_len(wsola->buf, wsola->hist_size + wsola->min_extra);
+
+ *p_wsola = wsola;
+ return PJ_SUCCESS;
+
+}
+
+PJ_DEF(pj_status_t) pjmedia_wsola_destroy(pjmedia_wsola *wsola)
+{
+ /* Nothing to do */
+ PJ_UNUSED_ARG(wsola);
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_wsola_set_max_expand(pjmedia_wsola *wsola,
+ unsigned msec)
+{
+ PJ_ASSERT_RETURN(wsola, PJ_EINVAL);
+ wsola->max_expand_cnt = msec * wsola->clock_rate / 1000;
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_wsola_reset( pjmedia_wsola *wsola,
+ unsigned options)
+{
+ PJ_ASSERT_RETURN(wsola && options==0, PJ_EINVAL);
+ PJ_UNUSED_ARG(options);
+
+ pjmedia_circ_buf_reset(wsola->buf);
+ pjmedia_circ_buf_set_len(wsola->buf, wsola->hist_size + wsola->min_extra);
+ pjmedia_zero_samples(wsola->buf->start, wsola->buf->len);
+ wsola->fade_out_pos = wsola->max_expand_cnt;
+
+ return PJ_SUCCESS;
+}
+
+
+static void expand(pjmedia_wsola *wsola, unsigned needed)
+{
+ unsigned generated = 0;
+ unsigned rep;
+
+ pj_int16_t *reg1, *reg2;
+ unsigned reg1_len, reg2_len;
+
+ pjmedia_circ_buf_pack_buffer(wsola->buf);
+ pjmedia_circ_buf_get_read_regions(wsola->buf, &reg1, &reg1_len,
+ &reg2, &reg2_len);
+ CHECK_(reg2_len == 0);
+
+ for (rep=1;; ++rep) {
+ pj_int16_t *start, *templ;
+ unsigned dist;
+
+ templ = reg1 + reg1_len - wsola->hanning_size;
+ CHECK_(templ - reg1 >= wsola->hist_size);
+
+ start = find_pitch(templ,
+ templ - wsola->expand_sr_max_dist,
+ templ - wsola->expand_sr_min_dist,
+ wsola->templ_size,
+ 1);
+
+ /* Should we make sure that "start" is really aligned to
+ * channel #0, in case of stereo? Probably not necessary, as
+ * find_pitch() should have found the best match anyway.
+ */
+
+ if (wsola->options & PJMEDIA_WSOLA_NO_HANNING) {
+ overlapp_add_simple(wsola->merge_buf, wsola->hanning_size,
+ templ, start);
+ } else {
+ /* Check if pointers are in the valid range */
+ CHECK_(templ >= wsola->buf->buf &&
+ templ + wsola->hanning_size <=
+ wsola->buf->buf + wsola->buf->capacity);
+ CHECK_(start >= wsola->buf->buf &&
+ start + wsola->hanning_size <=
+ wsola->buf->buf + wsola->buf->capacity);
+
+ overlapp_add(wsola->merge_buf, wsola->hanning_size, templ,
+ start, wsola->hanning);
+ }
+
+ /* How many new samples do we have */
+ dist = templ - start;
+
+ /* Not enough buffer to hold the result */
+ if (reg1_len + dist > wsola->buf_size) {
+ pj_assert(!"WSOLA buffer size may be to small!");
+ break;
+ }
+
+ /* Copy the "tail" (excess frame) to the end */
+ pjmedia_move_samples(templ + wsola->hanning_size,
+ start + wsola->hanning_size,
+ dist);
+
+ /* Copy the merged frame */
+ pjmedia_copy_samples(templ, wsola->merge_buf, wsola->hanning_size);
+
+ /* We have new samples */
+ reg1_len += dist;
+ pjmedia_circ_buf_set_len(wsola->buf, reg1_len);
+
+ generated += dist;
+
+ if (generated >= needed) {
+ TRACE_((THIS_FILE, "WSOLA frame expanded after %d iterations",
+ rep));
+ break;
+ }
+ }
+}
+
+
+static unsigned compress(pjmedia_wsola *wsola, pj_int16_t *buf, unsigned count,
+ unsigned del_cnt)
+{
+ unsigned samples_del = 0, rep;
+
+ for (rep=1; ; ++rep) {
+ pj_int16_t *start, *end;
+ unsigned dist;
+
+ if (count <= wsola->hanning_size + del_cnt) {
+ TRACE_((THIS_FILE, "Not enough samples to compress!"));
+ return samples_del;
+ }
+
+ // Make start distance to del_cnt, so discard will be performed in
+ // only one iteration.
+ //start = buf + (frmsz >> 1);
+ start = buf + del_cnt - samples_del;
+ end = start + wsola->samples_per_frame;
+
+ if (end + wsola->hanning_size > buf + count) {
+ end = buf+count-wsola->hanning_size;
+ }
+
+ CHECK_(start < end);
+
+ start = find_pitch(buf, start, end, wsola->templ_size, 0);
+ dist = start - buf;
+
+ if (wsola->options & PJMEDIA_WSOLA_NO_HANNING) {
+ overlapp_add_simple(buf, wsola->hanning_size, buf, start);
+ } else {
+ overlapp_add(buf, wsola->hanning_size, buf, start, wsola->hanning);
+ }
+
+ pjmedia_move_samples(buf + wsola->hanning_size,
+ buf + wsola->hanning_size + dist,
+ count - wsola->hanning_size - dist);
+
+ count -= dist;
+ samples_del += dist;
+
+ if (samples_del >= del_cnt) {
+ TRACE_((THIS_FILE,
+ "Erased %d of %d requested after %d iteration(s)",
+ samples_del, del_cnt, rep));
+ break;
+ }
+ }
+
+ return samples_del;
+}
+
+
+
+PJ_DEF(pj_status_t) pjmedia_wsola_save( pjmedia_wsola *wsola,
+ pj_int16_t frm[],
+ pj_bool_t prev_lost)
+{
+ unsigned buf_len;
+ pj_status_t status;
+
+ buf_len = pjmedia_circ_buf_get_len(wsola->buf);
+
+ /* Update vars */
+ wsola->ts.u64 += wsola->samples_per_frame;
+
+ /* If previous frame was lost, smoothen this frame with the generated one */
+ if (prev_lost) {
+ pj_int16_t *reg1, *reg2;
+ unsigned reg1_len, reg2_len;
+ pj_int16_t *ola_left;
+
+ /* Trim excessive len */
+ if ((int)buf_len > wsola->hist_size + (wsola->min_extra<<1)) {
+ buf_len = wsola->hist_size + (wsola->min_extra<<1);
+ pjmedia_circ_buf_set_len(wsola->buf, buf_len);
+ }
+
+ pjmedia_circ_buf_get_read_regions(wsola->buf, &reg1, &reg1_len,
+ &reg2, &reg2_len);
+
+ CHECK_(pjmedia_circ_buf_get_len(wsola->buf) >=
+ (unsigned)(wsola->hist_size + (wsola->min_extra<<1)));
+
+ /* Continue applying fade out to the extra samples */
+ if ((wsola->options & PJMEDIA_WSOLA_NO_FADING)==0) {
+ if (reg2_len == 0) {
+ wsola_fade_out(wsola, reg1 + reg1_len - (wsola->min_extra<<1),
+ (wsola->min_extra<<1));
+ } else if ((int)reg2_len >= (wsola->min_extra<<1)) {
+ wsola_fade_out(wsola, reg2 + reg2_len - (wsola->min_extra<<1),
+ (wsola->min_extra<<1));
+ } else {
+ unsigned tmp = (wsola->min_extra<<1) - reg2_len;
+ wsola_fade_out(wsola, reg1 + reg1_len - tmp, tmp);
+ wsola_fade_out(wsola, reg2, reg2_len);
+ }
+ }
+
+ /* Get the region in buffer to be merged with the frame */
+ if (reg2_len == 0) {
+ ola_left = reg1 + reg1_len - wsola->min_extra;
+ } else if (reg2_len >= wsola->min_extra) {
+ ola_left = reg2 + reg2_len - wsola->min_extra;
+ } else {
+ unsigned tmp;
+
+ tmp = wsola->min_extra - reg2_len;
+ pjmedia_copy_samples(wsola->merge_buf, reg1 + reg1_len - tmp, tmp);
+ pjmedia_copy_samples(wsola->merge_buf + tmp, reg2, reg2_len);
+ ola_left = wsola->merge_buf;
+ }
+
+ /* Apply fade-in to the frame before merging */
+ if ((wsola->options & PJMEDIA_WSOLA_NO_FADING)==0) {
+ unsigned count = wsola->min_extra;
+ int fade_in_pos;
+
+ /* Scale fade_in position based on last fade-out */
+ fade_in_pos = wsola->fade_out_pos * count /
+ wsola->max_expand_cnt;
+
+ /* Fade-in it */
+ fade_in(frm, wsola->samples_per_frame,
+ fade_in_pos, count);
+ }
+
+ /* Merge it */
+ overlapp_add_simple(frm, wsola->min_extra, ola_left, frm);
+
+ /* Trim len */
+ buf_len -= wsola->min_extra;
+ pjmedia_circ_buf_set_len(wsola->buf, buf_len);
+
+ } else if ((wsola->options & PJMEDIA_WSOLA_NO_FADING)==0 &&
+ wsola->fade_out_pos != wsola->max_expand_cnt)
+ {
+ unsigned count = wsola->min_extra;
+ int fade_in_pos;
+
+ /* Fade out the remaining synthetic samples */
+ if (buf_len > wsola->hist_size) {
+ pj_int16_t *reg1, *reg2;
+ unsigned reg1_len, reg2_len;
+
+ /* Number of samples to fade out */
+ count = buf_len - wsola->hist_size;
+
+ pjmedia_circ_buf_get_read_regions(wsola->buf, &reg1, &reg1_len,
+ &reg2, &reg2_len);
+
+ CHECK_(pjmedia_circ_buf_get_len(wsola->buf) >=
+ (unsigned)(wsola->hist_size + (wsola->min_extra<<1)));
+
+ /* Continue applying fade out to the extra samples */
+ if (reg2_len == 0) {
+ wsola_fade_out(wsola, reg1 + reg1_len - count, count);
+ } else if (reg2_len >= count) {
+ wsola_fade_out(wsola, reg2 + reg2_len - count, count);
+ } else {
+ unsigned tmp = count - reg2_len;
+ wsola_fade_out(wsola, reg1 + reg1_len - tmp, tmp);
+ wsola_fade_out(wsola, reg2, reg2_len);
+ }
+ }
+
+ /* Apply fade-in to the frame */
+ count = wsola->min_extra;
+
+ /* Scale fade_in position based on last fade-out */
+ fade_in_pos = wsola->fade_out_pos * count /
+ wsola->max_expand_cnt;
+
+ /* Fade it in */
+ fade_in(frm, wsola->samples_per_frame,
+ fade_in_pos, count);
+
+ }
+
+ wsola->fade_out_pos = wsola->max_expand_cnt;
+
+ status = pjmedia_circ_buf_write(wsola->buf, frm, wsola->samples_per_frame);
+ if (status != PJ_SUCCESS) {
+ TRACE_((THIS_FILE, "Failed writing to circbuf [err=%d]", status));
+ return status;
+ }
+
+ status = pjmedia_circ_buf_copy(wsola->buf, wsola->hist_size, frm,
+ wsola->samples_per_frame);
+ if (status != PJ_SUCCESS) {
+ TRACE_((THIS_FILE, "Failed copying from circbuf [err=%d]", status));
+ return status;
+ }
+
+ return pjmedia_circ_buf_adv_read_ptr(wsola->buf, wsola->samples_per_frame);
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_wsola_generate( pjmedia_wsola *wsola,
+ pj_int16_t frm[])
+{
+ unsigned samples_len, samples_req;
+ pj_status_t status = PJ_SUCCESS;
+
+ CHECK_(pjmedia_circ_buf_get_len(wsola->buf) >= wsola->hist_size +
+ wsola->min_extra);
+
+ /* Calculate how many samples in the buffer */
+ samples_len = pjmedia_circ_buf_get_len(wsola->buf) - wsola->hist_size;
+
+ /* Calculate how many samples are required to be available in the buffer */
+ samples_req = wsola->samples_per_frame + (wsola->min_extra << 1);
+
+ wsola->ts.u64 += wsola->samples_per_frame;
+
+ if (samples_len < samples_req) {
+ /* Expand buffer */
+ expand(wsola, samples_req - samples_len);
+ TRACE_((THIS_FILE, "Buf size after expanded = %d",
+ pjmedia_circ_buf_get_len(wsola->buf)));
+ }
+
+ status = pjmedia_circ_buf_copy(wsola->buf, wsola->hist_size, frm,
+ wsola->samples_per_frame);
+ if (status != PJ_SUCCESS) {
+ TRACE_((THIS_FILE, "Failed copying from circbuf [err=%d]", status));
+ return status;
+ }
+
+ pjmedia_circ_buf_adv_read_ptr(wsola->buf, wsola->samples_per_frame);
+
+ /* Apply fade-out to the frame */
+ if ((wsola->options & PJMEDIA_WSOLA_NO_FADING)==0) {
+ wsola_fade_out(wsola, frm, wsola->samples_per_frame);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_wsola_discard( pjmedia_wsola *wsola,
+ pj_int16_t buf1[],
+ unsigned buf1_cnt,
+ pj_int16_t buf2[],
+ unsigned buf2_cnt,
+ unsigned *del_cnt)
+{
+ PJ_ASSERT_RETURN(wsola && buf1 && buf1_cnt && del_cnt, PJ_EINVAL);
+ PJ_ASSERT_RETURN(*del_cnt, PJ_EINVAL);
+
+ if (buf2_cnt == 0) {
+ /* The whole buffer is contiguous space, straight away. */
+ *del_cnt = compress(wsola, buf1, buf1_cnt, *del_cnt);
+ } else {
+ PJ_ASSERT_RETURN(buf2, PJ_EINVAL);
+
+ if (buf1_cnt < ERASE_CNT * wsola->samples_per_frame &&
+ buf2_cnt < ERASE_CNT * wsola->samples_per_frame &&
+ wsola->erase_buf == NULL)
+ {
+ /* We need erase_buf but WSOLA was created with
+ * PJMEDIA_WSOLA_NO_DISCARD flag.
+ */
+ pj_assert(!"WSOLA need erase buffer!");
+ return PJ_EINVALIDOP;
+ }
+
+ if (buf2_cnt >= ERASE_CNT * wsola->samples_per_frame) {
+ /* Enough space to perform compress in the second buffer. */
+ *del_cnt = compress(wsola, buf2, buf2_cnt, *del_cnt);
+ } else if (buf1_cnt >= ERASE_CNT * wsola->samples_per_frame) {
+ /* Enough space to perform compress in the first buffer, but then
+ * we need to re-arrange the buffers so there is no gap between
+ * buffers.
+ */
+ unsigned max;
+
+ *del_cnt = compress(wsola, buf1, buf1_cnt, *del_cnt);
+
+ max = *del_cnt;
+ if (max > buf2_cnt)
+ max = buf2_cnt;
+
+ pjmedia_move_samples(buf1 + buf1_cnt - (*del_cnt), buf2, max);
+
+ if (max < buf2_cnt) {
+ pjmedia_move_samples(buf2, buf2+(*del_cnt),
+ buf2_cnt-max);
+ }
+ } else {
+ /* Not enough samples in either buffers to perform compress.
+ * Need to combine the buffers in a contiguous space, the erase_buf.
+ */
+ unsigned buf_size = buf1_cnt + buf2_cnt;
+ pj_int16_t *rem; /* remainder */
+ unsigned rem_cnt;
+
+ if (buf_size > ERASE_CNT * wsola->samples_per_frame) {
+ buf_size = ERASE_CNT * wsola->samples_per_frame;
+
+ rem_cnt = buf1_cnt + buf2_cnt - buf_size;
+ rem = buf2 + buf2_cnt - rem_cnt;
+
+ } else {
+ rem = NULL;
+ rem_cnt = 0;
+ }
+
+ pjmedia_copy_samples(wsola->erase_buf, buf1, buf1_cnt);
+ pjmedia_copy_samples(wsola->erase_buf+buf1_cnt, buf2,
+ buf_size-buf1_cnt);
+
+ *del_cnt = compress(wsola, wsola->erase_buf, buf_size, *del_cnt);
+
+ buf_size -= (*del_cnt);
+
+ /* Copy back to buffers */
+ if (buf_size == buf1_cnt) {
+ pjmedia_copy_samples(buf1, wsola->erase_buf, buf_size);
+ if (rem_cnt) {
+ pjmedia_move_samples(buf2, rem, rem_cnt);
+ }
+ } else if (buf_size < buf1_cnt) {
+ pjmedia_copy_samples(buf1, wsola->erase_buf, buf_size);
+ if (rem_cnt) {
+ unsigned c = rem_cnt;
+ if (c > buf1_cnt-buf_size) {
+ c = buf1_cnt-buf_size;
+ }
+ pjmedia_copy_samples(buf1+buf_size, rem, c);
+ rem += c;
+ rem_cnt -= c;
+ if (rem_cnt)
+ pjmedia_move_samples(buf2, rem, rem_cnt);
+ }
+ } else {
+ pjmedia_copy_samples(buf1, wsola->erase_buf, buf1_cnt);
+ pjmedia_copy_samples(buf2, wsola->erase_buf+buf1_cnt,
+ buf_size-buf1_cnt);
+ if (rem_cnt) {
+ pjmedia_move_samples(buf2+buf_size-buf1_cnt, rem,
+ rem_cnt);
+ }
+ }
+
+ }
+ }
+
+ return (*del_cnt) > 0 ? PJ_SUCCESS : PJ_ETOOSMALL;
+}
+
+
+#elif PJMEDIA_WSOLA_IMP==PJMEDIA_WSOLA_IMP_NULL
+/*
+ * WSOLA implementation using NULL
+ */
+
+struct pjmedia_wsola
+{
+ unsigned samples_per_frame;
+};
+
+
+PJ_DEF(pj_status_t) pjmedia_wsola_create( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned samples_per_frame,
+ unsigned channel_count,
+ unsigned options,
+ pjmedia_wsola **p_wsola)
+{
+ pjmedia_wsola *wsola;
+
+ wsola = PJ_POOL_ZALLOC_T(pool, struct pjmedia_wsola);
+ wsola->samples_per_frame = samples_per_frame;
+
+ PJ_UNUSED_ARG(clock_rate);
+ PJ_UNUSED_ARG(channel_count);
+ PJ_UNUSED_ARG(options);
+
+ *p_wsola = wsola;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_wsola_destroy(pjmedia_wsola *wsola)
+{
+ PJ_UNUSED_ARG(wsola);
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_wsola_reset( pjmedia_wsola *wsola,
+ unsigned options)
+{
+ PJ_UNUSED_ARG(wsola);
+ PJ_UNUSED_ARG(options);
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_wsola_save( pjmedia_wsola *wsola,
+ pj_int16_t frm[],
+ pj_bool_t prev_lost)
+{
+ PJ_UNUSED_ARG(wsola);
+ PJ_UNUSED_ARG(frm);
+ PJ_UNUSED_ARG(prev_lost);
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_wsola_generate( pjmedia_wsola *wsola,
+ pj_int16_t frm[])
+{
+ pjmedia_zero_samples(frm, wsola->samples_per_frame);
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_wsola_discard( pjmedia_wsola *wsola,
+ pj_int16_t buf1[],
+ unsigned buf1_cnt,
+ pj_int16_t buf2[],
+ unsigned buf2_cnt,
+ unsigned *del_cnt)
+{
+ CHECK_(buf1_cnt + buf2_cnt >= wsola->samples_per_frame);
+
+ PJ_UNUSED_ARG(buf1);
+ PJ_UNUSED_ARG(buf1_cnt);
+ PJ_UNUSED_ARG(buf2);
+ PJ_UNUSED_ARG(buf2_cnt);
+
+ *del_cnt = wsola->samples_per_frame;
+
+ return PJ_SUCCESS;
+}
+
+#endif /* #if PJMEDIA_WSOLA_IMP.. */
+
+
diff --git a/pjmedia/src/test/audio_tool.c b/pjmedia/src/test/audio_tool.c
new file mode 100644
index 0000000..fb761f1
--- /dev/null
+++ b/pjmedia/src/test/audio_tool.c
@@ -0,0 +1,410 @@
+/* $Id: audio_tool.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia.h>
+#include <pjlib.h>
+#include <stdio.h>
+
+#define THIS_FILE "audio_tool.c"
+
+static pj_caching_pool caching_pool;
+static pj_pool_factory *pf;
+static FILE *fhnd;
+static pj_med_mgr_t *mm;
+static pjmedia_codec *codec;
+static pjmedia_codec_param cattr;
+
+
+#define WRITE_ORIGINAL_PCM 0
+#if WRITE_ORIGINAL_PCM
+static FILE *fhnd_pcm;
+#endif
+
+static char talker_sdp[] =
+ "v=0\r\n"
+ "o=- 0 0 IN IP4 127.0.0.1\r\n"
+ "s=-\r\n"
+ "c=IN IP4 127.0.0.1\r\n"
+ "t=0 0\r\n"
+ "m=audio 4002 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=sendonly\r\n";
+static char listener_sdp[] =
+ "v=0\r\n"
+ "o=- 0 0 IN IP4 127.0.0.1\r\n"
+ "s=-\r\n"
+ "c=IN IP4 127.0.0.1\r\n"
+ "t=0 0\r\n"
+ "m=audio 4000 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=recvonly\r\n";
+
+static pj_status_t play_callback(/* in */ void *user_data,
+ /* in */ pj_uint32_t timestamp,
+ /* out */ void *frame,
+ /* out */ unsigned size)
+{
+ char pkt[160];
+ struct pjmedia_frame in, out;
+ int frmsz = cattr.avg_bps / 8 * cattr.ptime / 1000;
+
+ if (fread(pkt, frmsz, 1, fhnd) != 1) {
+ puts("EOF");
+ return -1;
+ } else {
+ in.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ in.buf = pkt;
+ in.size = frmsz;
+ out.buf = frame;
+ if (codec->op->decode (codec, &in, size, &out) != 0)
+ return -1;
+
+ size = out.size;
+ return 0;
+ }
+}
+
+static pj_status_t rec_callback( /* in */ void *user_data,
+ /* in */ pj_uint32_t timestamp,
+ /* in */ const void *frame,
+ /* in*/ unsigned size)
+{
+ char pkt[160];
+ struct pjmedia_frame in, out;
+ //int frmsz = cattr.avg_bps / 8 * cattr.ptime / 1000;
+
+#if WRITE_ORIGINAL_PCM
+ fwrite(frame, size, 1, fhnd_pcm);
+#endif
+
+ in.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ in.buf = (void*)frame;
+ in.size = size;
+ out.buf = pkt;
+
+ if (codec->op->encode(codec, &in, sizeof(pkt), &out) != 0)
+ return -1;
+
+ if (fwrite(pkt, out.size, 1, fhnd) != 1)
+ return -1;
+ return 0;
+}
+
+static pj_status_t init()
+{
+ pjmedia_codec_mgr *cm;
+ pjmedia_codec_info id;
+ int i;
+
+ pj_caching_pool_init(&caching_pool, &pj_pool_factory_default_policy, 0);
+ pf = &caching_pool.factory;
+
+ if (pj_snd_init(&caching_pool.factory))
+ return -1;
+
+ PJ_LOG(3,(THIS_FILE, "Dumping audio devices:"));
+ for (i=0; i<pj_snd_get_dev_count(); ++i) {
+ const pj_snd_dev_info *info;
+ info = pj_snd_get_dev_info(i);
+ PJ_LOG(3,(THIS_FILE, " %d: %s\t(%d in, %d out",
+ i, info->name,
+ info->input_count, info->output_count));
+ }
+
+ mm = pj_med_mgr_create (&caching_pool.factory);
+ cm = pj_med_mgr_get_codec_mgr (mm);
+
+ id.type = PJMEDIA_TYPE_AUDIO;
+ id.pt = 0;
+ id.encoding_name = pj_str("PCMU");
+ id.sample_rate = 8000;
+
+ codec = pjmedia_codec_mgr_alloc_codec (cm, &id);
+ codec->op->default_attr(codec, &cattr);
+ codec->op->open(codec, &cattr);
+ return 0;
+}
+
+static pj_status_t deinit()
+{
+ pjmedia_codec_mgr *cm;
+ cm = pj_med_mgr_get_codec_mgr (mm);
+ codec->op->close(codec);
+ pjmedia_codec_mgr_dealloc_codec (cm, codec);
+ pj_med_mgr_destroy (mm);
+ pj_caching_pool_destroy(&caching_pool);
+ return 0;
+}
+
+static pj_status_t record_file (const char *filename)
+{
+ pj_snd_stream *stream;
+ pj_snd_stream_info info;
+ int status;
+ char s[10];
+
+ printf("Recording to file %s...\n", filename);
+
+ fhnd = fopen(filename, "wb");
+ if (!fhnd)
+ return -1;
+
+#if WRITE_ORIGINAL_PCM
+ fhnd_pcm = fopen("ORIGINAL.PCM", "wb");
+ if (!fhnd_pcm)
+ return -1;
+#endif
+
+ pj_bzero(&info, sizeof(info));
+ info.bits_per_sample = 16;
+ info.bytes_per_frame = 2;
+ info.frames_per_packet = 160;
+ info.samples_per_frame = 1;
+ info.samples_per_sec = 8000;
+
+ stream = pj_snd_open_recorder(-1, &info, &rec_callback, NULL);
+ if (!stream)
+ return -1;
+
+ status = pj_snd_stream_start(stream);
+ if (status != 0)
+ goto on_error;
+
+ puts("Press <ENTER> to exit recording");
+ fgets(s, sizeof(s), stdin);
+
+ pj_snd_stream_stop(stream);
+ pj_snd_stream_close(stream);
+
+#if WRITE_ORIGINAL_PCM
+ fclose(fhnd_pcm);
+#endif
+ fclose(fhnd);
+ return 0;
+
+on_error:
+ pj_snd_stream_stop(stream);
+ pj_snd_stream_close(stream);
+ return -1;
+}
+
+
+static pj_status_t play_file (const char *filename)
+{
+ pj_snd_stream *stream;
+ pj_snd_stream_info info;
+ int status;
+ char s[10];
+
+ printf("Playing file %s...\n", filename);
+
+ fhnd = fopen(filename, "rb");
+ if (!fhnd)
+ return -1;
+
+ pj_bzero(&info, sizeof(info));
+ info.bits_per_sample = 16;
+ info.bytes_per_frame = 2;
+ info.frames_per_packet = 160;
+ info.samples_per_frame = 1;
+ info.samples_per_sec = 8000;
+
+ stream = pj_snd_open_player(-1, &info, &play_callback, NULL);
+ if (!stream)
+ return -1;
+
+ status = pj_snd_stream_start(stream);
+ if (status != 0)
+ goto on_error;
+
+ puts("Press <ENTER> to exit playing");
+ fgets(s, sizeof(s), stdin);
+
+ pj_snd_stream_stop(stream);
+ pj_snd_stream_close(stream);
+
+ fclose(fhnd);
+ return 0;
+
+on_error:
+ pj_snd_stream_stop(stream);
+ pj_snd_stream_close(stream);
+ return -1;
+}
+
+static int create_ses_by_remote_sdp(int local_port, char *sdp)
+{
+ pj_media_session_t *ses = NULL;
+ pjsdp_session_desc *sdp_ses;
+ pj_media_sock_info skinfo;
+ pj_pool_t *pool;
+ char s[4];
+ const pj_media_stream_info *info[2];
+ int i, count;
+
+ pool = pj_pool_create(pf, "sdp", 1024, 0, NULL);
+ if (!pool) {
+ PJ_LOG(1,(THIS_FILE, "Unable to create pool"));
+ return -1;
+ }
+
+ pj_bzero(&skinfo, sizeof(skinfo));
+ skinfo.rtp_sock = skinfo.rtcp_sock = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, 0);
+ if (skinfo.rtp_sock == PJ_INVALID_SOCKET) {
+ PJ_LOG(1,(THIS_FILE, "Unable to create socket"));
+ goto on_error;
+ }
+
+ pj_sockaddr_init2(&skinfo.rtp_addr_name, "0.0.0.0", local_port);
+ if (pj_sock_bind(skinfo.rtp_sock, (struct pj_sockaddr*)&skinfo.rtp_addr_name, sizeof(pj_sockaddr_in)) != 0) {
+ PJ_LOG(1,(THIS_FILE, "Unable to bind socket"));
+ goto on_error;
+ }
+
+ sdp_ses = pjsdp_parse(sdp, strlen(sdp), pool);
+ if (!sdp_ses) {
+ PJ_LOG(1,(THIS_FILE, "Error parsing SDP"));
+ goto on_error;
+ }
+
+ ses = pj_media_session_create_from_sdp(mm, sdp_ses, &skinfo);
+ if (!ses) {
+ PJ_LOG(1,(THIS_FILE, "Unable to create session from SDP"));
+ goto on_error;
+ }
+
+ if (pj_media_session_activate(ses) != 0) {
+ PJ_LOG(1,(THIS_FILE, "Error activating session"));
+ goto on_error;
+ }
+
+ count = pj_media_session_enum_streams(ses, 2, info);
+ printf("\nDumping streams: \n");
+ for (i=0; i<count; ++i) {
+ const char *dir;
+ char *local_ip;
+
+ switch (info[i]->dir) {
+ case PJMEDIA_DIR_NONE:
+ dir = "- NONE -"; break;
+ case PJMEDIA_DIR_ENCODING:
+ dir = "SENDONLY"; break;
+ case PJMEDIA_DIR_DECODING:
+ dir = "RECVONLY"; break;
+ case PJMEDIA_DIR_ENCODING_DECODING:
+ dir = "SENDRECV"; break;
+ default:
+ dir = "?UNKNOWN"; break;
+ }
+
+ local_ip = pj_sockaddr_get_str_addr(&info[i]->sock_info.rtp_addr_name);
+
+ printf(" Stream %d: %.*s %s local=%s:%d remote=%.*s:%d\n",
+ i, info[i]->type.slen, info[i]->type.ptr,
+ dir,
+ local_ip, pj_sockaddr_get_port(&info[i]->sock_info.rtp_addr_name),
+ info[i]->rem_addr.slen, info[i]->rem_addr.ptr, info[i]->rem_port);
+ }
+
+ puts("Press <ENTER> to quit");
+ fgets(s, sizeof(s), stdin);
+
+ pj_media_session_destroy(ses);
+ pj_sock_close(skinfo.rtp_sock);
+ pj_pool_release(pool);
+
+ return 0;
+
+on_error:
+ if (ses)
+ pj_media_session_destroy(ses);
+ if (skinfo.rtp_sock != PJ_INVALID_SOCKET)
+ pj_sock_close(skinfo.rtp_sock);
+ if (pool)
+ pj_pool_release(pool);
+ return -1;
+}
+
+#if WRITE_ORIGINAL_PCM
+static pj_status_t convert(const char *src, const char *dst)
+{
+ char pcm[320];
+ char frame[160];
+ struct pjmedia_frame in, out;
+
+ fhnd_pcm = fopen(src, "rb");
+ if (!fhnd_pcm)
+ return -1;
+ fhnd = fopen(dst, "wb");
+ if (!fhnd)
+ return -1;
+
+ while (fread(pcm, 320, 1, fhnd_pcm) == 1) {
+
+ in.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ in.buf = pcm;
+ in.size = 320;
+ out.buf = frame;
+
+ if (codec->op->encode(codec, &in, 160, &out) != 0)
+ break;
+
+ if (fwrite(frame, out.size, 1, fhnd) != 1)
+ break;
+
+ }
+
+ fclose(fhnd);
+ fclose(fhnd_pcm);
+ return 0;
+}
+#endif
+
+static void usage(const char *exe)
+{
+ printf("Usage: %s <command> <file>\n", exe);
+ puts("where:");
+ puts(" <command> play|record|send|recv");
+}
+
+int main(int argc, char *argv[])
+{
+ if (argc < 2) {
+ usage(argv[0]);
+ return 1;
+ }
+
+ pj_init();
+
+ init();
+
+ if (stricmp(argv[1], "record")==0) {
+ record_file("FILE.PCM");
+ } else if (stricmp(argv[1], "play")==0) {
+ play_file("FILE.PCM");
+ } else if (stricmp(argv[1], "send")==0) {
+ create_ses_by_remote_sdp(4002, listener_sdp);
+ } else if (stricmp(argv[1], "recv")==0) {
+ create_ses_by_remote_sdp(4000, talker_sdp);
+ } else {
+ usage(argv[0]);
+ }
+ deinit();
+ return 0;
+}
diff --git a/pjmedia/src/test/codec_vectors.c b/pjmedia/src/test/codec_vectors.c
new file mode 100644
index 0000000..71962f4
--- /dev/null
+++ b/pjmedia/src/test/codec_vectors.c
@@ -0,0 +1,626 @@
+/* $Id: codec_vectors.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 "test.h"
+#include <pjmedia-codec.h>
+
+#define THIS_FILE "codec_vectors.c"
+#define TMP_OUT "output.tmp"
+
+/*
+ * Encode test. Read input from WAV file, encode to temporary output file,
+ * and compare the temporary output file to the reference file.
+ */
+static int codec_test_encode(pjmedia_codec_mgr *mgr,
+ char *codec_name,
+ unsigned bitrate,
+ const char *wav_file,
+ const char *ref_encoded_file)
+{
+ pj_str_t codec_id = pj_str(codec_name);
+ pj_pool_t *pool = NULL;
+ unsigned count, samples_per_frame, encoded_frame_len = 0, pos;
+ pjmedia_codec *codec = NULL;
+ const pjmedia_codec_info *ci[1];
+ pjmedia_codec_param codec_param;
+ pjmedia_port *wav_port = NULL;
+ pjmedia_frame in_frame, out_frame;
+ FILE *output = NULL, *fref = NULL;
+ int rc = 0;
+ pj_status_t status;
+
+ pool = pj_pool_create(mem, "codec-vectors", 512, 512, NULL);
+ if (!pool) {
+ rc = -20;
+ goto on_return;
+ }
+
+ /* Find and open the codec */
+ count = 1;
+ status = pjmedia_codec_mgr_find_codecs_by_id(mgr, &codec_id, &count, ci, NULL);
+ if (status != PJ_SUCCESS) {
+ rc = -30;
+ goto on_return;
+ }
+
+ status = pjmedia_codec_mgr_alloc_codec(mgr, ci[0], &codec);
+ if (status != PJ_SUCCESS) {
+ rc = -40;
+ goto on_return;
+ }
+
+ status = pjmedia_codec_mgr_get_default_param(mgr, ci[0], &codec_param);
+ if (status != PJ_SUCCESS) {
+ rc = -50;
+ goto on_return;
+ }
+
+ codec_param.info.avg_bps = bitrate;
+ codec_param.setting.vad = 0;
+
+ status = pjmedia_codec_init(codec, pool);
+ if (status != PJ_SUCCESS) {
+ rc = -60;
+ goto on_return;
+ }
+
+ status = pjmedia_codec_open(codec, &codec_param);
+ if (status != PJ_SUCCESS) {
+ rc = -70;
+ goto on_return;
+ }
+
+ /* Open WAV file */
+ status = pjmedia_wav_player_port_create(pool, wav_file,
+ codec_param.info.frm_ptime,
+ PJMEDIA_FILE_NO_LOOP, 0,
+ &wav_port);
+ if (status != PJ_SUCCESS) {
+ rc = -80;
+ goto on_return;
+ }
+
+ /* Open output file */
+ output = fopen(TMP_OUT, "wb");
+ if (!output) {
+ rc = -90;
+ goto on_return;
+ }
+
+ /* Allocate buffer for PCM and encoded frames */
+ samples_per_frame = codec_param.info.clock_rate * codec_param.info.frm_ptime / 1000;
+ in_frame.buf = pj_pool_alloc(pool, samples_per_frame * 2);
+ out_frame.buf = (pj_uint8_t*) pj_pool_alloc(pool, samples_per_frame);
+
+ /* Loop read WAV file and encode and write to output file */
+ for (;;) {
+ in_frame.size = samples_per_frame * 2;
+ in_frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+
+ status = pjmedia_port_get_frame(wav_port, &in_frame);
+ if (status != PJ_SUCCESS || in_frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
+ break;
+
+ out_frame.size = samples_per_frame;
+ status = pjmedia_codec_encode(codec, &in_frame, samples_per_frame,
+ &out_frame);
+ if (status != PJ_SUCCESS) {
+ rc = -95;
+ goto on_return;
+ }
+
+ if (out_frame.size) {
+ int cnt;
+
+ cnt = fwrite(out_frame.buf, out_frame.size, 1, output);
+
+ if (encoded_frame_len == 0)
+ encoded_frame_len = out_frame.size;
+ }
+ }
+
+ fclose(output);
+ output = NULL;
+
+ /* Compare encoded files */
+ fref = fopen(ref_encoded_file, "rb");
+ if (!fref) {
+ rc = -100;
+ goto on_return;
+ }
+
+ output = fopen(TMP_OUT, "rb");
+ if (!output) {
+ rc = -110;
+ goto on_return;
+ }
+
+ pos = 0;
+ for (;;) {
+ int count;
+
+ count = fread(in_frame.buf, encoded_frame_len, 1, fref);
+ if (count != 1)
+ break;
+
+ count = fread(out_frame.buf, encoded_frame_len, 1, output);
+ if (count != 1)
+ break;
+
+ if (memcmp(in_frame.buf, out_frame.buf, encoded_frame_len)) {
+ unsigned i;
+ pj_uint8_t *in = (pj_uint8_t*)in_frame.buf;
+ pj_uint8_t *out = (pj_uint8_t*)out_frame.buf;
+
+ for (i=0; i<encoded_frame_len; ++i) {
+ if (in[i] != out[i])
+ break;
+ }
+
+ PJ_LOG(1,(THIS_FILE," failed: mismatch at pos %d", pos+i));
+ rc = -200;
+ break;
+ }
+
+ pos += encoded_frame_len;
+ }
+
+on_return:
+ if (output)
+ fclose(output);
+
+ if (fref)
+ fclose(fref);
+
+ if (codec) {
+ pjmedia_codec_close(codec);
+ pjmedia_codec_mgr_dealloc_codec(mgr, codec);
+ }
+
+ if (wav_port)
+ pjmedia_port_destroy(wav_port);
+
+ if (pool)
+ pj_pool_release(pool);
+
+ return rc;
+}
+
+
+/*
+ * Read file in ITU format (".itu" extension).
+ *
+ * Set swap_endian to TRUE if the ITU file is stored in little
+ * endian format (normally true).
+ */
+static int read_ITU_format(FILE *fp_bitstream,
+ short *out_words,
+ short *p_frame_error_flag,
+ int number_of_16bit_words_per_frame,
+ pj_bool_t swap_endian)
+{
+ enum { MAX_BITS_PER_FRAME = 160*8 };
+ short i,j;
+ short nsamp;
+ short packed_word;
+ short bit_count;
+ short bit;
+ short in_array[MAX_BITS_PER_FRAME+2];
+ short one = 0x0081;
+ short zero = 0x007f;
+ short frame_start = 0x6b21;
+
+ nsamp = (short)fread(in_array, 2, 2 + 16*number_of_16bit_words_per_frame,
+ fp_bitstream);
+
+ j = 0;
+ bit = in_array[j++];
+ if (bit != frame_start) {
+ *p_frame_error_flag = 1;
+ } else {
+ *p_frame_error_flag = 0;
+
+ /* increment j to skip over the number of bits in frame */
+ j++;
+
+ for (i=0; i<number_of_16bit_words_per_frame; i++) {
+ packed_word = 0;
+ bit_count = 15;
+ while (bit_count >= 0) {
+ bit = in_array[j++];
+ if (bit == zero)
+ bit = 0;
+ else if (bit == one)
+ bit = 1;
+ else
+ *p_frame_error_flag = 1;
+
+ packed_word <<= 1;
+ packed_word = (short )(packed_word + bit);
+ bit_count--;
+ }
+
+ if (swap_endian)
+ out_words[i] = pj_ntohs(packed_word);
+ else
+ out_words[i] = packed_word;
+ }
+ }
+ return (nsamp-1)/16;
+}
+
+
+/*
+ * Decode test
+ *
+ * Decode the specified encoded file in "in_encoded_file" into temporary
+ * PCM output file, and compare the temporary PCM output file with
+ * the PCM reference file.
+ *
+ * Some reference file requires manipulation to the PCM output
+ * before comparison, such manipulation can be done by supplying
+ * this function with the "manip" function.
+ */
+static int codec_test_decode(pjmedia_codec_mgr *mgr,
+ char *codec_name,
+ unsigned bitrate,
+ unsigned encoded_len,
+ const char *in_encoded_file,
+ const char *ref_pcm_file,
+ void (*manip)(short *pcm, unsigned count))
+{
+ pj_str_t codec_id = pj_str(codec_name);
+ pj_pool_t *pool = NULL;
+ unsigned count, samples_per_frame, pos;
+ pjmedia_codec *codec = NULL;
+ const pjmedia_codec_info *ci[1];
+ pjmedia_codec_param codec_param;
+ pjmedia_frame out_frame;
+ void *pkt;
+ FILE *input = NULL, *output = NULL, *fref = NULL;
+ pj_bool_t is_itu_format = PJ_FALSE;
+ int rc = 0;
+ pj_status_t status;
+
+ pool = pj_pool_create(mem, "codec-vectors", 512, 512, NULL);
+ if (!pool) {
+ rc = -20;
+ goto on_return;
+ }
+
+ /* Find and open the codec */
+ count = 1;
+ status = pjmedia_codec_mgr_find_codecs_by_id(mgr, &codec_id, &count, ci, NULL);
+ if (status != PJ_SUCCESS) {
+ rc = -30;
+ goto on_return;
+ }
+
+ status = pjmedia_codec_mgr_alloc_codec(mgr, ci[0], &codec);
+ if (status != PJ_SUCCESS) {
+ rc = -40;
+ goto on_return;
+ }
+
+ status = pjmedia_codec_mgr_get_default_param(mgr, ci[0], &codec_param);
+ if (status != PJ_SUCCESS) {
+ rc = -50;
+ goto on_return;
+ }
+
+ codec_param.info.avg_bps = bitrate;
+ codec_param.setting.vad = 0;
+
+ status = pjmedia_codec_init(codec, pool);
+ if (status != PJ_SUCCESS) {
+ rc = -60;
+ goto on_return;
+ }
+
+ status = pjmedia_codec_open(codec, &codec_param);
+ if (status != PJ_SUCCESS) {
+ rc = -70;
+ goto on_return;
+ }
+
+ /* Open input file */
+ input = fopen(in_encoded_file, "rb");
+ if (!input) {
+ rc = -80;
+ goto on_return;
+ }
+
+ /* Is the file in ITU format? */
+ is_itu_format = pj_ansi_stricmp(in_encoded_file+strlen(in_encoded_file)-4,
+ ".itu")==0;
+
+ /* Open output file */
+ output = fopen(TMP_OUT, "wb");
+ if (!output) {
+ rc = -90;
+ goto on_return;
+ }
+
+ /* Allocate buffer for PCM and encoded frames */
+ samples_per_frame = codec_param.info.clock_rate * codec_param.info.frm_ptime / 1000;
+ pkt = pj_pool_alloc(pool, samples_per_frame * 2);
+ out_frame.buf = (pj_uint8_t*) pj_pool_alloc(pool, samples_per_frame * 2);
+
+ /* Loop read WAV file and encode and write to output file */
+ for (;;) {
+ pjmedia_frame in_frame[2];
+ pj_timestamp ts;
+ unsigned count;
+ pj_bool_t has_frame;
+
+ if (is_itu_format) {
+ int nsamp;
+ short frame_err = 0;
+
+ nsamp = read_ITU_format(input, (short*)pkt, &frame_err,
+ encoded_len / 2, PJ_TRUE);
+ if (nsamp != (int)encoded_len / 2)
+ break;
+
+ has_frame = !frame_err;
+ } else {
+ if (fread(pkt, encoded_len, 1, input) != 1)
+ break;
+
+ has_frame = PJ_TRUE;
+ }
+
+ if (has_frame) {
+ count = 2;
+ if (pjmedia_codec_parse(codec, pkt, encoded_len, &ts,
+ &count, in_frame) != PJ_SUCCESS)
+ {
+ rc = -100;
+ goto on_return;
+ }
+
+ if (count != 1) {
+ rc = -110;
+ goto on_return;
+ }
+
+ if (pjmedia_codec_decode(codec, &in_frame[0], samples_per_frame*2,
+ &out_frame) != PJ_SUCCESS)
+ {
+ rc = -120;
+ goto on_return;
+ }
+ } else {
+ if (pjmedia_codec_recover(codec, samples_per_frame*2,
+ &out_frame) != PJ_SUCCESS)
+ {
+ rc = -125;
+ goto on_return;
+ }
+ }
+
+ if (manip)
+ manip((short*)out_frame.buf, samples_per_frame);
+
+ if (fwrite(out_frame.buf, out_frame.size, 1, output) != 1) {
+ rc = -130;
+ goto on_return;
+ }
+ }
+
+ fclose(input);
+ input = NULL;
+
+ fclose(output);
+ output = NULL;
+
+ /* Compare encoded files */
+ fref = fopen(ref_pcm_file, "rb");
+ if (!fref) {
+ rc = -140;
+ goto on_return;
+ }
+
+ output = fopen(TMP_OUT, "rb");
+ if (!output) {
+ rc = -110;
+ goto on_return;
+ }
+
+ pos = 0;
+ for (;;) {
+ int count;
+
+ count = fread(pkt, samples_per_frame*2, 1, fref);
+ if (count != 1)
+ break;
+
+ count = fread(out_frame.buf, samples_per_frame*2, 1, output);
+ if (count != 1)
+ break;
+
+ if (memcmp(pkt, out_frame.buf, samples_per_frame*2)) {
+ unsigned i;
+ pj_int16_t *in = (pj_int16_t*)pkt;
+ pj_int16_t *out = (pj_int16_t*)out_frame.buf;
+
+ for (i=0; i<samples_per_frame; ++i) {
+ if (in[i] != out[i])
+ break;
+ }
+
+ PJ_LOG(1,(THIS_FILE," failed: mismatch at samples %d", pos+i));
+ rc = -200;
+ break;
+ }
+
+ pos += samples_per_frame;
+ }
+
+on_return:
+ if (output)
+ fclose(output);
+
+ if (fref)
+ fclose(fref);
+
+ if (input)
+ fclose(input);
+
+ if (codec) {
+ pjmedia_codec_close(codec);
+ pjmedia_codec_mgr_dealloc_codec(mgr, codec);
+ }
+
+ if (pool)
+ pj_pool_release(pool);
+
+ return rc;
+}
+
+#if PJMEDIA_HAS_G7221_CODEC
+/* For ITU testing, off the 2 lsbs. */
+static void g7221_pcm_manip(short *pcm, unsigned count)
+{
+ unsigned i;
+ for (i=0; i<count; i++)
+ pcm[i] &= 0xfffc;
+
+}
+#endif /* PJMEDIA_HAS_G7221_CODEC */
+
+int codec_test_vectors(void)
+{
+ pjmedia_endpt *endpt;
+ pjmedia_codec_mgr *mgr;
+ int rc, rc_final = 0;
+ struct enc_vectors {
+ char *codec_name;
+ unsigned bit_rate;
+ const char *wav_file;
+ const char *ref_encoded_file;
+ } enc_vectors[] =
+ {
+#if PJMEDIA_HAS_G7221_CODEC
+ { "G7221/16000/1", 24000,
+ "../src/test/vectors/g722_1_enc_in.wav",
+ "../src/test/vectors/g722_1_enc_out_24000_be.pak"
+ },
+ { "G7221/16000/1", 32000,
+ "../src/test/vectors/g722_1_enc_in.wav",
+ "../src/test/vectors/g722_1_enc_out_32000_be.pak"
+ },
+#endif
+ { NULL }
+ };
+ struct dec_vectors {
+ char *codec_name;
+ unsigned bit_rate;
+ unsigned encoded_frame_len;
+ void (*manip)(short *pcm, unsigned count);
+ const char *enc_file;
+ const char *ref_pcm_file;
+ } dec_vectors[] =
+ {
+#if PJMEDIA_HAS_G7221_CODEC
+ { "G7221/16000/1", 24000, 60,
+ &g7221_pcm_manip,
+ "../src/test/vectors/g722_1_enc_out_24000_be.pak",
+ "../src/test/vectors/g722_1_dec_out_24000.pcm"
+ },
+ { "G7221/16000/1", 32000, 80,
+ &g7221_pcm_manip,
+ "../src/test/vectors/g722_1_enc_out_32000_be.pak",
+ "../src/test/vectors/g722_1_dec_out_32000.pcm"
+ },
+ { "G7221/16000/1", 24000, 60,
+ &g7221_pcm_manip,
+ "../src/test/vectors/g722_1_dec_in_24000_fe.itu",
+ "../src/test/vectors/g722_1_dec_out_24000_fe.pcm"
+ },
+ { "G7221/16000/1", 32000, 80,
+ &g7221_pcm_manip,
+ "../src/test/vectors/g722_1_dec_in_32000_fe.itu",
+ "../src/test/vectors/g722_1_dec_out_32000_fe.pcm"
+ },
+#endif
+ { NULL }
+ };
+ unsigned i;
+ pj_status_t status;
+
+ status = pjmedia_endpt_create(mem, NULL, 0, &endpt);
+ if (status != PJ_SUCCESS)
+ return -5;
+
+ mgr = pjmedia_endpt_get_codec_mgr(endpt);
+
+#if PJMEDIA_HAS_G7221_CODEC
+ status = pjmedia_codec_g7221_init(endpt);
+ if (status != PJ_SUCCESS) {
+ pjmedia_endpt_destroy(endpt);
+ return -7;
+ }
+
+ /* Set shift value to zero for the test vectors */
+ pjmedia_codec_g7221_set_pcm_shift(0);
+#endif
+
+ PJ_LOG(3,(THIS_FILE," encode tests:"));
+ for (i=0; i<PJ_ARRAY_SIZE(enc_vectors); ++i) {
+ if (!enc_vectors[i].codec_name)
+ continue;
+ PJ_LOG(3,(THIS_FILE," %s @%d bps %s ==> %s",
+ enc_vectors[i].codec_name,
+ enc_vectors[i].bit_rate,
+ enc_vectors[i].wav_file,
+ enc_vectors[i].ref_encoded_file));
+ rc = codec_test_encode(mgr, enc_vectors[i].codec_name,
+ enc_vectors[i].bit_rate,
+ enc_vectors[i].wav_file,
+ enc_vectors[i].ref_encoded_file);
+ if (rc != 0)
+ rc_final = rc;
+ }
+
+ PJ_LOG(3,(THIS_FILE," decode tests:"));
+ for (i=0; i<PJ_ARRAY_SIZE(dec_vectors); ++i) {
+ if (!dec_vectors[i].codec_name)
+ continue;
+ PJ_LOG(3,(THIS_FILE," %s @%d bps %s ==> %s",
+ dec_vectors[i].codec_name,
+ dec_vectors[i].bit_rate,
+ dec_vectors[i].enc_file,
+ dec_vectors[i].ref_pcm_file));
+ rc = codec_test_decode(mgr, dec_vectors[i].codec_name,
+ dec_vectors[i].bit_rate,
+ dec_vectors[i].encoded_frame_len,
+ dec_vectors[i].enc_file,
+ dec_vectors[i].ref_pcm_file,
+ dec_vectors[i].manip);
+ if (rc != 0)
+ rc_final = rc;
+ }
+
+ if (pj_file_exists(TMP_OUT))
+ pj_file_delete(TMP_OUT);
+
+ pjmedia_endpt_destroy(endpt);
+ return rc_final;
+}
+
diff --git a/pjmedia/src/test/jbuf_test.c b/pjmedia/src/test/jbuf_test.c
new file mode 100644
index 0000000..a292276
--- /dev/null
+++ b/pjmedia/src/test/jbuf_test.c
@@ -0,0 +1,349 @@
+/* $Id: jbuf_test.c 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <stdio.h>
+#include <ctype.h>
+#include <pj/pool.h>
+#include "test.h"
+
+#define JB_INIT_PREFETCH 0
+#define JB_MIN_PREFETCH 0
+#define JB_MAX_PREFETCH 10
+#define JB_PTIME 20
+#define JB_BUF_SIZE 50
+
+//#define REPORT
+//#define PRINT_COMMENT
+
+typedef struct test_param_t {
+ pj_bool_t adaptive;
+ unsigned init_prefetch;
+ unsigned min_prefetch;
+ unsigned max_prefetch;
+} test_param_t;
+
+typedef struct test_cond_t {
+ int burst;
+ int discard;
+ int lost;
+ int empty;
+ int delay; /**< Average delay, in frames. */
+ int delay_min; /**< Minimum delay, in frames. */
+} test_cond_t;
+
+static pj_bool_t parse_test_headers(char *line, test_param_t *param,
+ test_cond_t *cond)
+{
+ char *p = line;
+
+ if (*p == '%') {
+ /* Test params. */
+ char mode_st[16];
+
+ sscanf(p+1, "%s %u %u %u", mode_st, &param->init_prefetch,
+ &param->min_prefetch, &param->max_prefetch);
+ param->adaptive = (pj_ansi_stricmp(mode_st, "adaptive") == 0);
+
+ } else if (*p == '!') {
+ /* Success condition. */
+ char cond_st[16];
+ unsigned cond_val;
+
+ sscanf(p+1, "%s %u", cond_st, &cond_val);
+ if (pj_ansi_stricmp(cond_st, "burst") == 0)
+ cond->burst = cond_val;
+ else if (pj_ansi_stricmp(cond_st, "delay") == 0)
+ cond->delay = cond_val;
+ else if (pj_ansi_stricmp(cond_st, "delay_min") == 0)
+ cond->delay_min = cond_val;
+ else if (pj_ansi_stricmp(cond_st, "discard") == 0)
+ cond->discard = cond_val;
+ else if (pj_ansi_stricmp(cond_st, "empty") == 0)
+ cond->empty = cond_val;
+ else if (pj_ansi_stricmp(cond_st, "lost") == 0)
+ cond->lost = cond_val;
+
+ } else if (*p == '=') {
+ /* Test title. */
+ ++p;
+ while (*p && isspace(*p)) ++p;
+ printf("%s", p);
+
+ } else if (*p == '#') {
+ /* Ignore comment line. */
+
+ } else {
+ /* Unknown header, perhaps this is the test data */
+
+ /* Skip spaces */
+ while (*p && isspace(*p)) ++p;
+
+ /* Test data started.*/
+ if (*p != 0)
+ return PJ_FALSE;
+ }
+
+ return PJ_TRUE;
+}
+
+static pj_bool_t process_test_data(char data, pjmedia_jbuf *jb,
+ pj_uint16_t *seq, pj_uint16_t *last_seq)
+{
+ char frame[1];
+ char f_type;
+ pj_bool_t print_state = PJ_TRUE;
+ pj_bool_t data_eos = PJ_FALSE;
+
+ switch (toupper(data)) {
+ case 'G': /* Get */
+ pjmedia_jbuf_get_frame(jb, frame, &f_type);
+ break;
+ case 'P': /* Put */
+ pjmedia_jbuf_put_frame(jb, (void*)frame, 1, *seq);
+ *last_seq = *seq;
+ ++*seq;
+ break;
+ case 'L': /* Lost */
+ *last_seq = *seq;
+ ++*seq;
+ printf("Lost\n");
+ break;
+ case 'R': /* Sequence restarts */
+ *seq = 1;
+ printf("Sequence restarting, from %u to %u\n", *last_seq, *seq);
+ break;
+ case 'J': /* Sequence jumps */
+ (*seq) += 20;
+ printf("Sequence jumping, from %u to %u\n", *last_seq, *seq);
+ break;
+ case 'D': /* Frame duplicated */
+ pjmedia_jbuf_put_frame(jb, (void*)frame, 1, *seq - 1);
+ break;
+ case 'O': /* Old/late frame */
+ pjmedia_jbuf_put_frame(jb, (void*)frame, 1, *seq - 10 - pj_rand()%40);
+ break;
+ case '.': /* End of test session. */
+ data_eos = PJ_TRUE;
+ break;
+ default:
+ print_state = PJ_FALSE;
+ printf("Unknown test data '%c'\n", data);
+ break;
+ }
+
+ if (data_eos)
+ return PJ_FALSE;
+
+#ifdef REPORT
+ if (print_state) {
+ pjmedia_jb_state state;
+
+ pjmedia_jbuf_get_state(jb, &state);
+ printf("seq=%d\t%c\tsize=%d\tprefetch=%d\n",
+ *last_seq, toupper(data), state.size, state.prefetch);
+ }
+#endif
+
+ return PJ_TRUE;
+}
+
+int jbuf_main(void)
+{
+ FILE *input;
+ pj_bool_t data_eof = PJ_FALSE;
+ int old_log_level;
+ int rc = 0;
+ const char* input_filename = "Jbtest.dat";
+ const char* input_search_path[] = {
+ "../build",
+ "pjmedia/build",
+ "build"
+ };
+
+ /* Try to open test data file in the working directory */
+ input = fopen(input_filename, "rt");
+
+ /* If that fails, try to open test data file in specified search paths */
+ if (input == NULL) {
+ char input_path[PJ_MAXPATH];
+ int i;
+
+ for (i = 0; !input && i < PJ_ARRAY_SIZE(input_search_path); ++i) {
+ pj_ansi_snprintf(input_path, PJ_MAXPATH, "%s/%s",
+ input_search_path[i],
+ input_filename);
+ input = fopen(input_path, "rt");
+ }
+ }
+
+ /* Failed to open test data file. */
+ if (input == NULL) {
+ printf("Failed to open test data file, Jbtest.dat\n");
+ return -1;
+ }
+
+ old_log_level = pj_log_get_level();
+ pj_log_set_level(5);
+
+ while (rc == 0 && !data_eof) {
+ pj_str_t jb_name = {"JBTEST", 6};
+ pjmedia_jbuf *jb;
+ pj_pool_t *pool;
+ pjmedia_jb_state state;
+ pj_uint16_t last_seq = 0;
+ pj_uint16_t seq = 1;
+ char line[1024], *p = NULL;
+
+ test_param_t param;
+ test_cond_t cond;
+
+ param.adaptive = PJ_TRUE;
+ param.init_prefetch = JB_INIT_PREFETCH;
+ param.min_prefetch = JB_MIN_PREFETCH;
+ param.max_prefetch = JB_MAX_PREFETCH;
+
+ cond.burst = -1;
+ cond.delay = -1;
+ cond.delay_min = -1;
+ cond.discard = -1;
+ cond.empty = -1;
+ cond.lost = -1;
+
+ printf("\n\n");
+
+ /* Parse test session title, param, and conditions */
+ do {
+ p = fgets(line, sizeof(line), input);
+ } while (p && parse_test_headers(line, &param, &cond));
+
+ /* EOF test data */
+ if (p == NULL)
+ break;
+
+ //printf("======================================================\n");
+
+ /* Initialize test session */
+ pool = pj_pool_create(mem, "JBPOOL", 256*16, 256*16, NULL);
+ pjmedia_jbuf_create(pool, &jb_name, 1, JB_PTIME, JB_BUF_SIZE, &jb);
+ pjmedia_jbuf_reset(jb);
+
+ if (param.adaptive) {
+ pjmedia_jbuf_set_adaptive(jb,
+ param.init_prefetch,
+ param.min_prefetch,
+ param.max_prefetch);
+ } else {
+ pjmedia_jbuf_set_fixed(jb, param.init_prefetch);
+ }
+
+#ifdef REPORT
+ pjmedia_jbuf_get_state(jb, &state);
+ printf("Initial\tsize=%d\tprefetch=%d\tmin.pftch=%d\tmax.pftch=%d\n",
+ state.size, state.prefetch, state.min_prefetch,
+ state.max_prefetch);
+#endif
+
+
+ /* Test session start */
+ while (1) {
+ char c;
+
+ /* Get next line of test data */
+ if (!p || *p == 0) {
+ p = fgets(line, sizeof(line), input);
+ if (p == NULL) {
+ data_eof = PJ_TRUE;
+ break;
+ }
+ }
+
+ /* Get next char of test data */
+ c = *p++;
+
+ /* Skip spaces */
+ if (isspace(c))
+ continue;
+
+ /* Print comment line */
+ if (c == '#') {
+#ifdef PRINT_COMMENT
+ while (*p && isspace(*p)) ++p;
+ if (*p) printf("..%s", p);
+#endif
+ *p = 0;
+ continue;
+ }
+
+ /* Process test data */
+ if (!process_test_data(c, jb, &seq, &last_seq))
+ break;
+ }
+
+ /* Print JB states */
+ pjmedia_jbuf_get_state(jb, &state);
+ printf("------------------------------------------------------\n");
+ printf("Summary:\n");
+ printf(" size=%d prefetch=%d\n", state.size, state.prefetch);
+ printf(" delay (min/max/avg/dev)=%d/%d/%d/%d ms\n",
+ state.min_delay, state.max_delay, state.avg_delay,
+ state.dev_delay);
+ printf(" lost=%d discard=%d empty=%d burst(avg)=%d\n",
+ state.lost, state.discard, state.empty, state.avg_burst);
+
+ /* Evaluate test session */
+ if (cond.burst >= 0 && (int)state.avg_burst > cond.burst) {
+ printf("! 'Burst' should be %d, it is %d\n",
+ cond.burst, state.avg_burst);
+ rc |= 1;
+ }
+ if (cond.delay >= 0 && (int)state.avg_delay/JB_PTIME > cond.delay) {
+ printf("! 'Delay' should be %d, it is %d\n",
+ cond.delay, state.avg_delay/JB_PTIME);
+ rc |= 2;
+ }
+ if (cond.delay_min >= 0 && (int)state.min_delay/JB_PTIME > cond.delay_min) {
+ printf("! 'Minimum delay' should be %d, it is %d\n",
+ cond.delay_min, state.min_delay/JB_PTIME);
+ rc |= 32;
+ }
+ if (cond.discard >= 0 && (int)state.discard > cond.discard) {
+ printf("! 'Discard' should be %d, it is %d\n",
+ cond.discard, state.discard);
+ rc |= 4;
+ }
+ if (cond.empty >= 0 && (int)state.empty > cond.empty) {
+ printf("! 'Empty' should be %d, it is %d\n",
+ cond.empty, state.empty);
+ rc |= 8;
+ }
+ if (cond.lost >= 0 && (int)state.lost > cond.lost) {
+ printf("! 'Lost' should be %d, it is %d\n",
+ cond.lost, state.lost);
+ rc |= 16;
+ }
+
+ pjmedia_jbuf_destroy(jb);
+ pj_pool_release(pool);
+ }
+
+ fclose(input);
+ pj_log_set_level(old_log_level);
+
+ return rc;
+}
diff --git a/pjmedia/src/test/main.c b/pjmedia/src/test/main.c
new file mode 100644
index 0000000..3f581fa
--- /dev/null
+++ b/pjmedia/src/test/main.c
@@ -0,0 +1,53 @@
+/* $Id: main.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pj/os.h>
+
+#include "test.h"
+
+
+/* Any tests that want to build a linked executable for RTEMS must include
+ this header to get a default config for the network stack. */
+#if defined(PJ_RTEMS)
+# include <bsp.h>
+# include <rtems.h>
+# include <rtems/rtems_bsdnet.h>
+# include "../../../pjlib/include/rtems-network-config.h"
+#endif
+
+static int main_func(int argc, char *argv[])
+{
+ int rc;
+ char s[10];
+
+ rc = test_main();
+
+ if (argc == 2 && argv[1][0]=='-' && argv[1][1]=='i') {
+ puts("\nPress <ENTER> to quit");
+ if (fgets(s, sizeof(s), stdin) == NULL)
+ return rc;
+ }
+
+ return rc;
+}
+
+int main(int argc, char *argv[])
+{
+ return pj_run_app(&main_func, argc, argv, 0);
+}
diff --git a/pjmedia/src/test/mips_test.c b/pjmedia/src/test/mips_test.c
new file mode 100644
index 0000000..b0b146d
--- /dev/null
+++ b/pjmedia/src/test/mips_test.c
@@ -0,0 +1,2511 @@
+/* $Id: mips_test.c 3982 2012-03-22 09:56:52Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 "test.h"
+#include <pjmedia-codec.h>
+
+/* Define your CPU MIPS here!! */
+
+#ifndef CPU_IPS
+ /*
+ For complete table see:
+ http://en.wikipedia.org/wiki/Million_instructions_per_second
+
+ Processor IPS/MHz
+ -------------------------------------------
+ PowerPC G3 2.253 MIPS/MHz
+ Intel Pentium III 2.708 MIPS/MHz
+ AMD Athlon 2.967 MIPS/MHz
+ Pentium 4 Extreme Edition 3.039 MIPS/MHz
+ AMD Athlon FX-57 4.285 MIPS/MHz
+
+
+ From: http://en.wikipedia.org/wiki/ARM_architecture
+
+ Family Arch Core IPS/MHz
+ -------------------------------------------------------
+ ARM7TDMI ARMv4T ARM7TDMI 0.892 MIPS/MHz
+ ARM710T 0.900 MIPS/MHz
+ ARM720T 1.003 MIPS/MHz
+ ARM9TDMI ARMv4T ARM920T 1.111 MIPS/MHz
+ ARM9E ARMv5TEJ ARM926EJ-S 1.100 MIPS/MHz
+ XScale ARMv5TE PXA255 1.000 MIPS/MHz (?)
+ PXA27x 1.282 MIPS/MHz
+ Cortex ARMv7-A Cortex-A8 2.000 MIPS/MHz
+ Cortex-A9 2.000 MIPS/MHz
+ ARMv7-M Cortex-M3 1.250 MIPS/MHz
+ */
+
+# define CPU_MHZ (2666)
+# define CPU_IPS (3.039 * CPU_MHZ * MEGA) /* P4 2.6GHz */
+
+//# define CPU_MHZ 700
+//# define CPU_IPS (700 * MEGA * 2.708) /* P3 700Mhz */
+
+//# define CPU_MHZ 180
+//# define CPU_IPS (CPU_MHZ * MEGA * 1.1) /* ARM926EJ-S */
+
+//# define CPU_MHZ 312
+//# define CPU_IPS (CPU_MHZ * MEGA * 1.282) /* Dell Axim PDA */
+
+#endif
+
+
+
+/* Sample speech data, 360ms length, encoded at 8Khz */
+static const pj_int16_t ref_signal[] = {
+ 0, -4, 0, 0, 1, 8, 8, 7, 12, 16,
+ 20, 29, 28, 48, 40, 48, 56, 76, 68, 76,
+ 96, 100, 104, 124, 117, 120, 144, 120, 136, 168,
+ 184, 188, 176, 216, 237, 235, 268, 301, 312, 316,
+ 367, 356, 384, 400, 344, 409, 392, 416, 380, 432,
+ 404, 444, 457, 456, 453, 512, 499, 512, 584, 584,
+ 544, 584, 608, 596, 552, 628, 600, 667, 728, 672,
+ 681, 755, 736, 764, 752, 764, 724, 769, 792, 840,
+ 820, 895, 841, 856, 852, 867, 944, 944, 939, 944,
+ 907, 928, 920, 960, 833, 881, 1004, 1007, 1000, 1057,
+ 1032, 1056, 1016, 1056, 1072, 1080, 1108, 1028, 1076, 1072,
+ 956, 1020, 1080, 1008, 1095, 992, 1056, 1028, 940, 976,
+ 1008, 940, 916, 968, 849, 868, 956, 940, 852, 892,
+ 968, 860, 777, 904, 825, 716, 764, 708, 635, 728,
+ 620, 648, 472, 724, 548, 448, 472, 372, 272, 437,
+ 419, 260, 237, 371, 196, 136, 177, 264, 49, 120,
+ 40, 124, 32, 136, 71, 120, -95, -20, -76, -140,
+ -304, 12, -148, -168, -192, -63, -212, -36, -288, -232,
+ -352, -128, -397, -308, -431, -280, -675, -497, -761, -336,
+ -760, -471, -1088, 3013, -1596, -2000, 412, -968, 213, -668,
+ -1096, -1048, -1039, -825, -580, -612, -1056, -888, -1324, -1064,
+ -1164, -819, -940, -780, -1236, -688, -1931, -1, -464, -1804,
+ 1088, -1605, -1208, -664, -912, -905, -832, -1167, -512, 1832,
+ -924, -60, 660, -2143, 248, -1660, 1496, -3464, -164, 2072,
+ -3040, 539, 2904, 2040, -3488, 2600, 2412, 820, -551, -1401,
+ 1068, -385, 397, -2112, -980, 1064, -1244, -736, -1335, 332,
+ -1232, -1852, -12, -1073, -1747, -3328, -796, -2241, -4901, -3248,
+ -3124, -3432, -5892, -3840, -3968, -4752, -5668, -4796, -3580, -5909,
+ -5412, -6144, -5800, -5908, -6696, -6460, -8609, -3804, -5668, -8499,
+ -4636, -5744, -2377, -7132, -3712, -7221, -6608, 1656,-11728, -6656,
+ -3736, -204, -8580, -4808, -591, -5752, -472,-10308, -2116, -257,
+ -4720, -7640, -1279, 6412,-16404, -1495, 6204, -8072, -335, -3877,
+ -2363, 464, 441, -6872, 1447, 7884,-13197, 936, 5009, -4416,
+ -4445, 3956, 2280, -2844, 2036, -4285, 744, 4161, -7216, 5548,
+ 172, -964, -2873, 3296, 2184, -7424, 4300, 1855, 1453, -32,
+ 1585, 2160, -3440, 448, 4084, -1617, 1928, 3944, -3728, 4699,
+ 4556, -5556, 4096, 12928, -8287, -4320, 10739, 3172, -6068, 3684,
+ 6484, 1652, -1104, -1820, 11964, -1567, -4117, 7197, 5112, -2608,
+ -2363, 7379, 936, 1596, 217, 6508, 3284, 3960, 0, 2691,
+ 11324, -6140, 6720, 6188, 3596, -1120, 5319, 9420, -9360, 5780,
+ 5135, 623, -1844, 3473, 8488, -4532, 2844, 8368, 4387, -8628,
+ 14180, 3196, -4852, 9092, 5540, -600, 3088, 9864, -4992, 13984,
+ 2536, -5932, 10584, 7044, -2548, 388, 12597, -4776, -247, 7728,
+ 6048, -6152, 6449, 9644, -8924, 8993, 6319, 877, 1108, 9804,
+ -696, 2584, 9097, -3236, 4308, 5444, -2660, -365, 11427, -6900,
+ -803, 9388, -2444, -1068, 9160, 703, -5632, 12088, -2964, -1077,
+ 9804, -1263, -3679, 10161, 3337, -9755, 11601, -160, -6421, 11008,
+ -1952, -3411, 6792, -1665, -1344, 9116, -2545, -4100, 11577, 240,
+ -3612, 5140, 603, -2100, 4284, -784, 108, 968, -1244, 3500,
+ 3696, -108, 3780, 3836, -16, 4035, 2772, 1300, -688, 1544,
+ 2268, -2144, 1156, -564, 628, -1040, -168, 491, -72, -408,
+ -1812, 3460, -2083, -72, 797, 1436, -3824, 1200, 308, -3512,
+ 572, -4060, 540, -84, -4492, -1808, 4644, -4340, -3224, 5832,
+ -2180, -2544, 1475, 2224, -2588, 1312, 1452, -1676, -428, -1596,
+ -860, -116, -4000, -364, 148, -3680, -2229, -1632, 236, -3004,
+ -1917, 124, -1748, -2991, -644, -752, -3691, -1945, -3236, -2005,
+ -4388, -2084, -2052, -3788, -3100, -824, -2432, -3419, -1905, -2520,
+ -2120, -2904, -2200, -1712, -2500, -2796, -1140, -2460, -2955, -984,
+ -1519, -400, -412, 356, 97, -389, 1708, -115, 452, 1140,
+ -820, 1912, 1421, -1604, 556, -632, -1991, -1409, -1904, -3604,
+ -3292, -3405, -5584, -4891, -5436, -8940, -6280, -6604,-11764, -6389,
+ -9081,-10928, -7784, -8492,-11263, -8292, -8829, -9632, -7828, -8920,
+ -10224, -8912, -9836, -7313, -2916,-10240, -3748, 2860, -3308, -1236,
+ 6816, 4580, 1585, 9808, 7484, 5612, 6868, 7399, 6244, 5064,
+ 3823, 5159, 4940, 316, 4496, 4864, 1020, 3997, 6236, 3316,
+ 5712, 7032, 5568, 7329, 6135, 6904, 6431, 3768, 2580, 3724,
+ 504, -2213, -661, -3320, -6660, -6696, -7971,-11208,-11316,-11784,
+ -14212,-13651,-16312,-15876,-15984,-20283,-15168, 2073,-23888, -5839,
+ 13360, -8568, 1240, 18480, 11440, 4236, 23916, 15388, 14072, 15960,
+ 15064, 14840, 9395, 6981, 8584, 6540, -5453, 3568, 928, -7741,
+ -5260, -12, -5692, -7608, 1408, 2136, 776, 1775, 13568, 10992,
+ 8445, 17527, 21084, 14851, 15820, 23060, 15988, 11560, 15088, 14164,
+ 3376, 4059, 5212, -2520, -5891, -3596, -5080,-11752, -8861, -8124,
+ -12104,-12076,-10028,-12333,-14180,-12516,-16036,-15559,-20416, -4240,
+ -1077,-31052, 14840, 7405,-12660, 11896, 23572, 2829, 10932, 28444,
+ 10268, 15412, 13896, 16976, 10161, 6172, 5336, 9639, -2208, -7160,
+ 6544, -7188,-11280, -3308, -2428,-13447, -4880, -824, -6664, -1436,
+ 4608, 7473, 2688, 14275, 14921, 13564, 15960, 20393, 16976, 14832,
+ 17540, 13524, 10744, 6876, 7328, 1772, -2340, -3528, -4516, -9576,
+ -10872, -8640,-13092,-12825,-14076,-12192,-16620,-16207,-17004,-17548,
+ -22088,-21700,-20320, 2836,-29576,-15860, 25811,-22555, -1868, 23624,
+ 9872, -4044, 29472, 16037, 7433, 16640, 14577, 13119, 3120, 7072,
+ 5828, 2285,-12087, 3180, -4031,-17840, -6349, -5300,-15452,-13852,
+ -2659,-12079, -8568, -4492, -672, 660, 5424, 3040, 16488, 11408,
+ 8996, 24976, 15120, 9940, 21400, 16885, 2624, 13939, 8644, -2815,
+ 332, -160, -9312,-10172, -8320,-14033,-13056,-16200,-14113,-15712,
+ -18153,-18664,-15937,-21692,-23500,-18597,-25948, -8597,-10368,-32768,
+ 16916, -4469,-17121, 18448, 14791, -4252, 18880, 22312, 4347, 17672,
+ 12672, 12964, 7384, 5404, 5176, 5668, -7692, -2356, 1148,-14872,
+ -8920, -5593,-12753,-14600, -6429,-10608,-10372, -6757, -4857, -2044,
+ -2720, 8995, 5088, 6516, 18384, 12853, 14952, 18048, 17439, 13920,
+ 15512, 10960, 10268, 5136, 2888, 1184, -4271, -7104, -7376, -9688,
+ -14691,-11336,-14073,-17056,-14268,-16776,-17957,-19460,-18068,-23056,
+ -20512,-24004, -3396,-19239,-27272, 22283,-16439, -7300, 19484, 9020,
+ -1088, 22895, 15868, 9640, 17344, 11443, 17912, 6084, 6712, 9988,
+ 6104, -8559, 6403, -1196,-13152, -3632, -5388,-11924,-11973, -5748,
+ -10292, -8420, -8468, -2256, -2829, -4132, 6344, 8785, 7444, 9508,
+ 22152, 15108, 13151, 22536, 20725, 10672, 17028, 17052, 5536, 6192,
+ 7484, 403, -5520, -2132, -5532,-11527,-10644, -9572,-13316,-16124,
+ -10883,-15965,-17335,-17316,-16064,-20436,-21660, -8547, -3732,-32768,
+ 14672, 2213,-17200, 17964, 14387, 4232, 14800, 24296, 11288, 21368,
+ 11144, 22992, 13599, 6973, 14444, 12312, -2340, 4436, 8060, -9008,
+ -2188, -2164, -5756,-10268, -5076, -6292, -6472, -7916, -2336, 327,
+ -4492, 7144, 7696, 5691, 16352, 14244, 17764, 19852, 17296, 23160,
+ 18496, 14197, 19564, 13356, 5779, 10559, 4576, -2736, -528, -3211,
+ -8796, -8428, -9153,-10928,-13296,-12101,-12528,-14985,-16036,-14600,
+ -15888,-18792,-19604, -3176, -8887,-29240, 21405, -6999, -9568, 19052,
+ 11952, 3037, 20055, 18376, 14501, 18672, 11023, 24776, 9488, 7921,
+ 15896, 11068, -4484, 9667, 4328, -7544, -1240, -1456, -7204, -9192,
+ -5084, -5816, -6864, -9444, 276, -2316, -2852, 4640, 9600, 4412,
+ 13300, 16856, 12836, 18892, 17632, 18336, 16031, 14808, 13860, 12052,
+ 4284, 7372, 2623, -4284, -2172, -5036,-10163, -9788,-10384,-13205,
+ -13180,-13453,-14460,-15540,-16580,-15472,-17961,-19148,-18824, -8063,
+ -8620,-28300, 14323, -6748,-10204, 13100, 10548, 956, 16056, 14640,
+ 12680, 14171, 9672, 19741, 7524, 6615, 11825, 8788, -5080, 7224,
+ 1112, -6024, -4176, -1912, -7968, -8576, -7184, -5640, -8200, -9388,
+ -1980, -2064, -4040, 240, 9327, 2192, 8451, 13604, 13124, 10057,
+ 16505, 15099, 11008, 10344, 12405, 7244, 1948, 4420, 132, -5312,
+ -6072, -5772,-11608,-11756,-11660,-12684,-14335,-14548,-12400,-15268,
+ -15277,-14949,-14889,-17376,-16640,-15656, -1128,-23476, -6084, 7268,
+ -13880, 400, 10984, 1468, 4388, 14156, 6600, 13684, 5428, 12240,
+ 11815, 5460, 3663, 13164, -1269, 772, 3884, -788, -5536, -1652,
+ -4857, -4596, -7912, -6152, -4132, -7201, -6288, -1196, -1332, -4236,
+ 5020, 5020, 1648, 8572, 10224, 6256, 9816, 9404, 8124, 6644,
+ 4380, 4707, 636, -3300, -3208, -4395, -9716, -7540, -8175, -9980,
+ -10237, -7680,-12172, -9981,-10459, -9712, -8451,-13008,-10196, -9308,
+ -13109,-11700,-11636, -6143, -9472,-12117, 985, -3627, -6120, 2828,
+ 5268, 33, 6984, 6488, 7803, 6265, 6992, 8032, 7892, 3408,
+ 6021, 6224, 1016, 2053, 2632, -648, -1936, -1796, -2504, -2865,
+ -4400, -2524, -2388, -2524, -1432, 283, 696, 1180, 2912, 3996,
+ 3328, 3948, 5101, 4905, 3396, 3500, 3323, 2472, -152, 1580,
+ -860, -2109, -1331, -2460, -2960, -3396, -3476, -2616, -5172, -3352,
+ -4036, -4440, -5480, -4028, -4220, -5876, -4656, -5233, -4621, -5465,
+ -6417, -4936, -5092, -1860, -5651, -2699, 1273, -920, -888, 4279,
+ 3260, 2952, 5861, 5584, 5980, 6428, 5732, 6516, 6825, 4156,
+ 5000, 5071, 2669, 1764, 3273, 1148, 1312, 880, 1788, 1457,
+ 1299, 648, 3172, 2004, 1060, 3544, 1963, 2516, 3192, 3057,
+ 2936, 2892, 2896, 2224, 3188, 1524, 1541, 3120, 624, 917,
+ 1472, 1128, -317, 687, 400, -1131, 164, -812, -1232, -1120,
+ -1311, -1364, -1500, -1660, -2380, -1835, -2456, -2468, -2168, -2785,
+ -2824, -2408, -3316, -552, -1204, -3153, 1188, 1572, -752, 1756,
+ 4108, 2344, 3595, 4504, 4152, 4556, 4224, 3568, 4801, 3165,
+ 2776, 2808, 3233, 1300, 2411, 1536, 1828, 1424, 1576, 1412,
+ 1880, 895, 1601, 1916, 1763, 944, 2599, 1349, 1873, 1988,
+ 1744, 1956, 1667, 1548, 1812, 1048, 1528, 488, 1428, 832,
+ 232, 207, 632, -152, -520, 20, 15, -1580, -841, -948,
+ -1120, -1455, -2004, -1244, -1344, -2236, -1312, -1344, -2156, -1420,
+ -1856, -1637, -1847, -1460, -1780, -1372, -508, -1256, -752, 0,
+ 600, 859, 1156, 1532, 2364, 2204, 2059, 2269, 2240, 1928,
+ 1889, 2055, 1205, 1068, 1268, 892, 1371, 1036, 413, 1552,
+ 572, -84, 1364, 260, 624, 820, -160, 1492, 188, 204,
+ 796, 468, 552, 945, -504, 697, -936, 284, -1116, -204,
+ -1052, -700, -1637, -673, -2744, -25, -2163, -1728, -1704, -1260,
+ -2228, -2512, -496, -3992, -824, -2699, -2172, -2196, -1684, -3376,
+ -540, -3047, -2220, -376, -3416, 8, -2424, -428, -1111, -927,
+ 68, -1152, 640, -1308, 276, 536, 592, -292, 2256, -368,
+ 680, 2532, -536, 1548, 780, -92, 1596, 56, 444, 348,
+ 356, 500, -2168, 1527, -1632, -677, -980, -904, -868, 480,
+ -1476, -804, -1515, -335, -2472, -1533, -1007, -644, -2308, -1592,
+ -104, -3860, -984, -2364, 0, -1372, -2500, -2548, 1280, -3767,
+ -2228, -440, -2168, -2691, 628, -2013, -3773, -4292, 3796, -6452,
+ -1268, -156, -1320, -3779, 2612, -2924, -864, -619, 1227, -3408,
+ 3016, -200, -1432, 2219, -45, -1356, 5744, -2069, 4396, 488,
+ 3048, -801, 3876, 857, 2064, 80, 4240, -700, 1928, 1568,
+ -1992, 3431, 600, 2221, 657, -3116, 5888, -2668, 4871, -7963,
+ 8016, -4907, 1264, -1969, 3688, -4396, -1276, 2448, -3760, 2156,
+ -3039, -3064, 940, 2384, -7907, 4520, -2876, 2384, -5784, 4484,
+ -5487, 1907, -4064, 1991, -3496, 1304, -4521, 5255, -4189, 1136,
+ -2397, -152, 768, -1671, 2084, -2709, 6413, -1460, 1952, 448,
+ 7064, -444, 6256, 600, 8872, 2115, 4672, 7628, 6264, 2993,
+ 8920, 2876, 7744, 3956, 4848, 7236, 804, 7684, 5201, 2216,
+ 6360, 4900, -340, 6885, 1307, 2468, 1884, 4812, -1376, 4740,
+ 1751, 135, 1124, 3284, -3228, 3968, 1748, -4453, 1587, -1515,
+ 3084, -3300, -2548, -544, -296, 396, -7808, 4956, -5164, -292,
+ -4947, 212, -4055, -108, -4301, -2088, -2912, 3016, 952, -1888,
+ 4972, 4441, 6136, 1464, 9387, 4137, 6812, 6281, 2440, 6940,
+ 3928, 2704, -1204, 4260, -2289, -712, -1768, -383, -1195, 920,
+ -1200, -336, 4372, 720, 4392, 1291, 5819, 4528, 7532, 992,
+ 8176, 5588, 2760, 2836, 3412, 1132, 531, 2236, -5044, 621,
+ -2916, -3964, -5849, -864, -6300,-10019, -3964, -5524, -7004, -6833,
+ -7236, -9484, -2528,-10112,-12900, -4199, -8580,-12427,-10924, -8604,
+ -11520, -9636, -6560, -1647, -6948, -460, 1752, 2952, 4196, 4360,
+ 4215, 8156, 4528, 2464, 2500, 3299, -2224, -3812, -2568, -5875,
+ -5556, -7728, -8288, -5765, -6448, -7620, -5288, -2680, -4368, -972,
+ 472, 1716, 2467, 4408, 5141, 4724, 7316, 4132, 3493, 5935,
+ 3564, 96, 1068, 868, -2160, -2736, -3449, -5428, -3339, -5200,
+ -7156, -4189, -7928, -8064, -7532, -7999,-12124, -8509, -9888,-12420,
+ -13568,-13187,-15384,-14996,-15152,-15284,-17059,-15292,-11792, -1160,
+ -7300, -8284, 7237, 7241, 1616, 6327, 12064, 7920, 9564, 3556,
+ 4612, 6980, 261, -6365, -2028, -1701,-10136, -9573, -6901, -7747,
+ -7868, -8076, -6123, -1508, -100, -3048, 2004, 6704, 4507, 3256,
+ 9432, 8672, 7673, 6804, 7632, 8777, 6908, 3332, 3771, 6552,
+ 3153, 400, 3029, 4388, 1328, 160, 2304, 2023, 1325, -2640,
+ -2356, -1544, -3436, -8584, -6939, -7180,-10455,-12928,-12296,-14653,
+ -15243,-16436,-15240,-16672,-15476,-14628, 7004, -1360,-10100, 16344,
+ 18300, 9108, 12869, 22541, 16119, 17856, 10697, 6720, 12128, 6904,
+ -8184, -3440, 2592,-10440,-11735, -4739, -4455, -5457, -2432, -1476,
+ 4520, 10045, 5512, 7988, 17032, 15052, 9211, 13309, 14624, 10324,
+ 10488, 7809, 6908, 9896, 5861, 3284, 8348, 10505, 5189, 8144,
+ 13280, 11732, 10035, 12559, 12104, 12456, 10148, 6520, 5944, 5603,
+ -1848, -4196, -2544, -5876,-11416,-10032,-10248,-12753,-13344,-14900,
+ -14320,-11265,-14220,-17067, -1440, 20120, -9884, 2783, 32220, 22208,
+ 9032, 22661, 26820, 19916, 17747, 5288, 8628, 14293, -3331,-15672,
+ 1252, -324,-18236,-11592, -1172, -3384, -3864, 1052, 3640, 13099,
+ 13691, 6520, 14320, 22856, 12887, 7152, 14764, 13276, 4060, 2568,
+ 2268, 2224, 3312, -3336, -875, 9000, 6180, 1872, 10851, 17464,
+ 12312, 11197, 15388, 17816, 12024, 8332, 7119, 8096, 1608, -5611,
+ -5964, -4729,-11317,-14784,-12833,-11272,-14888,-16128,-15012,-12028,
+ -14472,-16227,-15356,-14484,-15056, 11496, 352,-14108, 19216, 24616,
+ 3724, 7872, 25948, 13832, 9680, 7492, 2052, 5220, 1188,-16112,
+ -11340, 703,-15400,-21572, -5816, -3320,-12072, -5664, 2296, 3101,
+ 6708, 5396, 5735, 13601, 12040, 1924, 6071, 10420, 984, -4904,
+ -204, -1945, -6229, -7460, -5636, 2864, -476, -2832, 6104, 13160,
+ 7151, 7148, 13063, 13596, 8796, 5092, 5976, 5668, -431, -7624,
+ -6741, -5676,-14332,-18700,-13396,-12387,-18576,-17516,-14184,-14124,
+ -15972,-17456,-16323,-14712,-18056,-23213,-10744, 12016,-14824,-12636,
+ 21656, 14112, -4085, 9255, 20864, 8196, 6384, 1223, 2244, 5304,
+ -6660,-19192, -4961, -2875,-22564,-18400, -3220, -8488,-14544, -5040,
+ -324, 820, 2732, 628, 5484, 11924, 4813, -852, 8656, 7160,
+ -3924, -2955, 1337, -3268, -7359, -2552, -2528, -532, 128, 411,
+ 5324, 9301, 5601, 6200, 11684, 10072, 4924, 5508, 6660, 1568,
+ -2332, -4268, -5628, -7987,-12004,-13760,-11567,-12104,-16539,-14437,
+ -12012,-14309,-16736,-14573,-13604,-15468,-18204,-19103, -9140, 10132,
+ -13631, -9568, 22580, 13756, -3548, 12112, 23891, 8144, 5964, 7240,
+ 7216, 4284, -4800,-11761, -1308, -3044,-19584,-13808, -759, -7968,
+ -14524, -1503, 3072, -396, 1936, 5900, 9264, 10769, 7240, 5961,
+ 13112, 8788, 660, 2807, 7980, -449, -2477, 3940, 2792, 1584,
+ 2791, 5603, 7528, 9692, 5924, 9123, 15240, 9636, 4924, 11044,
+ 11113, 956, 756, 2812, -1832, -6920, -7120, -7192, -7711, -9717,
+ -12704, -8736, -7508,-12067,-13176, -8133, -9304,-13160,-13437,-13268,
+ -4084, 11400,-12785, -700, 24992, 12168, -1268, 19404, 25183, 8373,
+ 10256, 13664, 11200, 5879, -60, -3656, 4556, -2972,-14688, -4932,
+ 2432, -9279,-10691, 4280, 3180, -2444, 4088, 9992, 9176, 9156,
+ 9520, 11164, 14484, 8608, 4919, 10556, 9792, 2740, 3456, 8840,
+ 6424, 2348, 5696, 9420, 6596, 5380, 8364, 10952, 8499, 6800,
+ 8728, 9641, 5412, 2340, 3596, 2039, -2864, -5489, -3616, -5596,
+ -9232, -8744, -7788, -9860,-11104, -9356, -9464,-11188,-11312,-11036,
+ -11736,-13564, -6016, 8744,-11784, -1196, 18972, 9512, -572, 17407,
+ 20316, 7472, 9784, 13369, 8952, 5092, 1003, -2004, 2755, -3952,
+ -12761, -4648, -744,-11667,-10240, 1556, -1572, -5872, 2196, 6011,
+ 3900, 5384, 7529, 8924, 9629, 6324, 5744, 9484, 7829, 3420,
+ 4384, 8644, 4360, 1500, 5248, 5921, 2200, 2564, 5212, 5037,
+ 2849, 2836, 3985, 3952, 875, -560, 416, -1052, -5228, -5185,
+ -4996, -7820, -9616, -9076,-10644,-11239,-11816,-12360,-12228,-12420,
+ -13560,-12940,-13044,-15648,-11664, 1945, -9676, -9088, 9676, 6708,
+ -3048, 8185, 15520, 4620, 5764, 10716, 6584, 2684, 2276, -1436,
+ -56, -2948, -9140, -6611, -2868, -9897,-10565, -2012, -3948, -7916,
+ -1440, 2420, -241, 1164, 4428, 4932, 5461, 3884, 4476, 6620,
+ 7724, 1779, 3172, 8256, 3132, -749, 5192, 4300, -1388, 1192,
+ 3575, 789, -228, 1185, 995, 937, -952, -2624, -449, -1992,
+ -6204, -4648, -3000, -7604, -8536, -5868, -9024,-10507,-10064, -9296,
+ -12896,-11120,-11776,-13288,-14137,-12668,-15780,-14157, -8392, -7444,
+ -11156, -2300, 2828, -1747, 1164, 8152, 6280, 4876, 7912, 7604,
+ 5609, 5164, 2600, 1620, 1592, -3237, -4441, -2068, -5052, -8268,
+ -4503, -3304, -6332, -4460, -388, -297, -319, 1911, 4071, 4272,
+ 4659, 8368, 6933, 6720, 8764, 8640, 6412, 6384, 5927, 3820,
+ 3488, 2648, 1104, 1220, 884, -692, 327, 616, -972, -160,
+ 713, -593, -652, 179, -651, -2005, -656, -1536, -2968, -3748,
+ -2640, -5052, -5548, -3476, -6151, -6388, -5168, -6099, -7416, -5752,
+ -7579, -8220, -8312, -8472, -5287, -8056, -3527, -2356, -1704, 1892,
+ 2408, 2893, 5965, 8121, 5136, 8480, 8928, 7364, 6408, 7960,
+ 4315, 4392, 3864, 1353, 928, 1436, -1480, -488, 1640, -380,
+ -36, 3420, 4044, 4432, 5185, 8044, 8740, 7983, 7912, 9588,
+ 8588, 6804, 6944, 6700, 4308, 2852, 3252, 2192, -136, 876,
+ 1008, 244, 160, 205, 992, 1684, -136, 984, 3312, 853,
+ -772, 2372, 436, -3008, -1024, -136, -3800, -2263, -3212, -2749,
+ -3688, -2424, -5372, -2136, -3288, -4952, -3596, -2028, -4640, -5797,
+ -2696, -4040, -7152, -4055, -2568, -6460, -4228, -1092, -2780, -2492,
+ 468, -235, 1620, 3500, 2040, 2840, 6300, 4488, 2488, 5707,
+ 5576, 3537, 2291, 4301, 2844, 3364, 1153, 2500, 3340, 3160,
+ 1224, 3220, 4016, 2228, 1788, 4199, 3604, 2096, 1763, 3237,
+ 2044, -564, 1280, 876, -584, -1904, 24, -60, -2948, -1440,
+ -1228, -1824, -2092, -1945, -3912, -227, -2411, -3219, -2252, -1808,
+ -3044, -1035, -3092, -1456, -3724, -2284, -3149, -3028, -2788, -1804,
+ -3360, -1276, -4097, -2531, -2248, -1635, -3215, -2376, -2468, -2596,
+ -2825, -2792, -1980, -4036, -1721, -2059, -4117, 364, -1452, -2772,
+ -1336, 480, -1043, 244, -2904, 924, -1329, 968, -1891, 523,
+ -624, -464, -564, 187, -852, 584, -764, -260, -147, 160,
+ 339, -32, 936, -896, 288, 136, 56, -36, -736, -683,
+ -332, 696, -2319, -259, 564, -2196, -860, 1108, -2177, -728,
+ 1344, -2520, -440, 1080, -780, -3513, 3272, -1635, -1597, -188,
+ 744, -1944, 140, -636, -1644, -141, -596, -1132, -816, 1168,
+ -2836, 196, 312, 136, -1381, 628, -223, -368, -425, 604,
+ -776, 595, -628, -128, -884, 960, -1092, 76, 144, 8,
+ 161, -504, 760, -808, 336, 185, 100, 404, 120, 236,
+ 68, -148, -64, 312, 320, -560, 117, -28, 236, -231,
+ -92, 60, 356, -176, 176, 212, 124, -57, -76, 168,
+ 88, -140, -37, 160, 0, -92, 96, 24, -84, 0,
+};
+
+#define THIS_FILE "mips_test.c"
+#define DURATION 5000
+#define PTIME 20 /* MUST be 20! */
+#define MEGA 1000000
+#define GIGA 1000000000
+
+enum op
+{
+ OP_GET = 1,
+ OP_PUT = 2,
+ OP_GET_PUT = 4,
+ OP_PUT_GET = 8
+};
+
+enum clock_rate
+{
+ K8 = 1,
+ K16 = 2,
+ K32 = 4,
+};
+
+
+struct test_entry
+{
+ const char *title;
+ unsigned valid_op;
+ unsigned valid_clock_rate;
+
+ pjmedia_port* (*init)(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *);
+ void (*custom_run)(struct test_entry*);
+ void (*custom_deinit)(struct test_entry*);
+
+ void *pdata[4];
+ unsigned idata[4];
+};
+
+
+/***************************************************************************/
+/* pjmedia_port to supply with continuous frames */
+static pjmedia_port* create_gen_port(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned pct_level)
+{
+ pjmedia_port *port;
+ pj_status_t status;
+
+ if (pct_level == 100 && channel_count==1) {
+ status = pjmedia_mem_player_create(pool, ref_signal,
+ sizeof(ref_signal), clock_rate,
+ channel_count, samples_per_frame,
+ 16, 0, &port);
+ } else {
+ pj_int16_t *buf;
+ unsigned c;
+
+ buf = (pj_int16_t*)
+ pj_pool_alloc(pool, sizeof(ref_signal)*channel_count);
+ for (c=0; c<channel_count; ++c) {
+ unsigned i;
+ pj_int16_t *p;
+
+ p = buf+c;
+ for (i=0; i<PJ_ARRAY_SIZE(ref_signal); ++i) {
+ *p = (pj_int16_t)(ref_signal[i] * pct_level / 100);
+ p += channel_count;
+ }
+ }
+ status = pjmedia_mem_player_create(pool, buf,
+ sizeof(ref_signal)*channel_count,
+ clock_rate, channel_count,
+ samples_per_frame,
+ 16, 0, &port);
+ }
+
+ return status==PJ_SUCCESS? port : NULL;
+}
+
+/***************************************************************************/
+static pjmedia_port* gen_port_test_init(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ PJ_UNUSED_ARG(flags);
+ PJ_UNUSED_ARG(te);
+ return create_gen_port(pool, clock_rate, channel_count, samples_per_frame,
+ 100);
+}
+
+
+/***************************************************************************/
+static pjmedia_port* init_conf_port(unsigned nb_participant,
+ pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ pjmedia_conf *conf;
+ unsigned i;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(flags);
+ PJ_UNUSED_ARG(te);
+
+ /* Create conf */
+ status = pjmedia_conf_create(pool, 2+nb_participant*2, clock_rate,
+ channel_count, samples_per_frame, 16,
+ PJMEDIA_CONF_NO_DEVICE, &conf);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ for (i=0; i<nb_participant; ++i) {
+ pjmedia_port *gen_port, *null_port;
+ unsigned slot1, slot2;
+
+ /* Create gen_port for source audio */
+ gen_port = create_gen_port(pool, clock_rate, channel_count,
+ samples_per_frame, 100 / nb_participant);
+ if (!gen_port)
+ return NULL;
+
+ /* Add port */
+ status = pjmedia_conf_add_port(conf, pool, gen_port, NULL, &slot1);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ /* Connect gen_port to sound dev */
+ status = pjmedia_conf_connect_port(conf, slot1, 0, 0);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ /* Create null sink frame */
+ status = pjmedia_null_port_create(pool, clock_rate, channel_count,
+ samples_per_frame, 16, &null_port);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ /* add null port */
+ status = pjmedia_conf_add_port(conf, pool, null_port, NULL, &slot2);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ /* connect sound to null sink port */
+ status = pjmedia_conf_connect_port(conf, 0, slot2, 0);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ /* connect gen_port to null sink port */
+#if 0
+ status = pjmedia_conf_connect_port(conf, slot1, slot2, 0);
+ if (status != PJ_SUCCESS)
+ return NULL;
+#endif
+ }
+
+ return pjmedia_conf_get_master_port(conf);
+}
+
+
+/***************************************************************************/
+/* Benchmark conf with 1 participant, no mixing */
+static pjmedia_port* conf1_test_init(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return init_conf_port(1, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+
+/***************************************************************************/
+/* Benchmark conf with 2 participants, mixing to/from snd dev */
+static pjmedia_port* conf2_test_init(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return init_conf_port(2, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/***************************************************************************/
+/* Benchmark conf with 4 participants, mixing to/from snd dev */
+static pjmedia_port* conf4_test_init(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return init_conf_port(4, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/***************************************************************************/
+/* Benchmark conf with 8 participants, mixing to/from snd dev */
+static pjmedia_port* conf8_test_init(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return init_conf_port(8, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/***************************************************************************/
+/* Benchmark conf with 16 participants, mixing to/from snd dev */
+static pjmedia_port* conf16_test_init(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ PJ_UNUSED_ARG(flags);
+ return init_conf_port(16, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/***************************************************************************/
+/* Up and downsample */
+static pjmedia_port* updown_resample_get(pj_pool_t *pool,
+ pj_bool_t high_quality,
+ pj_bool_t large_filter,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ pjmedia_port *gen_port, *up, *down;
+ unsigned opt = 0;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(flags);
+ PJ_UNUSED_ARG(te);
+
+ if (!high_quality)
+ opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
+ if (!large_filter)
+ opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
+
+ gen_port = create_gen_port(pool, clock_rate, channel_count,
+ samples_per_frame, 100);
+ status = pjmedia_resample_port_create(pool, gen_port, clock_rate*2, opt, &up);
+ if (status != PJ_SUCCESS)
+ return NULL;
+ status = pjmedia_resample_port_create(pool, up, clock_rate, opt, &down);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ return down;
+}
+
+/* Linear resampling */
+static pjmedia_port* linear_resample( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return updown_resample_get(pool, PJ_FALSE, PJ_FALSE, clock_rate,
+ channel_count, samples_per_frame, flags, te);
+}
+
+/* Small filter resampling */
+static pjmedia_port* small_filt_resample( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return updown_resample_get(pool, PJ_TRUE, PJ_FALSE, clock_rate,
+ channel_count, samples_per_frame, flags, te);
+}
+
+/* Larger filter resampling */
+static pjmedia_port* large_filt_resample( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return updown_resample_get(pool, PJ_TRUE, PJ_TRUE, clock_rate,
+ channel_count, samples_per_frame, flags, te);
+}
+
+
+/***************************************************************************/
+/* Codec encode/decode */
+
+struct codec_port
+{
+ pjmedia_port base;
+ pjmedia_endpt *endpt;
+ pjmedia_codec *codec;
+ pj_status_t (*codec_deinit)();
+ pj_uint8_t pkt[640];
+ pj_uint16_t pcm[32000 * PTIME / 1000];
+};
+
+
+static pj_status_t codec_put_frame(struct pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ struct codec_port *cp = (struct codec_port*)this_port;
+ pjmedia_frame out_frame;
+ pj_status_t status;
+
+ out_frame.buf = cp->pkt;
+ out_frame.size = sizeof(cp->pkt);
+ status = pjmedia_codec_encode(cp->codec, frame, sizeof(cp->pkt),
+ &out_frame);
+ pj_assert(status == PJ_SUCCESS);
+
+ if (out_frame.size != 0) {
+ pjmedia_frame parsed_frm[2], pcm_frm;
+ unsigned frame_cnt = PJ_ARRAY_SIZE(parsed_frm);
+ unsigned i;
+
+ status = pjmedia_codec_parse(cp->codec, out_frame.buf,
+ out_frame.size, &out_frame.timestamp,
+ &frame_cnt, parsed_frm);
+ pj_assert(status == PJ_SUCCESS);
+
+ for (i=0; i<frame_cnt; ++i) {
+ pcm_frm.buf = cp->pcm;
+ pcm_frm.size = sizeof(cp->pkt);
+ status = pjmedia_codec_decode(cp->codec, &parsed_frm[i],
+ sizeof(cp->pcm), &pcm_frm);
+ pj_assert(status == PJ_SUCCESS);
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t codec_on_destroy(struct pjmedia_port *this_port)
+{
+ struct codec_port *cp = (struct codec_port*)this_port;
+
+ pjmedia_codec_close(cp->codec);
+ pjmedia_codec_mgr_dealloc_codec(pjmedia_endpt_get_codec_mgr(cp->endpt),
+ cp->codec);
+ cp->codec_deinit();
+ pjmedia_endpt_destroy(cp->endpt);
+ return PJ_SUCCESS;
+}
+
+static pjmedia_port* codec_encode_decode( pj_pool_t *pool,
+ const char *codec,
+ pj_status_t (*codec_init)(pjmedia_endpt*),
+ pj_status_t (*codec_deinit)(),
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ struct codec_port *cp;
+ pj_str_t codec_id;
+ const pjmedia_codec_info *ci[1];
+ unsigned count;
+ pjmedia_codec_param codec_param;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(flags);
+ PJ_UNUSED_ARG(te);
+
+ codec_id = pj_str((char*)codec);
+ cp = PJ_POOL_ZALLOC_T(pool, struct codec_port);
+ pjmedia_port_info_init(&cp->base.info, &codec_id, 0x123456, clock_rate,
+ channel_count, 16, samples_per_frame);
+ cp->base.put_frame = &codec_put_frame;
+ cp->base.on_destroy = &codec_on_destroy;
+ cp->codec_deinit = codec_deinit;
+
+ status = pjmedia_endpt_create(mem, NULL, 0, &cp->endpt);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ status = codec_init(cp->endpt);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ count = 1;
+ status = pjmedia_codec_mgr_find_codecs_by_id(pjmedia_endpt_get_codec_mgr(cp->endpt),
+ &codec_id, &count, ci, NULL);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ status = pjmedia_codec_mgr_alloc_codec(pjmedia_endpt_get_codec_mgr(cp->endpt),
+ ci[0], &cp->codec);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ status = pjmedia_codec_mgr_get_default_param(pjmedia_endpt_get_codec_mgr(cp->endpt),
+ ci[0], &codec_param);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ status = pjmedia_codec_init(cp->codec, pool);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ status = pjmedia_codec_open(cp->codec, &codec_param);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ return &cp->base;
+}
+
+#if PJMEDIA_HAS_G711_CODEC
+/* G.711 benchmark */
+static pjmedia_port* g711_encode_decode( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return codec_encode_decode(pool, "pcmu", &pjmedia_codec_g711_init,
+ &pjmedia_codec_g711_deinit,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+#endif
+
+/* GSM benchmark */
+#if PJMEDIA_HAS_GSM_CODEC
+static pjmedia_port* gsm_encode_decode( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return codec_encode_decode(pool, "gsm", &pjmedia_codec_gsm_init,
+ &pjmedia_codec_gsm_deinit,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+#endif
+
+#if PJMEDIA_HAS_ILBC_CODEC
+static pj_status_t ilbc_init(pjmedia_endpt *endpt)
+{
+ return pjmedia_codec_ilbc_init(endpt, 20);
+}
+
+/* iLBC benchmark */
+static pjmedia_port* ilbc_encode_decode( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ samples_per_frame = 30 * clock_rate / 1000;
+ return codec_encode_decode(pool, "ilbc", &ilbc_init,
+ &pjmedia_codec_ilbc_deinit, clock_rate,
+ channel_count, samples_per_frame, flags, te);
+}
+#endif
+
+#if PJMEDIA_HAS_SPEEX_CODEC
+/* Speex narrowband benchmark */
+static pjmedia_port* speex8_encode_decode(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return codec_encode_decode(pool, "speex/8000",
+ &pjmedia_codec_speex_init_default,
+ &pjmedia_codec_speex_deinit,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* Speex wideband benchmark */
+static pjmedia_port* speex16_encode_decode(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return codec_encode_decode(pool, "speex/16000",
+ &pjmedia_codec_speex_init_default,
+ &pjmedia_codec_speex_deinit,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+#endif
+
+#if PJMEDIA_HAS_G722_CODEC
+/* G.722 benchmark benchmark */
+static pjmedia_port* g722_encode_decode(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return codec_encode_decode(pool, "g722", &pjmedia_codec_g722_init,
+ &pjmedia_codec_g722_deinit,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+#endif
+
+#if PJMEDIA_HAS_G7221_CODEC
+/* G.722.1 benchmark benchmark */
+static pjmedia_port* g7221_encode_decode(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return codec_encode_decode(pool, "g7221/16000",
+ &pjmedia_codec_g7221_init,
+ &pjmedia_codec_g7221_deinit,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* G.722.1 Annex C benchmark benchmark */
+static pjmedia_port* g7221c_encode_decode(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return codec_encode_decode(pool, "g7221/32000",
+ &pjmedia_codec_g7221_init,
+ &pjmedia_codec_g7221_deinit,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+#endif /* PJMEDIA_HAS_G7221_CODEC */
+
+#if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC
+/* AMR-NB benchmark benchmark */
+static pjmedia_port* amr_encode_decode(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return codec_encode_decode(pool, "AMR/8000",
+ &pjmedia_codec_opencore_amrnb_init,
+ &pjmedia_codec_opencore_amrnb_deinit,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+#endif /* PJMEDIA_HAS_OPENCORE_AMRNB_CODEC */
+
+#if defined(PJMEDIA_HAS_L16_CODEC) && PJMEDIA_HAS_L16_CODEC!=0
+static pj_status_t init_l16_default(pjmedia_endpt *endpt)
+{
+ return pjmedia_codec_l16_init(endpt, 0);
+}
+
+/* L16/8000/1 benchmark */
+static pjmedia_port* l16_8_encode_decode(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return codec_encode_decode(pool, "L16/8000/1", &init_l16_default,
+ &pjmedia_codec_l16_deinit,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* L16/16000/1 benchmark */
+static pjmedia_port* l16_16_encode_decode(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return codec_encode_decode(pool, "L16/16000/1", &init_l16_default,
+ &pjmedia_codec_l16_deinit,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+#endif
+
+/***************************************************************************/
+/* WSOLA PLC mode */
+
+struct wsola_plc_port
+{
+ pjmedia_port base;
+ pjmedia_wsola *wsola;
+ pjmedia_port *gen_port;
+ int loss_pct;
+ pj_bool_t prev_lost;
+};
+
+
+static pj_status_t wsola_plc_get_frame(struct pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ struct wsola_plc_port *wp = (struct wsola_plc_port*)this_port;
+ pj_status_t status;
+
+
+ if ((pj_rand() % 100) > wp->loss_pct) {
+ status = pjmedia_port_get_frame(wp->gen_port, frame);
+ pj_assert(status == PJ_SUCCESS);
+
+ status = pjmedia_wsola_save(wp->wsola, (short*)frame->buf,
+ wp->prev_lost);
+ pj_assert(status == PJ_SUCCESS);
+
+ wp->prev_lost = PJ_FALSE;
+ } else {
+ status = pjmedia_wsola_generate(wp->wsola, (short*)frame->buf);
+ wp->prev_lost = PJ_TRUE;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t wsola_plc_on_destroy(struct pjmedia_port *this_port)
+{
+ struct wsola_plc_port *wp = (struct wsola_plc_port*)this_port;
+ pjmedia_port_destroy(wp->gen_port);
+ pjmedia_wsola_destroy(wp->wsola);
+ return PJ_SUCCESS;
+}
+
+static pjmedia_port* create_wsola_plc(unsigned loss_pct,
+ pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ struct wsola_plc_port *wp;
+ pj_str_t name = pj_str("wsola");
+ unsigned opt = 0;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(flags);
+ PJ_UNUSED_ARG(te);
+
+ wp = PJ_POOL_ZALLOC_T(pool, struct wsola_plc_port);
+ wp->loss_pct = loss_pct;
+ wp->base.get_frame = &wsola_plc_get_frame;
+ wp->base.on_destroy = &wsola_plc_on_destroy;
+ pjmedia_port_info_init(&wp->base.info, &name, 0x4123, clock_rate,
+ channel_count, 16, samples_per_frame);
+
+ if (loss_pct == 0)
+ opt |= PJMEDIA_WSOLA_NO_PLC;
+
+ status = pjmedia_wsola_create(pool, clock_rate, samples_per_frame,
+ channel_count, 0, &wp->wsola);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ wp->gen_port = create_gen_port(pool, clock_rate, channel_count,
+ samples_per_frame, 100);
+ if (wp->gen_port == NULL)
+ return NULL;
+
+ return &wp->base;
+}
+
+
+/* WSOLA PLC with 0% packet loss */
+static pjmedia_port* wsola_plc_0( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_wsola_plc(0, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+
+/* WSOLA PLC with 2% packet loss */
+static pjmedia_port* wsola_plc_2( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_wsola_plc(2, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* WSOLA PLC with 5% packet loss */
+static pjmedia_port* wsola_plc_5( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_wsola_plc(5, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* WSOLA PLC with 10% packet loss */
+static pjmedia_port* wsola_plc_10(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_wsola_plc(10, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* WSOLA PLC with 20% packet loss */
+static pjmedia_port* wsola_plc_20(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_wsola_plc(20, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* WSOLA PLC with 50% packet loss */
+static pjmedia_port* wsola_plc_50(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_wsola_plc(50, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+
+
+/***************************************************************************/
+/* WSOLA discard mode */
+enum { CIRC_BUF_FRAME_CNT = 4 };
+struct wsola_discard_port
+{
+ pjmedia_port base;
+ pjmedia_port *gen_port;
+ pjmedia_wsola *wsola;
+ pjmedia_circ_buf *circbuf;
+ unsigned discard_pct;
+};
+
+
+static pj_status_t wsola_discard_get_frame(struct pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ struct wsola_discard_port *wp = (struct wsola_discard_port*)this_port;
+ pj_status_t status;
+
+ while (pjmedia_circ_buf_get_len(wp->circbuf) <
+ PJMEDIA_PIA_SPF(&wp->base.info) * (CIRC_BUF_FRAME_CNT-1))
+ {
+ status = pjmedia_port_get_frame(wp->gen_port, frame);
+ pj_assert(status==PJ_SUCCESS);
+
+ status = pjmedia_circ_buf_write(wp->circbuf, (short*)frame->buf,
+ PJMEDIA_PIA_SPF(&wp->base.info));
+ pj_assert(status==PJ_SUCCESS);
+ }
+
+ if ((pj_rand() % 100) < (int)wp->discard_pct) {
+ pj_int16_t *reg1, *reg2;
+ unsigned reg1_len, reg2_len;
+ unsigned del_cnt;
+
+ pjmedia_circ_buf_get_read_regions(wp->circbuf, &reg1, &reg1_len,
+ &reg2, &reg2_len);
+
+ del_cnt = PJMEDIA_PIA_SPF(&wp->base.info);
+ status = pjmedia_wsola_discard(wp->wsola, reg1, reg1_len, reg2,
+ reg2_len, &del_cnt);
+ pj_assert(status==PJ_SUCCESS);
+
+ status = pjmedia_circ_buf_adv_read_ptr(wp->circbuf, del_cnt);
+ pj_assert(status==PJ_SUCCESS);
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t wsola_discard_on_destroy(struct pjmedia_port *this_port)
+{
+ struct wsola_discard_port *wp = (struct wsola_discard_port*)this_port;
+ pjmedia_port_destroy(wp->gen_port);
+ pjmedia_wsola_destroy(wp->wsola);
+ return PJ_SUCCESS;
+}
+
+
+static pjmedia_port* create_wsola_discard(unsigned discard_pct,
+ pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ struct wsola_discard_port *wp;
+ pj_str_t name = pj_str("wsola");
+ unsigned i, opt = 0;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(flags);
+ PJ_UNUSED_ARG(te);
+
+ wp = PJ_POOL_ZALLOC_T(pool, struct wsola_discard_port);
+ wp->discard_pct = discard_pct;
+ wp->base.get_frame = &wsola_discard_get_frame;
+ wp->base.on_destroy = &wsola_discard_on_destroy;
+ pjmedia_port_info_init(&wp->base.info, &name, 0x4123, clock_rate,
+ channel_count, 16, samples_per_frame);
+
+ if (discard_pct == 0)
+ opt |= PJMEDIA_WSOLA_NO_DISCARD;
+
+ status = pjmedia_wsola_create(pool, clock_rate, samples_per_frame,
+ channel_count, 0, &wp->wsola);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ wp->gen_port = create_gen_port(pool, clock_rate, channel_count,
+ samples_per_frame, 100);
+ if (wp->gen_port == NULL)
+ return NULL;
+
+ status = pjmedia_circ_buf_create(pool, samples_per_frame * CIRC_BUF_FRAME_CNT,
+ &wp->circbuf);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ /* fill up the circbuf */
+ for (i=0; i<CIRC_BUF_FRAME_CNT; ++i) {
+ short pcm[320];
+ pjmedia_frame frm;
+
+ pj_assert(PJ_ARRAY_SIZE(pcm) >= samples_per_frame);
+ frm.buf = pcm;
+ frm.size = samples_per_frame * 2;
+
+ status = pjmedia_port_get_frame(wp->gen_port, &frm);
+ pj_assert(status==PJ_SUCCESS);
+
+ status = pjmedia_circ_buf_write(wp->circbuf, pcm, samples_per_frame);
+ pj_assert(status==PJ_SUCCESS);
+ }
+
+ return &wp->base;
+}
+
+
+/* WSOLA with 2% discard rate */
+static pjmedia_port* wsola_discard_2( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_wsola_discard(2, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* WSOLA with 5% discard rate */
+static pjmedia_port* wsola_discard_5( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_wsola_discard(5, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* WSOLA with 10% discard rate */
+static pjmedia_port* wsola_discard_10(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_wsola_discard(10, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* WSOLA with 20% discard rate */
+static pjmedia_port* wsola_discard_20(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_wsola_discard(20, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* WSOLA with 50% discard rate */
+static pjmedia_port* wsola_discard_50(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_wsola_discard(50, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+
+
+/***************************************************************************/
+
+static pjmedia_port* ec_create(unsigned ec_tail_msec,
+ pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ pjmedia_port *gen_port, *ec_port;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(te);
+
+ gen_port = create_gen_port(pool, clock_rate, channel_count,
+ samples_per_frame, 100);
+ if (gen_port == NULL)
+ return NULL;
+
+ status = pjmedia_echo_port_create(pool, gen_port, ec_tail_msec, 0,
+ flags, &ec_port);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ return ec_port;
+}
+
+/* EC with 100ms tail length */
+static pjmedia_port* ec_create_100(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ flags = 0;
+ return ec_create(100, pool, clock_rate, channel_count, samples_per_frame,
+ flags, te);
+}
+
+/* EC with 128ms tail length */
+static pjmedia_port* ec_create_128(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ flags = 0;
+ return ec_create(128, pool, clock_rate, channel_count, samples_per_frame,
+ flags, te);
+}
+
+/* EC with 200ms tail length */
+static pjmedia_port* ec_create_200(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ flags = 0;
+ return ec_create(200, pool, clock_rate, channel_count, samples_per_frame,
+ flags, te);
+}
+
+/* EC with 256ms tail length */
+static pjmedia_port* ec_create_256(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ flags = 0;
+ return ec_create(256, pool, clock_rate, channel_count, samples_per_frame,
+ flags, te);
+}
+
+
+/* EC with 400ms tail length */
+static pjmedia_port* ec_create_400(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ flags = 0;
+ return ec_create(400, pool, clock_rate, channel_count, samples_per_frame,
+ flags, te);
+}
+
+/* EC with 500ms tail length */
+static pjmedia_port* ec_create_500(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ flags = 0;
+ return ec_create(500, pool, clock_rate, channel_count, samples_per_frame,
+ flags, te);
+}
+
+/* EC with 512ms tail length */
+static pjmedia_port* ec_create_512(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ flags = 0;
+ return ec_create(512, pool, clock_rate, channel_count, samples_per_frame,
+ flags, te);
+}
+
+/* EC with 600ms tail length */
+static pjmedia_port* ec_create_600(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ flags = 0;
+ return ec_create(600, pool, clock_rate, channel_count, samples_per_frame,
+ flags, te);
+}
+
+/* EC with 800ms tail length */
+static pjmedia_port* ec_create_800(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ flags = 0;
+ return ec_create(800, pool, clock_rate, channel_count, samples_per_frame,
+ flags, te);
+}
+
+
+
+/* Echo suppressor with 100ms tail length */
+static pjmedia_port* es_create_100(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ flags = PJMEDIA_ECHO_SIMPLE;
+ return ec_create(100, pool, clock_rate, channel_count, samples_per_frame,
+ flags, te);
+}
+
+/* Echo suppressor with 128ms tail length */
+static pjmedia_port* es_create_128(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ flags = PJMEDIA_ECHO_SIMPLE;
+ return ec_create(128, pool, clock_rate, channel_count, samples_per_frame,
+ flags, te);
+}
+
+/* Echo suppressor with 200ms tail length */
+static pjmedia_port* es_create_200(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ flags = PJMEDIA_ECHO_SIMPLE;
+ return ec_create(200, pool, clock_rate, channel_count, samples_per_frame,
+ flags, te);
+}
+
+/* Echo suppressor with 256ms tail length */
+static pjmedia_port* es_create_256(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ flags = PJMEDIA_ECHO_SIMPLE;
+ return ec_create(256, pool, clock_rate, channel_count, samples_per_frame,
+ flags, te);
+}
+
+
+/* Echo suppressor with 400ms tail length */
+static pjmedia_port* es_create_400(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ flags = PJMEDIA_ECHO_SIMPLE;
+ return ec_create(400, pool, clock_rate, channel_count, samples_per_frame,
+ flags, te);
+}
+
+/* Echo suppressor with 500ms tail length */
+static pjmedia_port* es_create_500(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ flags = PJMEDIA_ECHO_SIMPLE;
+ return ec_create(500, pool, clock_rate, channel_count, samples_per_frame,
+ flags, te);
+}
+
+/* Echo suppressor with 512ms tail length */
+static pjmedia_port* es_create_512(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ flags = PJMEDIA_ECHO_SIMPLE;
+ return ec_create(512, pool, clock_rate, channel_count, samples_per_frame,
+ flags, te);
+}
+
+/* Echo suppressor with 600ms tail length */
+static pjmedia_port* es_create_600(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ flags = PJMEDIA_ECHO_SIMPLE;
+ return ec_create(600, pool, clock_rate, channel_count, samples_per_frame,
+ flags, te);
+}
+
+/* Echo suppressor with 800ms tail length */
+static pjmedia_port* es_create_800(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ flags = PJMEDIA_ECHO_SIMPLE;
+ return ec_create(800, pool, clock_rate, channel_count, samples_per_frame,
+ flags, te);
+}
+
+
+/***************************************************************************/
+/* Tone generator, single frequency */
+static pjmedia_port* create_tonegen(unsigned freq1,
+ unsigned freq2,
+ pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ pjmedia_port *tonegen;
+ pjmedia_tone_desc tones[2];
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(flags);
+ PJ_UNUSED_ARG(te);
+
+ status = pjmedia_tonegen_create(pool, clock_rate, channel_count,
+ samples_per_frame, 16,
+ PJMEDIA_TONEGEN_LOOP, &tonegen);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ pj_bzero(tones, sizeof(tones));
+ tones[0].freq1 = (short)freq1;
+ tones[0].freq2 = (short)freq2;
+ tones[0].on_msec = 400;
+ tones[0].off_msec = 0;
+ tones[1].freq1 = (short)freq1;
+ tones[1].freq2 = (short)freq2;
+ tones[1].on_msec = 400;
+ tones[1].off_msec = 100;
+
+ status = pjmedia_tonegen_play(tonegen, PJ_ARRAY_SIZE(tones), tones,
+ PJMEDIA_TONEGEN_LOOP);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ return tonegen;
+}
+
+/* Tonegen with single frequency */
+static pjmedia_port* create_tonegen1(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_tonegen(400, 0, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* Tonegen with dual frequency */
+static pjmedia_port* create_tonegen2(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_tonegen(400, 440, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+
+
+/***************************************************************************/
+/* Stream */
+
+struct stream_port
+{
+ pjmedia_port base;
+ pj_status_t (*codec_deinit)();
+ pjmedia_endpt *endpt;
+ pjmedia_stream *stream;
+ pjmedia_transport *transport;
+};
+
+
+static void stream_port_custom_deinit(struct test_entry *te)
+{
+ struct stream_port *sp = (struct stream_port*) te->pdata[0];
+
+ pjmedia_stream_destroy(sp->stream);
+ pjmedia_transport_close(sp->transport);
+ sp->codec_deinit();
+ pjmedia_endpt_destroy(sp->endpt);
+
+}
+
+static pjmedia_port* create_stream( pj_pool_t *pool,
+ const char *codec,
+ pj_status_t (*codec_init)(pjmedia_endpt*),
+ pj_status_t (*codec_deinit)(),
+ pj_bool_t srtp_enabled,
+ pj_bool_t srtp_80,
+ pj_bool_t srtp_auth,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ struct stream_port *sp;
+ pj_str_t codec_id;
+ pjmedia_port *port;
+ const pjmedia_codec_info *ci[1];
+ unsigned count;
+ pjmedia_codec_param codec_param;
+ pjmedia_stream_info si;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(flags);
+
+ codec_id = pj_str((char*)codec);
+ sp = PJ_POOL_ZALLOC_T(pool, struct stream_port);
+ pjmedia_port_info_init(&sp->base.info, &codec_id, 0x123456, clock_rate,
+ channel_count, 16, samples_per_frame);
+
+ te->pdata[0] = sp;
+ te->custom_deinit = &stream_port_custom_deinit;
+ sp->codec_deinit = codec_deinit;
+
+ status = pjmedia_endpt_create(mem, NULL, 0, &sp->endpt);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ status = codec_init(sp->endpt);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ count = 1;
+ status = pjmedia_codec_mgr_find_codecs_by_id(pjmedia_endpt_get_codec_mgr(sp->endpt),
+ &codec_id, &count, ci, NULL);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+
+
+ status = pjmedia_codec_mgr_get_default_param(pjmedia_endpt_get_codec_mgr(sp->endpt),
+ ci[0], &codec_param);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ /* Create stream info */
+ pj_bzero(&si, sizeof(si));
+ si.type = PJMEDIA_TYPE_AUDIO;
+ si.proto = PJMEDIA_TP_PROTO_RTP_AVP;
+ si.dir = PJMEDIA_DIR_ENCODING_DECODING;
+ pj_sockaddr_in_init(&si.rem_addr.ipv4, NULL, 4000);
+ pj_sockaddr_in_init(&si.rem_rtcp.ipv4, NULL, 4001);
+ pj_memcpy(&si.fmt, ci[0], sizeof(pjmedia_codec_info));
+ si.param = NULL;
+ si.tx_pt = ci[0]->pt;
+ si.tx_event_pt = 101;
+ si.rx_event_pt = 101;
+ si.ssrc = pj_rand();
+ si.jb_init = si.jb_min_pre = si.jb_max_pre = si.jb_max = -1;
+
+ /* Create loop transport */
+ status = pjmedia_transport_loop_create(sp->endpt, &sp->transport);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+#if PJMEDIA_HAS_SRTP
+ if (srtp_enabled) {
+ pjmedia_srtp_setting opt;
+ pjmedia_srtp_crypto crypto;
+ pjmedia_transport *srtp;
+
+ pjmedia_srtp_setting_default(&opt);
+ opt.close_member_tp = PJ_TRUE;
+ opt.use = PJMEDIA_SRTP_MANDATORY;
+
+ status = pjmedia_transport_srtp_create(sp->endpt, sp->transport, &opt,
+ &srtp);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ pj_bzero(&crypto, sizeof(crypto));
+ if (srtp_80) {
+ crypto.key = pj_str("123456789012345678901234567890");
+ crypto.name = pj_str("AES_CM_128_HMAC_SHA1_80");
+ } else {
+ crypto.key = pj_str("123456789012345678901234567890");
+ crypto.name = pj_str("AES_CM_128_HMAC_SHA1_32");
+ }
+
+ if (!srtp_auth)
+ crypto.flags = PJMEDIA_SRTP_NO_AUTHENTICATION;
+
+ status = pjmedia_transport_srtp_start(srtp, &crypto, &crypto);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ sp->transport = srtp;
+ }
+#endif
+
+ /* Create stream */
+ status = pjmedia_stream_create(sp->endpt, pool, &si, sp->transport, NULL,
+ &sp->stream);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ /* Start stream */
+ status = pjmedia_stream_start(sp->stream);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ status = pjmedia_stream_get_port(sp->stream, &port);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ return port;
+}
+
+#if PJMEDIA_HAS_G711_CODEC
+/* G.711 stream, no SRTP */
+static pjmedia_port* create_stream_pcmu( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_stream(pool, "pcmu", &pjmedia_codec_g711_init,
+ &pjmedia_codec_g711_deinit,
+ PJ_FALSE, PJ_FALSE, PJ_FALSE,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* G.711 stream, SRTP 32bit key no auth */
+static pjmedia_port* create_stream_pcmu_srtp32_no_auth( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_stream(pool, "pcmu", &pjmedia_codec_g711_init,
+ &pjmedia_codec_g711_deinit,
+ PJ_TRUE, PJ_FALSE, PJ_FALSE,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* G.711 stream, SRTP 32bit key with auth */
+static pjmedia_port* create_stream_pcmu_srtp32_with_auth(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_stream(pool, "pcmu", &pjmedia_codec_g711_init,
+ &pjmedia_codec_g711_deinit,
+ PJ_TRUE, PJ_FALSE, PJ_TRUE,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* G.711 stream, SRTP 80bit key no auth */
+static pjmedia_port* create_stream_pcmu_srtp80_no_auth( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_stream(pool, "pcmu", &pjmedia_codec_g711_init,
+ &pjmedia_codec_g711_deinit,
+ PJ_TRUE, PJ_TRUE, PJ_FALSE,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* G.711 stream, SRTP 80bit key with auth */
+static pjmedia_port* create_stream_pcmu_srtp80_with_auth(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_stream(pool, "pcmu", &pjmedia_codec_g711_init,
+ &pjmedia_codec_g711_deinit,
+ PJ_TRUE, PJ_TRUE, PJ_TRUE,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+#endif
+
+#if PJMEDIA_HAS_GSM_CODEC
+/* GSM stream */
+static pjmedia_port* create_stream_gsm( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_stream(pool, "gsm", &pjmedia_codec_gsm_init,
+ &pjmedia_codec_gsm_deinit,
+ PJ_FALSE, PJ_FALSE, PJ_FALSE,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* GSM stream, SRTP 32bit, no auth */
+static pjmedia_port* create_stream_gsm_srtp32_no_auth(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_stream(pool, "gsm", &pjmedia_codec_gsm_init,
+ &pjmedia_codec_gsm_deinit,
+ PJ_TRUE, PJ_FALSE, PJ_FALSE,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* GSM stream, SRTP 32bit, with auth */
+static pjmedia_port* create_stream_gsm_srtp32_with_auth(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_stream(pool, "gsm", &pjmedia_codec_gsm_init,
+ &pjmedia_codec_gsm_deinit,
+ PJ_TRUE, PJ_FALSE, PJ_TRUE,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* GSM stream, SRTP 80bit, no auth */
+static pjmedia_port* create_stream_gsm_srtp80_no_auth(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_stream(pool, "gsm", &pjmedia_codec_gsm_init,
+ &pjmedia_codec_gsm_deinit,
+ PJ_TRUE, PJ_TRUE, PJ_FALSE,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* GSM stream, SRTP 80bit, with auth */
+static pjmedia_port* create_stream_gsm_srtp80_with_auth(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_stream(pool, "gsm", &pjmedia_codec_gsm_init,
+ &pjmedia_codec_gsm_deinit,
+ PJ_TRUE, PJ_TRUE, PJ_TRUE,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+#endif
+
+#if PJMEDIA_HAS_G722_CODEC
+/* G722 stream */
+static pjmedia_port* create_stream_g722( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_stream(pool, "g722", &pjmedia_codec_g722_init,
+ &pjmedia_codec_g722_deinit,
+ PJ_FALSE, PJ_FALSE, PJ_FALSE,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+#endif
+
+#if PJMEDIA_HAS_G7221_CODEC
+/* G722.1 stream */
+static pjmedia_port* create_stream_g7221( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_stream(pool, "g7221/16000", &pjmedia_codec_g7221_init,
+ &pjmedia_codec_g7221_deinit,
+ PJ_FALSE, PJ_FALSE, PJ_FALSE,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* G722.1 Annex C stream */
+static pjmedia_port* create_stream_g7221c( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_stream(pool, "g7221/32000", &pjmedia_codec_g7221_init,
+ &pjmedia_codec_g7221_deinit,
+ PJ_FALSE, PJ_FALSE, PJ_FALSE,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+#endif /* PJMEDIA_HAS_G7221_CODEC */
+
+/* AMR-NB stream */
+#if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC
+static pjmedia_port* create_stream_amr( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_stream(pool, "AMR/8000", &pjmedia_codec_opencore_amrnb_init,
+ &pjmedia_codec_opencore_amrnb_deinit,
+ PJ_FALSE, PJ_FALSE, PJ_FALSE,
+ clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+#endif /* PJMEDIA_HAS_OPENCORE_AMRNB_CODEC */
+
+/***************************************************************************/
+/* Delay buffer */
+enum {DELAY_BUF_MAX_DELAY = 80};
+struct delaybuf_port
+{
+ pjmedia_port base;
+ pjmedia_delay_buf *delaybuf;
+ pjmedia_port *gen_port;
+ int drift_pct;
+};
+
+
+static pj_status_t delaybuf_get_frame(struct pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ struct delaybuf_port *dp = (struct delaybuf_port*)this_port;
+ pj_status_t status;
+
+ status = pjmedia_delay_buf_get(dp->delaybuf, (pj_int16_t*)frame->buf);
+ pj_assert(status == PJ_SUCCESS);
+
+ /* Additional GET when drift_pct is negative */
+ if (dp->drift_pct < 0) {
+ int rnd;
+ rnd = pj_rand() % 100;
+
+ if (rnd < -dp->drift_pct) {
+ status = pjmedia_delay_buf_get(dp->delaybuf, (pj_int16_t*)frame->buf);
+ pj_assert(status == PJ_SUCCESS);
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t delaybuf_put_frame(struct pjmedia_port *this_port,
+ pjmedia_frame *frame)
+{
+ struct delaybuf_port *dp = (struct delaybuf_port*)this_port;
+ pj_status_t status;
+ pjmedia_frame f = *frame;
+
+ status = pjmedia_port_get_frame(dp->gen_port, &f);
+ pj_assert(status == PJ_SUCCESS);
+ status = pjmedia_delay_buf_put(dp->delaybuf, (pj_int16_t*)f.buf);
+ pj_assert(status == PJ_SUCCESS);
+
+ /* Additional PUT when drift_pct is possitive */
+ if (dp->drift_pct > 0) {
+ int rnd;
+ rnd = pj_rand() % 100;
+
+ if (rnd < dp->drift_pct) {
+ status = pjmedia_port_get_frame(dp->gen_port, &f);
+ pj_assert(status == PJ_SUCCESS);
+ status = pjmedia_delay_buf_put(dp->delaybuf, (pj_int16_t*)f.buf);
+ pj_assert(status == PJ_SUCCESS);
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t delaybuf_on_destroy(struct pjmedia_port *this_port)
+{
+ struct delaybuf_port *dp = (struct delaybuf_port*)this_port;
+ pjmedia_port_destroy(dp->gen_port);
+ pjmedia_delay_buf_destroy(dp->delaybuf);
+ return PJ_SUCCESS;
+}
+
+static pjmedia_port* create_delaybuf(int drift_pct,
+ pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ struct delaybuf_port *dp;
+ pj_str_t name = pj_str("delaybuf");
+ unsigned opt = 0;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(flags);
+ PJ_UNUSED_ARG(te);
+
+ dp = PJ_POOL_ZALLOC_T(pool, struct delaybuf_port);
+ dp->drift_pct = drift_pct;
+ dp->base.get_frame = &delaybuf_get_frame;
+ dp->base.put_frame = &delaybuf_put_frame;
+ dp->base.on_destroy = &delaybuf_on_destroy;
+ pjmedia_port_info_init(&dp->base.info, &name, 0x5678, clock_rate,
+ channel_count, 16, samples_per_frame);
+
+ status = pjmedia_delay_buf_create(pool, "mips_test", clock_rate,
+ samples_per_frame, channel_count,
+ DELAY_BUF_MAX_DELAY,
+ opt, &dp->delaybuf);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ dp->gen_port = create_gen_port(pool, clock_rate, channel_count,
+ samples_per_frame, 100);
+ if (dp->gen_port == NULL)
+ return NULL;
+
+ return &dp->base;
+}
+
+
+/* Delay buffer without drift */
+static pjmedia_port* delaybuf_0( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_delaybuf(0, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+
+/* Delay buffer with 2% drift */
+static pjmedia_port* delaybuf_p2( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_delaybuf(2, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* Delay buffer with 5% drift */
+static pjmedia_port* delaybuf_p5( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_delaybuf(5, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* Delay buffer with 10% drift */
+static pjmedia_port* delaybuf_p10(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_delaybuf(10, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* Delay buffer with 20% drift */
+static pjmedia_port* delaybuf_p20(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_delaybuf(20, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* Delay buffer with -2% drift */
+static pjmedia_port* delaybuf_n2( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_delaybuf(-2, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* Delay buffer with -5% drift */
+static pjmedia_port* delaybuf_n5( pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_delaybuf(-5, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* Delay buffer with -10% drift */
+static pjmedia_port* delaybuf_n10(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_delaybuf(-10, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+/* Delay buffer with -20% drift */
+static pjmedia_port* delaybuf_n20(pj_pool_t *pool,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned flags,
+ struct test_entry *te)
+{
+ return create_delaybuf(-20, pool, clock_rate, channel_count,
+ samples_per_frame, flags, te);
+}
+
+
+/***************************************************************************/
+/* Run test entry, return elapsed time */
+static pj_timestamp run_entry(unsigned clock_rate, struct test_entry *e)
+{
+ pj_pool_t *pool;
+ pjmedia_port *port;
+ pj_timestamp t0, t1;
+ unsigned j, samples_per_frame;
+ pj_int16_t pcm[32000 * PTIME / 1000];
+ pjmedia_port *gen_port;
+ pj_status_t status;
+
+ samples_per_frame = clock_rate * PTIME / 1000;
+
+ pool = pj_pool_create(mem, "pool", 1024, 1024, NULL);
+ port = e->init(pool, clock_rate, 1, samples_per_frame, 0, e);
+ if (port == NULL) {
+ t0.u64 = 0;
+ pj_pool_release(pool);
+ PJ_LOG(1,(THIS_FILE, " init error"));
+ return t0;
+ }
+
+ /* Port may decide to use different ptime (e.g. iLBC) */
+ samples_per_frame = PJMEDIA_PIA_SPF(&port->info);
+
+ gen_port = create_gen_port(pool, clock_rate, 1,
+ samples_per_frame, 100);
+ if (gen_port == NULL) {
+ t0.u64 = 0;
+ pj_pool_release(pool);
+ return t0;
+ }
+
+ pj_get_timestamp(&t0);
+ for (j=0; j<DURATION*clock_rate/samples_per_frame/1000; ++j) {
+ pjmedia_frame frm;
+
+ if (e->valid_op==OP_GET_PUT) {
+ frm.buf = (void*)pcm;
+ frm.size = samples_per_frame * 2;
+ frm.type = PJMEDIA_FRAME_TYPE_NONE;
+
+ status = pjmedia_port_get_frame(port, &frm);
+ pj_assert(status == PJ_SUCCESS);
+
+ status = pjmedia_port_put_frame(port, &frm);
+ pj_assert(status == PJ_SUCCESS);
+
+ } else if (e->valid_op == OP_GET) {
+ frm.buf = (void*)pcm;
+ frm.size = samples_per_frame * 2;
+ frm.type = PJMEDIA_FRAME_TYPE_NONE;
+
+ status = pjmedia_port_get_frame(port, &frm);
+ pj_assert(status == PJ_SUCCESS);
+
+ } else if (e->valid_op == OP_PUT) {
+ frm.buf = (void*)pcm;
+ frm.size = samples_per_frame * 2;
+ frm.type = PJMEDIA_FRAME_TYPE_NONE;
+
+ status = pjmedia_port_get_frame(gen_port, &frm);
+ pj_assert(status == PJ_SUCCESS);
+
+ status = pjmedia_port_put_frame(port, &frm);
+ pj_assert(status == PJ_SUCCESS);
+
+ } else if (e->valid_op == OP_PUT_GET) {
+ frm.buf = (void*)pcm;
+ frm.size = samples_per_frame * 2;
+ frm.type = PJMEDIA_FRAME_TYPE_NONE;
+
+ status = pjmedia_port_get_frame(gen_port, &frm);
+ pj_assert(status == PJ_SUCCESS);
+
+ status = pjmedia_port_put_frame(port, &frm);
+ pj_assert(status == PJ_SUCCESS);
+
+ status = pjmedia_port_get_frame(port, &frm);
+ pj_assert(status == PJ_SUCCESS);
+ }
+ }
+ pj_get_timestamp(&t1);
+
+ pj_sub_timestamp(&t1, &t0);
+
+ if (e->custom_deinit)
+ e->custom_deinit(e);
+
+ pjmedia_port_destroy(port);
+ pj_pool_release(pool);
+
+ return t1;
+}
+
+/***************************************************************************/
+int mips_test(void)
+{
+ struct test_entry entries[] = {
+ { "get from memplayer", OP_GET, K8|K16, &gen_port_test_init},
+ { "conference bridge with 1 call", OP_GET_PUT, K8|K16, &conf1_test_init},
+ { "conference bridge with 2 calls", OP_GET_PUT, K8|K16, &conf2_test_init},
+ { "conference bridge with 4 calls", OP_GET_PUT, K8|K16, &conf4_test_init},
+ { "conference bridge with 8 calls", OP_GET_PUT, K8|K16, &conf8_test_init},
+ { "conference bridge with 16 calls", OP_GET_PUT, K8|K16, &conf16_test_init},
+ { "upsample+downsample - linear", OP_GET, K8|K16, &linear_resample},
+ { "upsample+downsample - small filter", OP_GET, K8|K16, &small_filt_resample},
+ { "upsample+downsample - large filter", OP_GET, K8|K16, &large_filt_resample},
+ { "WSOLA PLC - 0% loss", OP_GET, K8|K16, &wsola_plc_0},
+ { "WSOLA PLC - 2% loss", OP_GET, K8|K16, &wsola_plc_2},
+ { "WSOLA PLC - 5% loss", OP_GET, K8|K16, &wsola_plc_5},
+ { "WSOLA PLC - 10% loss", OP_GET, K8|K16, &wsola_plc_10},
+ { "WSOLA PLC - 20% loss", OP_GET, K8|K16, &wsola_plc_20},
+ { "WSOLA PLC - 50% loss", OP_GET, K8|K16, &wsola_plc_50},
+ { "WSOLA discard 2% excess", OP_GET, K8|K16, &wsola_discard_2},
+ { "WSOLA discard 5% excess", OP_GET, K8|K16, &wsola_discard_5},
+ { "WSOLA discard 10% excess", OP_GET, K8|K16, &wsola_discard_10},
+ { "WSOLA discard 20% excess", OP_GET, K8|K16, &wsola_discard_20},
+ { "WSOLA discard 50% excess", OP_GET, K8|K16, &wsola_discard_50},
+ { "Delay buffer", OP_GET_PUT, K8|K16, &delaybuf_0},
+ { "Delay buffer - drift -2%", OP_GET_PUT, K8|K16, &delaybuf_n2},
+ { "Delay buffer - drift -5%", OP_GET_PUT, K8|K16, &delaybuf_n5},
+ { "Delay buffer - drift -10%", OP_GET_PUT, K8|K16, &delaybuf_n10},
+ { "Delay buffer - drift -20%", OP_GET_PUT, K8|K16, &delaybuf_n20},
+ { "Delay buffer - drift +2%", OP_GET_PUT, K8|K16, &delaybuf_p2},
+ { "Delay buffer - drift +5%", OP_GET_PUT, K8|K16, &delaybuf_p5},
+ { "Delay buffer - drift +10%", OP_GET_PUT, K8|K16, &delaybuf_p10},
+ { "Delay buffer - drift +20%", OP_GET_PUT, K8|K16, &delaybuf_p20},
+ { "echo canceller 100ms tail len", OP_GET_PUT, K8|K16, &ec_create_100},
+ { "echo canceller 128ms tail len", OP_GET_PUT, K8|K16, &ec_create_128},
+ { "echo canceller 200ms tail len", OP_GET_PUT, K8|K16, &ec_create_200},
+ { "echo canceller 256ms tail len", OP_GET_PUT, K8|K16, &ec_create_256},
+ { "echo canceller 400ms tail len", OP_GET_PUT, K8|K16, &ec_create_400},
+ { "echo canceller 500ms tail len", OP_GET_PUT, K8|K16, &ec_create_500},
+ { "echo canceller 512ms tail len", OP_GET_PUT, K8|K16, &ec_create_512},
+ { "echo canceller 600ms tail len", OP_GET_PUT, K8|K16, &ec_create_600},
+ { "echo canceller 800ms tail len", OP_GET_PUT, K8|K16, &ec_create_800},
+ { "echo suppressor 100ms tail len", OP_GET_PUT, K8|K16, &es_create_100},
+ { "echo suppressor 128ms tail len", OP_GET_PUT, K8|K16, &es_create_128},
+ { "echo suppressor 200ms tail len", OP_GET_PUT, K8|K16, &es_create_200},
+ { "echo suppressor 256ms tail len", OP_GET_PUT, K8|K16, &es_create_256},
+ { "echo suppressor 400ms tail len", OP_GET_PUT, K8|K16, &es_create_400},
+ { "echo suppressor 500ms tail len", OP_GET_PUT, K8|K16, &es_create_500},
+ { "echo suppressor 512ms tail len", OP_GET_PUT, K8|K16, &es_create_512},
+ { "echo suppressor 600ms tail len", OP_GET_PUT, K8|K16, &es_create_600},
+ { "echo suppressor 800ms tail len", OP_GET_PUT, K8|K16, &es_create_800},
+ { "tone generator with single freq", OP_GET, K8|K16, &create_tonegen1},
+ { "tone generator with dual freq", OP_GET, K8|K16, &create_tonegen2},
+#if PJMEDIA_HAS_G711_CODEC
+ { "codec encode/decode - G.711", OP_PUT, K8, &g711_encode_decode},
+#endif
+#if PJMEDIA_HAS_G722_CODEC
+ { "codec encode/decode - G.722", OP_PUT, K16, &g722_encode_decode},
+#endif
+#if PJMEDIA_HAS_GSM_CODEC
+ { "codec encode/decode - GSM", OP_PUT, K8, &gsm_encode_decode},
+#endif
+#if PJMEDIA_HAS_ILBC_CODEC
+ { "codec encode/decode - iLBC", OP_PUT, K8, &ilbc_encode_decode},
+#endif
+#if PJMEDIA_HAS_SPEEX_CODEC
+ { "codec encode/decode - Speex 8Khz", OP_PUT, K8, &speex8_encode_decode},
+ { "codec encode/decode - Speex 16Khz", OP_PUT, K16, &speex16_encode_decode},
+#endif
+#if PJMEDIA_HAS_G7221_CODEC
+ { "codec encode/decode - G.722.1", OP_PUT, K16, &g7221_encode_decode},
+ { "codec encode/decode - G.722.1c", OP_PUT, K32, &g7221c_encode_decode},
+#endif
+#if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC
+ { "codec encode/decode - AMR-NB", OP_PUT, K8, &amr_encode_decode},
+#endif
+#if PJMEDIA_HAS_L16_CODEC
+ { "codec encode/decode - L16/8000/1", OP_PUT, K8, &l16_8_encode_decode},
+ { "codec encode/decode - L16/16000/1", OP_PUT, K16, &l16_16_encode_decode},
+#endif
+#if PJMEDIA_HAS_G711_CODEC
+ { "stream TX/RX - G.711", OP_PUT_GET, K8, &create_stream_pcmu},
+ { "stream TX/RX - G.711 SRTP 32bit", OP_PUT_GET, K8, &create_stream_pcmu_srtp32_no_auth},
+ { "stream TX/RX - G.711 SRTP 32bit +auth", OP_PUT_GET, K8, &create_stream_pcmu_srtp32_with_auth},
+ { "stream TX/RX - G.711 SRTP 80bit", OP_PUT_GET, K8, &create_stream_pcmu_srtp80_no_auth},
+ { "stream TX/RX - G.711 SRTP 80bit +auth", OP_PUT_GET, K8, &create_stream_pcmu_srtp80_with_auth},
+#endif
+#if PJMEDIA_HAS_G722_CODEC
+ { "stream TX/RX - G.722", OP_PUT_GET, K16, &create_stream_g722},
+#endif
+#if PJMEDIA_HAS_GSM_CODEC
+ { "stream TX/RX - GSM", OP_PUT_GET, K8, &create_stream_gsm},
+ { "stream TX/RX - GSM SRTP 32bit", OP_PUT_GET, K8, &create_stream_gsm_srtp32_no_auth},
+ { "stream TX/RX - GSM SRTP 32bit + auth", OP_PUT_GET, K8, &create_stream_gsm_srtp32_with_auth},
+ { "stream TX/RX - GSM SRTP 80bit", OP_PUT_GET, K8, &create_stream_gsm_srtp80_no_auth},
+ { "stream TX/RX - GSM SRTP 80bit + auth", OP_PUT_GET, K8, &create_stream_gsm_srtp80_with_auth},
+#endif
+#if PJMEDIA_HAS_G7221_CODEC
+ { "stream TX/RX - G.722.1", OP_PUT_GET, K16, &create_stream_g7221},
+ { "stream TX/RX - G.722.1c", OP_PUT_GET, K32, &create_stream_g7221c},
+#endif
+#if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC
+ { "stream TX/RX - AMR-NB", OP_PUT_GET, K8, &create_stream_amr},
+#endif
+ };
+
+ unsigned i, c, k[3] = {K8, K16, K32}, clock_rates[3] = {8000, 16000, 32000};
+
+ PJ_LOG(3,(THIS_FILE, "MIPS test, with CPU=%dMhz, %6.1f MIPS", CPU_MHZ, CPU_IPS / 1000000));
+ PJ_LOG(3,(THIS_FILE, "Clock Item Time CPU MIPS"));
+ PJ_LOG(3,(THIS_FILE, " Rate (usec) (%%) "));
+ PJ_LOG(3,(THIS_FILE, "----------------------------------------------------------------------"));
+
+ for (c=0; c<PJ_ARRAY_SIZE(clock_rates); ++c) {
+ for (i=0; i<PJ_ARRAY_SIZE(entries); ++i) {
+ enum
+ {
+ RETRY = 5, /* number of test retries */
+ };
+ struct test_entry *e = &entries[i];
+ pj_timestamp times[RETRY], tzero;
+ int usec;
+ float cpu_pct, mips_val;
+ unsigned j, clock_rate = clock_rates[c];
+
+ if ((e->valid_clock_rate & k[c]) == 0)
+ continue;
+
+ /* Run test */
+ for (j=0; j<RETRY; ++j) {
+ pj_thread_sleep(1);
+ times[j] = run_entry(clock_rate, e);
+ }
+
+ /* Sort ascending */
+ for (j=0; j<RETRY; ++j) {
+ unsigned k;
+ for (k=j+1; k<RETRY; ++k) {
+ if (times[k].u64 < times[j].u64) {
+ pj_timestamp tmp = times[j];
+ times[j] = times[k];
+ times[k] = tmp;
+ }
+ }
+ }
+
+ /* Calculate usec elapsed as average of two best times */
+ tzero.u32.hi = tzero.u32.lo = 0;
+ usec = (pj_elapsed_usec(&tzero, &times[0]) +
+ pj_elapsed_usec(&tzero, &times[1])) / 2;
+
+ usec = usec / (DURATION / 1000);
+
+ mips_val = (float)(CPU_IPS * usec / 1000000.0 / 1000000);
+ cpu_pct = (float)(100.0 * usec / 1000000);
+ PJ_LOG(3,(THIS_FILE, "%2dKHz %-38s % 8d %8.3f %7.2f",
+ clock_rate/1000, e->title, usec, cpu_pct, mips_val));
+
+ }
+ }
+
+ return 0;
+}
+
+
+
diff --git a/pjmedia/src/test/rtp_test.c b/pjmedia/src/test/rtp_test.c
new file mode 100644
index 0000000..4468563
--- /dev/null
+++ b/pjmedia/src/test/rtp_test.c
@@ -0,0 +1,41 @@
+/* $Id: rtp_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/rtp.h>
+#include <stdio.h>
+
+int rtp_test()
+{
+ pjmedia_rtp_session rtp;
+ FILE *fhnd = fopen("RTP.DAT", "wb");
+ const void *rtphdr;
+ int hdrlen;
+
+ if (!fhnd)
+ return -1;
+
+ pjmedia_rtp_session_init (&rtp, 4, 0x12345678);
+ pjmedia_rtp_encode_rtp (&rtp, 4, 0, 0, 160, &rtphdr, &hdrlen);
+ if (fwrite (rtphdr, hdrlen, 1, fhnd) != 1) {
+ fclose(fhnd);
+ return -1;
+ }
+ fclose(fhnd);
+ return 0;
+}
diff --git a/pjmedia/src/test/sdp_neg_test.c b/pjmedia/src/test/sdp_neg_test.c
new file mode 100644
index 0000000..5d79697
--- /dev/null
+++ b/pjmedia/src/test/sdp_neg_test.c
@@ -0,0 +1,1596 @@
+/* $Id: sdp_neg_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms oa the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 oa 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 oa
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy oa 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 <pjmedia/sdp.h>
+#include <pjmedia/sdp_neg.h>
+#include "test.h"
+
+
+#define THIS_FILE "sdp_neg_test.c"
+#define START_TEST 0
+
+enum session_type
+{
+ REMOTE_OFFER,
+ LOCAL_OFFER,
+};
+
+struct offer_answer
+{
+ enum session_type type; /* LOCAL_OFFER: REMOTE_OFFER: */
+ char *sdp1; /* local offer remote offer */
+ char *sdp2; /* remote answer initial local */
+ char *sdp3; /* local active media local answer */
+};
+
+static struct test
+{
+ const char *title;
+ unsigned offer_answer_count;
+ struct offer_answer offer_answer[4];
+} test[] =
+{
+ /* test 0: */
+ {
+ /*********************************************************************
+ * RFC 3264 examples, section 10.1 (Alice's view)
+ *
+ * Difference from the example:
+ * - Bob's port number of the third media stream in the first answer
+ * is changed (make it different than Alice's)
+ * - in the second offer/answer exchange, Alice can't accept the
+ * additional line since she didn't specify the capability
+ * in the initial negotiator creation.
+ */
+
+ "RFC 3264 example 10.1 (Alice's view)",
+ 2,
+ {
+ {
+ LOCAL_OFFER,
+ /* Alice sends offer: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.anywhere.com\r\n"
+ "s= \r\n"
+ "c=IN IP4 host.anywhere.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 51372 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "m=video 53000 RTP/AVP 32\r\n"
+ "a=rtpmap:32 MPV/90000\r\n",
+ /* Received Bob's answer: */
+ "v=0\r\n"
+ "o=bob 2890844730 2890844730 IN IP4 host.example.com\r\n"
+ "s= \r\n"
+ "c=IN IP4 host.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49920 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n"
+ "m=video 53002 RTP/AVP 32\r\n"
+ "a=rtpmap:32 MPV/90000\r\n",
+ /* Alice's SDP now: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.anywhere.com\r\n"
+ "s= \r\n"
+ "c=IN IP4 host.anywhere.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n"
+ //"a=rtpmap:31 H261/90000\r\n" /* <-- this is not necessary (port 0) */
+ "m=video 53000 RTP/AVP 32\r\n"
+ "a=rtpmap:32 MPV/90000\r\n"
+ },
+ {
+ REMOTE_OFFER,
+ /* Bob wants to change his local SDP
+ * (change local port for the first stream and add new stream)
+ * Received SDP from Bob:
+ */
+ "v=0\r\n"
+ "o=bob 2890844730 2890844731 IN IP4 host.example.com\r\n"
+ "s=-\r\n"
+ "c=IN IP4 host.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 65422 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n"
+ "m=video 53002 RTP/AVP 32\r\n"
+ "a=rtpmap:32 MPV/90000\r\n"
+ "m=audio 51434 RTP/AVP 110\r\n"
+ "a=rtpmap:110 telephone-events/8000\r\n"
+ "a=recvonly\r\n",
+ NULL,
+ /* Alice's SDP now */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844527 IN IP4 host.anywhere.com\r\n"
+ "s= \r\n"
+ "c=IN IP4 host.anywhere.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n"
+ //"a=rtpmap:31 H261/90000\r\n" /* <-- this is not necessary (port 0) */
+ "m=video 53000 RTP/AVP 32\r\n"
+ "a=rtpmap:32 MPV/90000\r\n"
+ "m=audio 0 RTP/AVP 110\r\n"
+ /* <-- the following attributes are not necessary (port 0) */
+ //"a=rtpmap:110 telephone-events/8000\r\n"
+ //"a=sendonly\r\n"
+ }
+ }
+ },
+
+ /* test 1: */
+ {
+ /*********************************************************************
+ * RFC 3264 examples, section 10.1. (Bob's view)
+ *
+ * Difference:
+ * - the SDP version in Bob's capability is changed to ver-1.
+ */
+
+ "RFC 3264 example 10.1 (Bob's view)",
+ 2,
+ {
+ {
+ REMOTE_OFFER,
+ /* Remote offer from Alice: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.anywhere.com\r\n"
+ "s= \r\n"
+ "c=IN IP4 host.anywhere.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 51372 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "m=video 53000 RTP/AVP 32\r\n"
+ "a=rtpmap:32 MPV/90000\r\n",
+ /* Bob's capability: */
+ "v=0\r\n"
+ "o=bob 2890844730 2890844729 IN IP4 host.example.com\r\n"
+ "s= \r\n"
+ "c=IN IP4 host.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49920 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n"
+ "m=video 53000 RTP/AVP 32\r\n"
+ "a=rtpmap:32 MPV/90000\r\n",
+ /* This's how Bob's answer should look like: */
+ "v=0\r\n"
+ "o=bob 2890844730 2890844730 IN IP4 host.example.com\r\n"
+ "s= \r\n"
+ "c=IN IP4 host.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49920 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n"
+ "m=video 53000 RTP/AVP 32\r\n"
+ "a=rtpmap:32 MPV/90000\r\n"
+ },
+ {
+ LOCAL_OFFER,
+ /* Bob wants to change his local SDP
+ * (change local port for the first stream and add new stream)
+ */
+ "v=0\r\n"
+ "o=bob 2890844730 2890844731 IN IP4 host.example.com\r\n"
+ "s=-\r\n"
+ "c=IN IP4 host.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 65422 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n"
+ "m=video 53000 RTP/AVP 32\r\n"
+ "a=rtpmap:32 MPV/90000\r\n"
+ "m=audio 51434 RTP/AVP 110\r\n"
+ "a=rtpmap:110 telephone-events/8000\r\n"
+ "a=recvonly\r\n",
+ /* Got answer from Alice */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844527 IN IP4 host.anywhere.com\r\n"
+ "s=-\r\n"
+ "c=IN IP4 host.anywhere.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "m=video 53000 RTP/AVP 32\r\n"
+ "a=rtpmap:32 MPV/90000\r\n"
+ "m=audio 53122 RTP/AVP 110\r\n"
+ "a=rtpmap:110 telephone-events/8000\r\n"
+ "a=sendonly\r\n",
+ /* This is how Bob's SDP should look like after negotiation */
+ "v=0\r\n"
+ "o=bob 2890844730 2890844731 IN IP4 host.example.com\r\n"
+ "s=-\r\n"
+ "c=IN IP4 host.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 65422 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n"
+ "m=video 53000 RTP/AVP 32\r\n"
+ "a=rtpmap:32 MPV/90000\r\n"
+ "m=audio 51434 RTP/AVP 110\r\n"
+ "a=rtpmap:110 telephone-events/8000\r\n"
+ "a=recvonly\r\n"
+ }
+ }
+ },
+
+ /* test 2: */
+ {
+ /*********************************************************************
+ * RFC 3264 examples, section 10.2.
+ * This is from Alice's point of view.
+ */
+
+ "RFC 3264 example 10.2 (Alice's view)",
+ 2,
+ {
+ {
+ LOCAL_OFFER,
+ /* The initial offer from Alice to Bob indicates a single audio
+ * stream with the three audio codecs that are available in the
+ * DSP. The stream is marked as inactive,
+ */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.anywhere.com\r\n"
+ "s=-\r\n"
+ "c=IN IP4 host.anywhere.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 62986 RTP/AVP 0 4 18\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:4 G723/8000\r\n"
+ "a=rtpmap:18 G729/8000\r\n"
+ "a=inactive\r\n",
+ /* Bob can support dynamic switching between PCMU and G.723. So,
+ * he sends the following answer:
+ */
+ "v=0\r\n"
+ "o=bob 2890844730 2890844731 IN IP4 host.example.com\r\n"
+ "s=-\r\n"
+ "c=IN IP4 host.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 54344 RTP/AVP 0 4\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:4 G723/8000\r\n"
+ "a=inactive\r\n",
+ /* This is how Alice's media should look like after negotiation */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.anywhere.com\r\n"
+ "s=-\r\n"
+ "c=IN IP4 host.anywhere.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 62986 RTP/AVP 0 4\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:4 G723/8000\r\n"
+ "a=inactive\r\n",
+ },
+ {
+ LOCAL_OFFER,
+ /* Alice sends an updated offer with a sendrecv stream: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844527 IN IP4 host.anywhere.com\r\n"
+ "s=-\r\n"
+ "c=IN IP4 host.anywhere.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 62986 RTP/AVP 4\r\n"
+ "a=rtpmap:4 G723/8000\r\n"
+ "a=sendrecv\r\n",
+ /* Bob accepts the single codec: */
+ "v=0\r\n"
+ "o=bob 2890844730 2890844732 IN IP4 host.example.com\r\n"
+ "s= \r\n"
+ "c=IN IP4 host.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 54344 RTP/AVP 4\r\n"
+ "a=rtpmap:4 G723/8000\r\n"
+ "a=sendrecv\r\n",
+ /* This is how Alice's media should look like after negotiation */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844527 IN IP4 host.anywhere.com\r\n"
+ "s=-\r\n"
+ "c=IN IP4 host.anywhere.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 62986 RTP/AVP 4\r\n"
+ "a=rtpmap:4 G723/8000\r\n"
+ "a=sendrecv\r\n"
+ }
+ }
+ },
+
+#if 0
+ // this test is commented, this causes error:
+ // No suitable codec for remote offer (PJMEDIA_SDPNEG_NOANSCODEC),
+ // since currently the negotiator always answer with one codec,
+ // PCMU in this case, while PCMU is not included in the second offer.
+
+ /* test 3: */
+ {
+ /*********************************************************************
+ * RFC 3264 examples, section 10.2.
+ * This is from Bob's point of view.
+ *
+ * Difference:
+ * - The SDP version number in Bob's initial capability is ver-1
+ */
+
+ "RFC 3264 example 10.2 (Bob's view)",
+ 2,
+ {
+ {
+ REMOTE_OFFER,
+ /* Bob received offer from Alice:
+ */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.anywhere.com\r\n"
+ "s=-\r\n"
+ "c=IN IP4 host.anywhere.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 62986 RTP/AVP 0 4 18\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:4 G723/8000\r\n"
+ "a=rtpmap:18 G729/8000\r\n"
+ "a=inactive\r\n",
+ /* Bob's capability:
+ */
+ "v=0\r\n"
+ "o=bob 2890844730 2890844730 IN IP4 host.example.com\r\n"
+ "s=-\r\n"
+ "c=IN IP4 host.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 54344 RTP/AVP 0 4\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:4 G723/8000\r\n"
+ "a=inactive\r\n",
+ /* This is how Bob's media should look like after negotiation */
+ "v=0\r\n"
+ "o=bob 2890844730 2890844731 IN IP4 host.example.com\r\n"
+ "s=-\r\n"
+ "c=IN IP4 host.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 54344 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=inactive\r\n"
+ },
+ {
+ REMOTE_OFFER,
+ /* Received updated Alice's SDP: offer with a sendrecv stream: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844527 IN IP4 host.anywhere.com\r\n"
+ "s=-\r\n"
+ "c=IN IP4 host.anywhere.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 62986 RTP/AVP 4\r\n"
+ "a=rtpmap:4 G723/8000\r\n"
+ "a=sendrecv\r\n",
+ /* Bob accepts the single codec: */
+ NULL,
+ /* This is how Bob's media should look like after negotiation */
+ "v=0\r\n"
+ "o=bob 2890844730 2890844732 IN IP4 host.example.com\r\n"
+ "s=-\r\n"
+ "c=IN IP4 host.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 54344 RTP/AVP 4\r\n"
+ "a=rtpmap:4 G723/8000\r\n"
+ "a=sendrecv\r\n",
+ }
+ }
+ },
+#endif
+
+ /* test 4: */
+ {
+ /*********************************************************************
+ * RFC 4317 Sample 2.1: Audio and Video 1 (Alice's view)
+ *
+ * This common scenario shows a video and audio session in which
+ * multiple codecs are offered but only one is accepted. As a result of
+ * the exchange shown below, Alice and Bob may send only PCMU audio and
+ * MPV video. Note: Dynamic payload type 97 is used for iLBC codec
+ */
+ "RFC 4317 section 2.1: Audio and Video 1 (Alice's view)",
+ 1,
+ {
+ {
+ LOCAL_OFFER,
+ /* Alice's local offer: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=-\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 0 8 97\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:8 PCMA/8000\r\n"
+ "a=rtpmap:97 iLBC/8000\r\n"
+ "m=video 51372 RTP/AVP 31 32\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "a=rtpmap:32 MPV/90000\r\n",
+ /* Received answer from Bob: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n"
+ "s=-\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49174 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 49170 RTP/AVP 32\r\n"
+ "a=rtpmap:32 MPV/90000\r\n",
+ /* This is how Alice's media should look like now: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=-\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 51372 RTP/AVP 32\r\n"
+ "a=rtpmap:32 MPV/90000\r\n"
+ }
+ }
+ },
+
+ /* test 5: */
+ {
+ /*********************************************************************
+ * RFC 4317 Sample 2.1: Audio and Video 1 (Bob's view)
+ *
+ * This common scenario shows a video and audio session in which
+ * multiple codecs are offered but only one is accepted. As a result of
+ * the exchange shown below, Alice and Bob may send only PCMU audio and
+ * MPV video. Note: Dynamic payload type 97 is used for iLBC codec
+ *
+ * Difference:
+ * - Bob's initial capability version number
+ */
+ "RFC 4317 section 2.1: Audio and Video 1 (Bob's view)",
+ 1,
+ {
+ {
+ REMOTE_OFFER,
+ /* Received Alice's local offer: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=-\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 0 8 97\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:8 PCMA/8000\r\n"
+ "a=rtpmap:97 iLBC/8000\r\n"
+ "m=video 51372 RTP/AVP 31 32\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "a=rtpmap:32 MPV/90000\r\n",
+ /* Bob's capability: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844563 IN IP4 host.biloxi.example.com\r\n"
+ "s=-\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49174 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 49170 RTP/AVP 32\r\n"
+ "a=rtpmap:32 MPV/90000\r\n",
+ /* This is how Bob's media should look like now: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n"
+ "s=-\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49174 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 49170 RTP/AVP 32\r\n"
+ "a=rtpmap:32 MPV/90000\r\n"
+ }
+ }
+ },
+
+ /* test 6: */
+ {
+ /*********************************************************************
+ * RFC 4317 Sample 2.2: Audio and Video 2 (Alice's view)
+ *
+ * Difference:
+ * - Bob's initial capability version number
+ */
+ "RFC 4317 section 2.2: Audio and Video 2 (Alice's view)",
+ 2,
+ {
+ {
+ LOCAL_OFFER,
+ /* Alice sends offer: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 0 8 97\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:8 PCMA/8000\r\n"
+ "a=rtpmap:97 iLBC/8000\r\n"
+ "m=video 51372 RTP/AVP 31 32\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "a=rtpmap:32 MPV/90000\r\n",
+ /* Bob's answer: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n"
+ "s=bob\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49172 RTP/AVP 0 8\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:8 PCMA/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n",
+ /* This is how Alice's media should look like now: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 0 8\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:8 PCMA/8000\r\n"
+ // By #1088, the formats won't be negotiated when the media has port 0.
+ //"m=video 0 RTP/AVP 31\r\n"
+ "m=video 0 RTP/AVP 31 32\r\n"
+ //"a=rtpmap:31 H261/90000\r\n" /* <-- this is not necessary (port 0) */
+ },
+ {
+ LOCAL_OFFER,
+ /* Alice sends updated offer: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 51372 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n",
+ /* Bob's answer: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com\r\n"
+ "s=bob\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49172 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n",
+ /* This is how Alice's SDP should look like: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 51372 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n"
+ //"a=rtpmap:31 H261/90000\r\n" /* <-- this is not necessary (port 0) */
+ }
+ }
+ },
+
+ /* test 7: */
+ {
+ /*********************************************************************
+ * RFC 4317 Sample 2.2: Audio and Video 2 (Bob's view)
+ *
+ * Difference:
+ * - Bob's initial capability version number
+ */
+ "RFC 4317 section 2.2: Audio and Video 2 (Bob's view)",
+ 2,
+ {
+ {
+ REMOTE_OFFER,
+ /* Received offer from alice: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 0 8 97\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:8 PCMA/8000\r\n"
+ "a=rtpmap:97 iLBC/8000\r\n"
+ "m=video 51372 RTP/AVP 31 32\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "a=rtpmap:32 MPV/90000\r\n",
+ /* Bob's initial capability: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844563 IN IP4 host.biloxi.example.com\r\n"
+ "s=bob\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49172 RTP/AVP 0 8\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:8 PCMA/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n",
+ /* This is how Bob's answer should look like now: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n"
+ "s=bob\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49172 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ },
+ {
+ REMOTE_OFFER,
+ /* Received updated offer from Alice: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 51372 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n",
+ /* Bob's answer: */
+ NULL,
+ /* This is how Bob's answer should look like: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com\r\n"
+ "s=bob\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49172 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n"
+ //"a=rtpmap:31 H261/90000\r\n" /* <-- this is not necessary (port 0) */
+ }
+ }
+ },
+
+ /* test 8: */
+ {
+ /*********************************************************************
+ * RFC 4317 Sample 2.4: Audio and Telephone-Events (Alice's view)
+ *
+ */
+
+ "RFC 4317 section 2.4: Audio and Telephone-Events (Alice's view)",
+ 1,
+ {
+ {
+ LOCAL_OFFER,
+ /* Alice sends offer: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 0 97\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:97 iLBC/8000\r\n"
+ "m=audio 49172 RTP/AVP 98\r\n"
+ "a=rtpmap:98 telephone-event/8000\r\n"
+ "a=sendonly\r\n",
+ /* Received Bob's answer: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n"
+ "s=bob\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49172 RTP/AVP 97\r\n"
+ "a=rtpmap:97 iLBC/8000\r\n"
+ "m=audio 49174 RTP/AVP 98\r\n"
+ "a=rtpmap:98 telephone-event/8000\r\n"
+ "a=recvonly\r\n",
+ /* Alice's SDP now: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 97\r\n"
+ "a=rtpmap:97 iLBC/8000\r\n"
+ "m=audio 49172 RTP/AVP 98\r\n"
+ "a=rtpmap:98 telephone-event/8000\r\n"
+ "a=sendonly\r\n"
+ }
+ }
+ },
+
+
+ /* test 9: */
+ {
+ /*********************************************************************
+ * RFC 4317 Sample 2.4: Audio and Telephone-Events (Bob's view)
+ *
+ * Difference:
+ * - Bob's initial SDP version number
+ * - Bob's capability are added with more formats, and the
+ * stream order is interchanged to test the negotiator.
+ */
+
+ "RFC 4317 section 2.4: Audio and Telephone-Events (Bob's view)",
+ 1,
+ {
+ {
+ REMOTE_OFFER,
+ /* Received Alice's offer: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 0 97\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:97 iLBC/8000\r\n"
+ "m=audio 49172 RTP/AVP 98\r\n"
+ "a=rtpmap:98 telephone-event/8000\r\n"
+ "a=sendonly\r\n",
+ /* Bob's initial capability: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844563 IN IP4 host.biloxi.example.com\r\n"
+ "s=bob\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49174 RTP/AVP 4 98\r\n"
+ "a=rtpmap:98 telephone-event/8000\r\n"
+ "m=audio 49172 RTP/AVP 97 8 99\r\n"
+ "a=rtpmap:97 iLBC/8000\r\n"
+ "a=rtpmap:99 telephone-event/8000\r\n",
+ /* Bob's answer should be: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n"
+ "s=bob\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49172 RTP/AVP 97\r\n"
+ "a=rtpmap:97 iLBC/8000\r\n"
+ "m=audio 49174 RTP/AVP 98\r\n"
+ "a=rtpmap:98 telephone-event/8000\r\n"
+ "a=recvonly\r\n"
+ }
+ }
+ },
+
+ /* test 10: */
+ {
+ /*********************************************************************
+ * RFC 4317 Sample 2.6: Audio with Telephone-Events (Alice's view)
+ *
+ */
+
+ "RFC 4317 section 2.6: Audio with Telephone-Events (Alice's view)",
+ 1,
+ {
+ {
+ LOCAL_OFFER,
+ /* Alice sends offer: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=audio 51372 RTP/AVP 97 101\r\n"
+ "a=rtpmap:97 iLBC/8000\r\n"
+ "a=rtpmap:101 telephone-event/8000\r\n",
+ /* Received bob's answer: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n"
+ "s=bob\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 0 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=audio 49170 RTP/AVP 97 101\r\n"
+ "a=rtpmap:97 iLBC/8000\r\n"
+ "a=rtpmap:101 telephone-event/8000\r\n",
+ /* Alice's local SDP should be: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 0 RTP/AVP 0\r\n"
+ //"a=rtpmap:0 PCMU/8000\r\n" /* <-- this is not necessary (port 0) */
+ "m=audio 51372 RTP/AVP 97 101\r\n"
+ "a=rtpmap:97 iLBC/8000\r\n"
+ "a=rtpmap:101 telephone-event/8000\r\n"
+ }
+ }
+ },
+
+ /* test 11: */
+ {
+ /*********************************************************************
+ * RFC 4317 Sample 2.6: Audio with Telephone-Events (Bob's view)
+ *
+ * Difference:
+ * - Bob's SDP version number
+ * - Bob's initial capability are expanded with multiple m lines
+ * and more formats
+ */
+
+ "RFC 4317 section 2.6: Audio with Telephone-Events (Bob's view)",
+ 1,
+ {
+ {
+ REMOTE_OFFER,
+ /* Received Alice's offer: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=audio 51372 RTP/AVP 97 101\r\n"
+ "a=rtpmap:97 iLBC/8000\r\n"
+ "a=rtpmap:101 telephone-event/8000\r\n",
+ /* Bob's initial capability also has video: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844563 IN IP4 host.biloxi.example.com\r\n"
+ "s=bob\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 4 97 101\r\n"
+ "a=rtpmap:4 G723/8000\r\n"
+ "a=rtpmap:97 iLBC/8000\r\n"
+ "a=rtpmap:101 telephone-event/8000\r\n"
+ "m=video 1000 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n",
+ /* Bob's answer should be: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n"
+ "s=bob\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 0 RTP/AVP 0\r\n"
+ //"a=rtpmap:0 PCMU/8000\r\n" /* <-- this is not necessary (port 0) */
+ "m=audio 49170 RTP/AVP 97 101\r\n"
+ "a=rtpmap:97 iLBC/8000\r\n"
+ "a=rtpmap:101 telephone-event/8000\r\n",
+ }
+ }
+ },
+
+ /* test 12: */
+ {
+ /*********************************************************************
+ * Ticket #527: More lenient SDP negotiator.
+ */
+
+ "Ticket #527 scenario #1: Partial answer",
+ 1,
+ {
+ {
+ LOCAL_OFFER,
+ /* Alice sends offer audio and video: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 4000 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n",
+ /* Receive Bob's answer only audio: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844563 IN IP4 host.biloxi.example.com\r\n"
+ "s=bob\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n",
+ /* Alice's local SDP should be: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n"
+ //"a=rtpmap:31 H261/90000\r\n" /* <-- this is not necessary (port 0) */
+ "",
+ }
+ }
+ },
+
+ /* test 13: */
+ {
+ /*********************************************************************
+ * Ticket #527: More lenient SDP negotiator.
+ */
+
+ "Ticket #527 scenario #1: Media mismatch in answer",
+ 1,
+ {
+ {
+ LOCAL_OFFER,
+ /* Alice sends offer audio and video: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 3000 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 4000 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n",
+ /* Receive Bob's answer two audio: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844563 IN IP4 host.biloxi.example.com\r\n"
+ "s=bob\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=audio 49172 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n",
+ /* Alice's local SDP should be: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 3000 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n"
+ //"a=rtpmap:31 H261/90000\r\n" /* <-- this is not necessary (port 0) */
+ "",
+ }
+ }
+ },
+
+ /* test 14: */
+ {
+ /*********************************************************************
+ * Ticket #527: More lenient SDP negotiator.
+ */
+
+ "Ticket #527 scenario #2: Modify offer - partial streams",
+ 2,
+ {
+ {
+ LOCAL_OFFER,
+ /* Alice sends offer: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 3000 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=audio 3100 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 3200 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "",
+ /* Receive Bob's answer: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844563 IN IP4 host.biloxi.example.com\r\n"
+ "s=bob\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 4000 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=audio 4100 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 4200 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "",
+ /* Alice's local SDP should be: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 3000 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=audio 3100 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 3200 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "",
+ },
+ {
+ LOCAL_OFFER,
+ /* Alice modifies offer with only specify one audio: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 5200 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "",
+ /* Receive Bob's answer: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844563 IN IP4 host.biloxi.example.com\r\n"
+ "s=bob\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 7000 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=audio 0 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "",
+ /* Alice's local SDP should be: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 5200 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=audio 0 RTP/AVP 0\r\n"
+ //"a=rtpmap:0 PCMU/8000\r\n" /* <-- this is not necessary (port 0) */
+ "m=video 0 RTP/AVP 31\r\n"
+ //"a=rtpmap:31 H261/90000\r\n" /* <-- this is not necessary (port 0) */
+ "",
+ }
+ }
+ },
+
+ /* test 15: */
+ {
+ /*********************************************************************
+ * Ticket #527: More lenient SDP negotiator.
+ */
+
+ "Ticket #527 scenario #2: Modify offer - unordered m= lines",
+ 2,
+ {
+ {
+ LOCAL_OFFER,
+ /* Alice sends offer: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 3000 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 3200 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "",
+ /* Receive Bob's answer: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844563 IN IP4 host.biloxi.example.com\r\n"
+ "s=bob\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 4000 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 4200 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "",
+ /* Alice's local SDP should be: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 3000 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 3200 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "",
+ },
+ {
+ LOCAL_OFFER,
+ /* Alice modifies offer with unordered m= lines: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=video 5000 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "m=audio 5200 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "",
+ /* Receive Bob's answer: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844563 IN IP4 host.biloxi.example.com\r\n"
+ "s=bob\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 7000 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 2000 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "",
+ /* Alice's local SDP should be: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 5200 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 5000 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "",
+ }
+ }
+ },
+
+ /* test 16: */
+ {
+ /*********************************************************************
+ * Ticket #527: More lenient SDP negotiator.
+ */
+
+ "Ticket #527 scenario #2: Modify offer - partial & unordered streams",
+ 2,
+ {
+ {
+ LOCAL_OFFER,
+ /* Alice sends offer: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 3000 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=audio 3200 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 3400 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "",
+ /* Receive Bob's answer: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844563 IN IP4 host.biloxi.example.com\r\n"
+ "s=bob\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 4000 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=audio 4200 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 4400 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "",
+ /* Alice's local SDP should be: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 3000 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=audio 3200 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 3400 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "",
+ },
+ {
+ LOCAL_OFFER,
+ /* Alice modifies offer by specifying partial and unordered media: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=video 5000 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "m=audio 7000 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "",
+ /* Receive Bob's answer: */
+ "v=0\r\n"
+ "o=bob 2808844564 2808844563 IN IP4 host.biloxi.example.com\r\n"
+ "s=bob\r\n"
+ "c=IN IP4 host.biloxi.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 4000 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=audio 0 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 4400 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "",
+ /* Alice's local SDP should be: */
+ "v=0\r\n"
+ "o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\r\n"
+ "s=alice\r\n"
+ "c=IN IP4 host.atlanta.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 7000 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=audio 0 RTP/AVP 0\r\n"
+ //"a=rtpmap:0 PCMU/8000\r\n" /* <-- this is not necessary (port 0) */
+ "m=video 5000 RTP/AVP 31\r\n"
+ "a=rtpmap:31 H261/90000\r\n"
+ "",
+ }
+ }
+ },
+
+};
+
+static const char *find_diff(const char *s1, const char *s2,
+ int *line)
+{
+ *line = 1;
+ while (*s2 && *s1) {
+ if (*s2 != *s1)
+ return s2;
+ if (*s2 == '\n')
+ ++*line;
+ ++s2, ++s1;
+ }
+
+ return s2;
+}
+
+static int compare_sdp_string(const char *cmp_title,
+ const char *title1,
+ const pjmedia_sdp_session *sdp1,
+ const char *title2,
+ const pjmedia_sdp_session *sdp2,
+ pj_status_t logical_cmp)
+{
+ char sdpbuf1[1024], sdpbuf2[1024];
+ pj_ssize_t len1, len2;
+
+ len1 = pjmedia_sdp_print(sdp1, sdpbuf1, sizeof(sdpbuf1));
+ if (len1 < 1) {
+ PJ_LOG(3,(THIS_FILE," error: printing sdp1"));
+ return -1;
+ }
+ sdpbuf1[len1] = '\0';
+
+ len2 = pjmedia_sdp_print(sdp2, sdpbuf2, sizeof(sdpbuf2));
+ if (len2 < 1) {
+ PJ_LOG(3,(THIS_FILE," error: printing sdp2"));
+ return -1;
+ }
+ sdpbuf2[len2] = '\0';
+
+ if (logical_cmp != PJ_SUCCESS) {
+ char errbuf[80];
+
+ pjmedia_strerror(logical_cmp, errbuf, sizeof(errbuf));
+
+ PJ_LOG(3,(THIS_FILE,"%s mismatch: %s\n"
+ "%s:\n"
+ "%s\n"
+ "%s:\n"
+ "%s\n",
+ cmp_title,
+ errbuf,
+ title1, sdpbuf1,
+ title2, sdpbuf2));
+
+ return -1;
+
+ } else if (strcmp(sdpbuf1, sdpbuf2) != 0) {
+ int line;
+ const char *diff;
+
+ PJ_LOG(3,(THIS_FILE,"%s mismatch:\n"
+ "%s:\n"
+ "%s\n"
+ "%s:\n"
+ "%s\n",
+ cmp_title,
+ title1, sdpbuf1,
+ title2, sdpbuf2));
+
+ diff = find_diff(sdpbuf1, sdpbuf2, &line);
+ PJ_LOG(3,(THIS_FILE,"Difference: line %d:\n"
+ "%s",
+ line, diff));
+ return -1;
+
+ } else {
+ return 0;
+ }
+
+}
+
+static int offer_answer_test(pj_pool_t *pool, pjmedia_sdp_neg **p_neg,
+ struct offer_answer *oa)
+{
+ pjmedia_sdp_session *sdp1;
+ pjmedia_sdp_neg *neg;
+ pj_status_t status;
+
+ status = pjmedia_sdp_parse(pool, oa->sdp1, pj_ansi_strlen(oa->sdp1),
+ &sdp1);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: unexpected parse status for sdp1");
+ return -10;
+ }
+
+ status = pjmedia_sdp_validate(sdp1);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: sdp1 validation failed");
+ return -15;
+ }
+
+ neg = *p_neg;
+
+ if (oa->type == LOCAL_OFFER) {
+
+ /*
+ * Local creates offer first.
+ */
+ pjmedia_sdp_session *sdp2, *sdp3;
+ const pjmedia_sdp_session *active;
+
+ if (neg == NULL) {
+ /* Create negotiator with local offer. */
+ status = pjmedia_sdp_neg_create_w_local_offer(pool, sdp1, &neg);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: pjmedia_sdp_neg_create_w_local_offer");
+ return -20;
+ }
+ *p_neg = neg;
+
+ } else {
+ /* Modify local offer */
+ status = pjmedia_sdp_neg_modify_local_offer(pool, neg, sdp1);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: pjmedia_sdp_neg_modify_local_offer");
+ return -30;
+ }
+ }
+
+ /* Parse and validate remote answer */
+ status = pjmedia_sdp_parse(pool, oa->sdp2, pj_ansi_strlen(oa->sdp2),
+ &sdp2);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: parsing sdp2");
+ return -40;
+ }
+
+ status = pjmedia_sdp_validate(sdp2);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: sdp2 validation failed");
+ return -50;
+ }
+
+ /* Give the answer to negotiator. */
+ status = pjmedia_sdp_neg_set_remote_answer(pool, neg, sdp2);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: pjmedia_sdp_neg_rx_remote_answer");
+ return -60;
+ }
+
+ /* Negotiate remote answer with local answer */
+ status = pjmedia_sdp_neg_negotiate(pool, neg, 0);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: pjmedia_sdp_neg_negotiate");
+ return -70;
+ }
+
+ /* Get the local active media. */
+ status = pjmedia_sdp_neg_get_active_local(neg, &active);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: pjmedia_sdp_neg_get_local");
+ return -80;
+ }
+
+ /* Parse and validate the correct active media. */
+ status = pjmedia_sdp_parse(pool, oa->sdp3, pj_ansi_strlen(oa->sdp3),
+ &sdp3);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: parsing sdp3");
+ return -90;
+ }
+
+ status = pjmedia_sdp_validate(sdp3);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: sdp3 validation failed");
+ return -100;
+ }
+
+ /* Compare active with sdp3 */
+ status = pjmedia_sdp_session_cmp(active, sdp3, 0);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: active local comparison mismatch");
+ compare_sdp_string("Logical cmp after negotiatin remote answer",
+ "Active local sdp from negotiator", active,
+ "The correct active local sdp", sdp3,
+ status);
+ return -110;
+ }
+
+ /* Compare the string representation oa both sdps */
+ status = compare_sdp_string("String cmp after negotiatin remote answer",
+ "Active local sdp from negotiator", active,
+ "The correct active local sdp", sdp3,
+ PJ_SUCCESS);
+ if (status != 0)
+ return -120;
+
+ } else {
+ /*
+ * Remote creates offer first.
+ */
+
+ pjmedia_sdp_session *sdp2 = NULL, *sdp3;
+ const pjmedia_sdp_session *answer;
+
+ if (oa->sdp2) {
+ /* Parse and validate initial local capability */
+ status = pjmedia_sdp_parse(pool, oa->sdp2, pj_ansi_strlen(oa->sdp2),
+ &sdp2);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: parsing sdp2");
+ return -200;
+ }
+
+ status = pjmedia_sdp_validate(sdp2);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: sdp2 validation failed");
+ return -210;
+ }
+ } else if (neg) {
+ const pjmedia_sdp_session *lsdp;
+ status = pjmedia_sdp_neg_get_active_local(neg, &lsdp);
+ if (status != PJ_SUCCESS) {
+ app_perror(status,
+ " error: pjmedia_sdp_neg_get_active_local");
+ return -215;
+ }
+ sdp2 = (pjmedia_sdp_session*)lsdp;
+ }
+
+ if (neg == NULL) {
+ /* Create negotiator with remote offer. */
+ status = pjmedia_sdp_neg_create_w_remote_offer(pool, sdp2, sdp1, &neg);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: pjmedia_sdp_neg_create_w_remote_offer");
+ return -220;
+ }
+ *p_neg = neg;
+
+ } else {
+ /* Received subsequent offer from remote. */
+ status = pjmedia_sdp_neg_set_remote_offer(pool, neg, sdp1);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: pjmedia_sdp_neg_rx_remote_offer");
+ return -230;
+ }
+
+ status = pjmedia_sdp_neg_set_local_answer(pool, neg, sdp2);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: pjmedia_sdp_neg_set_local_answer");
+ return -235;
+ }
+ }
+
+ /* Negotiate. */
+ status = pjmedia_sdp_neg_negotiate(pool, neg, 0);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: pjmedia_sdp_neg_negotiate");
+ return -240;
+ }
+
+ /* Get our answer. */
+ status = pjmedia_sdp_neg_get_active_local(neg, &answer);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: pjmedia_sdp_neg_get_local");
+ return -250;
+ }
+
+ /* Parse the correct answer. */
+ status = pjmedia_sdp_parse(pool, oa->sdp3, pj_ansi_strlen(oa->sdp3),
+ &sdp3);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: parsing sdp3");
+ return -260;
+ }
+
+ /* Validate the correct answer. */
+ status = pjmedia_sdp_validate(sdp3);
+ if (status != PJ_SUCCESS) {
+ app_perror(status, " error: sdp3 validation failed");
+ return -270;
+ }
+
+ /* Compare answer from negotiator and the correct answer */
+ status = pjmedia_sdp_session_cmp(sdp3, answer, 0);
+ if (status != PJ_SUCCESS) {
+ compare_sdp_string("Logical cmp after negotiating remote offer",
+ "Local answer from negotiator", answer,
+ "The correct local answer", sdp3,
+ status);
+
+ return -280;
+ }
+
+ /* Compare the string representation oa both answers */
+ status = compare_sdp_string("String cmp after negotiating remote offer",
+ "Local answer from negotiator", answer,
+ "The correct local answer", sdp3,
+ PJ_SUCCESS);
+ if (status != 0)
+ return -290;
+
+ }
+
+ return 0;
+}
+
+static int perform_test(pj_pool_t *pool, int test_index)
+{
+ pjmedia_sdp_neg *neg = NULL;
+ unsigned i;
+ int rc;
+
+ for (i=0; i<test[test_index].offer_answer_count; ++i) {
+
+ rc = offer_answer_test(pool, &neg, &test[test_index].offer_answer[i]);
+ if (rc != 0) {
+ PJ_LOG(3,(THIS_FILE, " test %d offer answer %d failed with status %d",
+ test_index, i, rc));
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+int sdp_neg_test()
+{
+ unsigned i;
+ int status;
+
+ for (i=START_TEST; i<PJ_ARRAY_SIZE(test); ++i) {
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(mem, "sdp_neg_test", 4000, 4000, NULL);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ PJ_LOG(3,(THIS_FILE," test %d: %s", i, test[i].title));
+ status = perform_test(pool, i);
+
+ pj_pool_release(pool);
+
+ if (status != 0) {
+ return status;
+ }
+ }
+
+ return 0;
+}
+
diff --git a/pjmedia/src/test/sdptest.c b/pjmedia/src/test/sdptest.c
new file mode 100644
index 0000000..f9733b5
--- /dev/null
+++ b/pjmedia/src/test/sdptest.c
@@ -0,0 +1,122 @@
+/* $Id: sdptest.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/sdp.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <stdio.h>
+#include <string.h>
+
+static char *sdp[] = {
+ /*
+ "v=0\r\n"
+ "o=mhandley 2890844526 2890842807 IN IP4 126.16.64.4\r\n"
+ "s=SDP Seminar\r\n"
+ "i=A Seminar on the session description protocol\r\n"
+ "u=http://www.cs.ucl.ac.uk/staff/M.Handley/sdp.03.ps\r\n"
+ "e=mjh@isi.edu (Mark Handley)\r\n"
+ "c=IN IP4 224.2.17.12/127\r\n"
+ "t=2873397496 2873404696\r\n"
+ "a=recvonly\r\n"
+ "m=audio 49170 RTP/AVP 0\r\n"
+ "m=video 51372 RTP/AVP 31\r\n"
+ "m=application 32416 udp wb\r\n"
+ "a=orient:portrait\r\n"
+ "m=audio 49230 RTP/AVP 96 97 98\r\n"
+ "a=rtpmap:96 L8/8000\r\n"
+ "a=rtpmap:97 L16/8000\r\n"
+ "a=rtpmap:98 L16/11025/2\r\n",
+ */
+ "v=0\r\n"
+ "o=usera 2890844526 2890844527 IN IP4 alice.example.com\r\n"
+ "s=\r\n"
+ "c=IN IP4 alice.example.com\r\n"
+ "t=0 0\r\n"
+ "m=message 7394 msrp/tcp *\r\n"
+ "a=accept-types: message/cpim text/plain text/html\r\n"
+ "a=path:msrp://alice.example.com:7394/2s93i9;tcp\r\n"
+};
+
+static int sdp_perform_test(pj_pool_factory *pf)
+{
+ pj_pool_t *pool;
+ int inputlen, len;
+ pjsdp_session_desc *ses;
+ char buf[1500];
+ enum { LOOP=1000000 };
+ int i;
+ pj_time_val start, end;
+
+ printf("Parsing and printing %d SDP messages..\n", LOOP);
+
+ pool = pj_pool_create(pf, "", 4096, 0, NULL);
+ inputlen = strlen(sdp[0]);
+ pj_gettimeofday(&start);
+ for (i=0; i<LOOP; ++i) {
+ ses = pjsdp_parse(sdp[0], inputlen, pool);
+ len = pjsdp_print(ses, buf, sizeof(buf));
+ buf[len] = '\0';
+ pj_pool_reset(pool);
+ }
+ pj_gettimeofday(&end);
+
+ printf("Original:\n%s\n", sdp[0]);
+ printf("Parsed:\n%s\n", buf);
+
+ PJ_TIME_VAL_SUB(end, start);
+ printf("Time: %ld:%03lds\n", end.sec, end.msec);
+
+ if (end.msec==0 && end.sec==0) end.msec=1;
+ printf("Performance: %ld msg/sec\n", LOOP*1000/PJ_TIME_VAL_MSEC(end));
+ puts("");
+
+ pj_pool_release(pool);
+ return 0;
+}
+
+static int sdp_conform_test(pj_pool_factory *pf)
+{
+ pj_pool_t *pool;
+ pjsdp_session_desc *ses;
+ int i, len;
+ char buf[1500];
+
+ for (i=0; i<sizeof(sdp)/sizeof(sdp[0]); ++i) {
+ pool = pj_pool_create(pf, "sdp", 4096, 0, NULL);
+ ses = pjsdp_parse(sdp[i], strlen(sdp[i]), pool);
+ len = pjsdp_print(ses, buf, sizeof(buf));
+ buf[len] = '\0';
+ printf("%s\n", buf);
+ pj_pool_release(pool);
+ }
+
+ return 0;
+}
+
+pj_status_t sdp_test(pj_pool_factory *pf)
+{
+ if (sdp_conform_test(pf) != 0)
+ return -2;
+
+ if (sdp_perform_test(pf) != 0)
+ return -3;
+
+ return 0;
+}
+
diff --git a/pjmedia/src/test/session_test.c b/pjmedia/src/test/session_test.c
new file mode 100644
index 0000000..02564b3
--- /dev/null
+++ b/pjmedia/src/test/session_test.c
@@ -0,0 +1,131 @@
+/* $Id: session_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/mediamgr.h>
+#include <pjmedia/session.h>
+#include <pj/sock.h>
+#include <pj/pool.h>
+#include <stdio.h>
+#include <pj/string.h>
+
+pj_status_t session_test (pj_pool_factory *pf)
+{
+ pj_med_mgr_t *mm;
+ pj_media_session_t *s1, *s2;
+ pj_pool_t *pool;
+ pjsdp_session_desc *sdp;
+ pj_media_stream_info sd_info;
+ char buf[1024];
+ int len;
+ pj_media_stream_stat tx_stat, rx_stat;
+
+ pool = pj_pool_create(pf, "test", 4096, 1024, NULL);
+
+ // Init media manager.
+ mm = pj_med_mgr_create ( pf );
+
+ // Create caller session.
+ // THIS WILL DEFINITELY CRASH (NULL as argument)!
+ s1 = pj_media_session_create (mm, NULL);
+
+ // Set caller's media to send-only.
+ sd_info.dir = PJMEDIA_DIR_ENCODING;
+ pj_media_session_modify_stream (s1, 0, PJMEDIA_STREAM_MODIFY_DIR, &sd_info);
+
+ // Create caller SDP.
+ sdp = pj_media_session_create_sdp (s1, pool, 0);
+ len = pjsdp_print (sdp, buf, sizeof(buf));
+ buf[len] = '\0';
+ printf("Caller's initial SDP:\n<BEGIN>\n%s\n<END>\n", buf);
+
+ // Parse SDP from caller.
+ sdp = pjsdp_parse (buf, len, pool);
+
+ // Create callee session based on caller's SDP.
+ // THIS WILL DEFINITELY CRASH (NULL as argument)!
+ s2 = pj_media_session_create_from_sdp (mm, sdp, NULL);
+
+ // Create callee SDP
+ sdp = pj_media_session_create_sdp (s2, pool, 0);
+ len = pjsdp_print (sdp, buf, sizeof(buf));
+ buf[len] = '\0';
+ printf("Callee's SDP:\n<BEGIN>\n%s\n<END>\n", buf);
+
+ // Parse SDP from callee.
+ sdp = pjsdp_parse (buf, len, pool);
+
+ // Update caller
+ pj_media_session_update (s1, sdp);
+ sdp = pj_media_session_create_sdp (s1, pool, 0);
+ pjsdp_print (sdp, buf, sizeof(buf));
+ printf("Caller's SDP after update:\n<BEGIN>\n%s\n<END>\n", buf);
+
+ // Now start media.
+ pj_media_session_activate (s2);
+ pj_media_session_activate (s1);
+
+ // Wait
+ for (;;) {
+ int has_stat;
+
+ printf("Enter q to exit, 1 or 2 to print statistics.\n");
+ fgets (buf, 10, stdin);
+ has_stat = 0;
+
+ switch (buf[0]) {
+ case 'q':
+ case 'Q':
+ goto done;
+ break;
+ case '1':
+ pj_media_session_get_stat (s1, 0, &tx_stat, &rx_stat);
+ has_stat = 1;
+ break;
+ case '2':
+ pj_media_session_get_stat (s2, 0, &tx_stat, &rx_stat);
+ has_stat = 1;
+ break;
+ }
+
+ if (has_stat) {
+ pj_media_stream_stat *stat[2] = { &tx_stat, &rx_stat };
+ const char *statname[2] = { "TX", "RX" };
+ int i;
+
+ for (i=0; i<2; ++i) {
+ printf("%s statistics:\n", statname[i]);
+ printf(" Pkt TX=%d RX=%d\n", stat[i]->pkt_tx, stat[i]->pkt_rx);
+ printf(" Octets TX=%d RX=%d\n", stat[i]->oct_tx, stat[i]->oct_rx);
+ printf(" Jitter %d ms\n", stat[i]->jitter);
+ printf(" Pkt lost %d\n", stat[i]->pkt_lost);
+ }
+ printf("\n");
+ }
+ }
+
+done:
+
+ // Done.
+ pj_pool_release (pool);
+ pj_media_session_destroy (s2);
+ pj_media_session_destroy (s1);
+ pj_med_mgr_destroy (mm);
+
+ return 0;
+}
diff --git a/pjmedia/src/test/test.c b/pjmedia/src/test/test.c
new file mode 100644
index 0000000..44b46c3
--- /dev/null
+++ b/pjmedia/src/test/test.c
@@ -0,0 +1,127 @@
+/* $Id: test.c 3893 2011-12-01 10:49:07Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 "test.h"
+
+#define THIS_FILE "test.c"
+
+#define DO_TEST(test) do { \
+ PJ_LOG(3, (THIS_FILE, "Running %s...", #test)); \
+ rc = test; \
+ PJ_LOG(3, (THIS_FILE, \
+ "%s(%d)", \
+ (rc ? "..ERROR" : "..success"), rc)); \
+ if (rc!=0) goto on_return; \
+ } while (0)
+
+
+pj_pool_factory *mem;
+
+
+void app_perror(pj_status_t status, const char *msg)
+{
+ char errbuf[PJ_ERR_MSG_SIZE];
+
+ pjmedia_strerror(status, errbuf, sizeof(errbuf));
+
+ PJ_LOG(3,(THIS_FILE, "%s: %s", msg, errbuf));
+}
+
+/* Force linking PLC stuff if G.711 is disabled. See:
+ * https://trac.pjsip.org/repos/ticket/1337
+ */
+#if PJMEDIA_HAS_G711_CODEC==0
+int dummy()
+{
+ // Dummy
+ return (int) &pjmedia_plc_save;
+}
+#endif
+
+int test_main(void)
+{
+ int rc = 0;
+ pj_caching_pool caching_pool;
+ pj_pool_t *pool;
+
+ pj_init();
+ pj_caching_pool_init(&caching_pool, &pj_pool_factory_default_policy, 0);
+ pool = pj_pool_create(&caching_pool.factory, "test", 1000, 512, NULL);
+
+ pj_log_set_decor(PJ_LOG_HAS_NEWLINE);
+ pj_log_set_level(3);
+
+ mem = &caching_pool.factory;
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+ pjmedia_video_format_mgr_create(pool, 64, 0, NULL);
+ pjmedia_converter_mgr_create(pool, NULL);
+ pjmedia_event_mgr_create(pool, 0, NULL);
+ pjmedia_vid_codec_mgr_create(pool, NULL);
+#endif
+
+#if HAS_VID_PORT_TEST
+ DO_TEST(vid_port_test());
+#endif
+
+#if HAS_VID_DEV_TEST
+ DO_TEST(vid_dev_test());
+#endif
+
+#if HAS_VID_CODEC_TEST
+ DO_TEST(vid_codec_test());
+#endif
+
+#if HAS_SDP_NEG_TEST
+ DO_TEST(sdp_neg_test());
+#endif
+ //DO_TEST(sdp_test (&caching_pool.factory));
+ //DO_TEST(rtp_test(&caching_pool.factory));
+ //DO_TEST(session_test (&caching_pool.factory));
+#if HAS_JBUF_TEST
+ DO_TEST(jbuf_main());
+#endif
+#if HAS_MIPS_TEST
+ DO_TEST(mips_test());
+#endif
+#if HAS_CODEC_VECTOR_TEST
+ DO_TEST(codec_test_vectors());
+#endif
+
+ PJ_LOG(3,(THIS_FILE," "));
+
+on_return:
+ if (rc != 0) {
+ PJ_LOG(3,(THIS_FILE,"Test completed with error(s)!"));
+ } else {
+ PJ_LOG(3,(THIS_FILE,"Looks like everything is okay!"));
+ }
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+ pjmedia_video_format_mgr_destroy(pjmedia_video_format_mgr_instance());
+ pjmedia_converter_mgr_destroy(pjmedia_converter_mgr_instance());
+ pjmedia_event_mgr_destroy(pjmedia_event_mgr_instance());
+ pjmedia_vid_codec_mgr_destroy(pjmedia_vid_codec_mgr_instance());
+#endif
+
+ pj_pool_release(pool);
+ pj_caching_pool_destroy(&caching_pool);
+
+ return rc;
+}
diff --git a/pjmedia/src/test/test.h b/pjmedia/src/test/test.h
new file mode 100644
index 0000000..872c097
--- /dev/null
+++ b/pjmedia/src/test/test.h
@@ -0,0 +1,50 @@
+/* $Id: test.h 3667 2011-07-19 11:11:07Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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_TEST_H__
+#define __PJMEDIA_TEST_H__
+
+#include <pjmedia.h>
+#include <pjlib.h>
+
+#define HAS_VID_DEV_TEST PJMEDIA_HAS_VIDEO
+#define HAS_VID_PORT_TEST PJMEDIA_HAS_VIDEO
+#define HAS_VID_CODEC_TEST PJMEDIA_HAS_VIDEO
+#define HAS_SDP_NEG_TEST 1
+#define HAS_JBUF_TEST 1
+#define HAS_MIPS_TEST 1
+#define HAS_CODEC_VECTOR_TEST 1
+
+int session_test(void);
+int rtp_test(void);
+int sdp_test(void);
+int jbuf_main(void);
+int sdp_neg_test(void);
+int mips_test(void);
+int codec_test_vectors(void);
+int vid_codec_test(void);
+int vid_dev_test(void);
+int vid_port_test(void);
+
+extern pj_pool_factory *mem;
+void app_perror(pj_status_t status, const char *title);
+
+int test_main(void);
+
+#endif /* __PJMEDIA_TEST_H__ */
diff --git a/pjmedia/src/test/vectors/.keep b/pjmedia/src/test/vectors/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pjmedia/src/test/vectors/.keep
diff --git a/pjmedia/src/test/vid_codec_test.c b/pjmedia/src/test/vid_codec_test.c
new file mode 100644
index 0000000..e98fbe9
--- /dev/null
+++ b/pjmedia/src/test/vid_codec_test.c
@@ -0,0 +1,486 @@
+/* $Id: vid_codec_test.c 4084 2012-04-25 07:13:05Z ming $ */
+/*
+ * Copyright (C) 2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 "test.h"
+#include <pjmedia-codec/ffmpeg_vid_codecs.h>
+#include <pjmedia-videodev/videodev.h>
+#include <pjmedia/vid_codec.h>
+#include <pjmedia/port.h>
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+
+#define THIS_FILE "vid_codec.c"
+
+/*
+ * Capture device setting:
+ * -1 = colorbar,
+ * -2 = any non-colorbar capture device (first found)
+ * x = specified capture device id
+ */
+#define CAPTURE_DEV -1
+
+
+typedef struct codec_port_data_t
+{
+ pjmedia_vid_codec *codec;
+ pjmedia_vid_port *rdr_port;
+ pj_uint8_t *enc_buf;
+ pj_size_t enc_buf_size;
+ pj_uint8_t *pack_buf;
+ pj_size_t pack_buf_size;
+} codec_port_data_t;
+
+static pj_status_t codec_on_event(pjmedia_event *event,
+ void *user_data)
+{
+ codec_port_data_t *port_data = (codec_port_data_t*)user_data;
+
+ if (event->type == PJMEDIA_EVENT_FMT_CHANGED) {
+ pjmedia_vid_codec *codec = port_data->codec;
+ pjmedia_vid_codec_param codec_param;
+ pj_status_t status;
+
+ status = pjmedia_vid_codec_get_param(codec, &codec_param);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = pjmedia_vid_dev_stream_set_cap(
+ pjmedia_vid_port_get_stream(port_data->rdr_port),
+ PJMEDIA_VID_DEV_CAP_FORMAT,
+ &codec_param.dec_fmt);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t codec_put_frame(pjmedia_port *port,
+ pjmedia_frame *frame)
+{
+ enum { MAX_PACKETS = 50 };
+ codec_port_data_t *port_data = (codec_port_data_t*)port->port_data.pdata;
+ pj_status_t status;
+ pjmedia_vid_codec *codec = port_data->codec;
+ unsigned enc_cnt = 0;
+ pj_uint8_t *enc_buf;
+ unsigned enc_size_left;
+ pjmedia_frame enc_frames[MAX_PACKETS];
+ pj_bool_t has_more = PJ_FALSE;
+
+ enc_buf = port_data->enc_buf;
+ enc_size_left = port_data->enc_buf_size;
+
+ /*
+ * Encode
+ */
+ enc_frames[enc_cnt].buf = enc_buf;
+ enc_frames[enc_cnt].size = enc_size_left;
+
+ status = pjmedia_vid_codec_encode_begin(codec, NULL, frame, enc_size_left,
+ &enc_frames[enc_cnt], &has_more);
+ if (status != PJ_SUCCESS) goto on_error;
+
+ enc_buf += enc_frames[enc_cnt].size;
+ enc_size_left -= enc_frames[enc_cnt].size;
+
+ ++enc_cnt;
+ while (has_more) {
+ enc_frames[enc_cnt].buf = enc_buf;
+ enc_frames[enc_cnt].size = enc_size_left;
+
+ status = pjmedia_vid_codec_encode_more(codec, enc_size_left,
+ &enc_frames[enc_cnt],
+ &has_more);
+ if (status != PJ_SUCCESS)
+ break;
+
+ enc_buf += enc_frames[enc_cnt].size;
+ enc_size_left -= enc_frames[enc_cnt].size;
+
+ ++enc_cnt;
+
+ if (enc_cnt >= MAX_PACKETS) {
+ assert(!"Too many packets!");
+ break;
+ }
+ }
+
+ /*
+ * Decode
+ */
+ status = pjmedia_vid_codec_decode(codec, enc_cnt, enc_frames,
+ frame->size, frame);
+ if (status != PJ_SUCCESS) goto on_error;
+
+ /* Display */
+ status = pjmedia_port_put_frame(
+ pjmedia_vid_port_get_passive_port(port_data->rdr_port),
+ frame);
+ if (status != PJ_SUCCESS) goto on_error;
+
+ return PJ_SUCCESS;
+
+on_error:
+ pj_perror(3, THIS_FILE, status, "codec_put_frame() error");
+ return status;
+}
+
+static const char* dump_codec_info(const pjmedia_vid_codec_info *info)
+{
+ static char str[80];
+ unsigned i;
+ char *p = str;
+
+ /* Raw format ids */
+ for (i=0; (i<info->dec_fmt_id_cnt) && (p-str+5<sizeof(str)); ++i) {
+ pj_memcpy(p, &info->dec_fmt_id[i], 4);
+ p += 4;
+ *p++ = ' ';
+ }
+ *p = '\0';
+
+ return str;
+}
+
+static int enum_codecs()
+{
+ unsigned i, cnt;
+ pjmedia_vid_codec_info info[PJMEDIA_CODEC_MGR_MAX_CODECS];
+ pj_status_t status;
+
+ PJ_LOG(3, (THIS_FILE, " codec enums"));
+ cnt = PJ_ARRAY_SIZE(info);
+ status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &cnt, info, NULL);
+ if (status != PJ_SUCCESS)
+ return 100;
+
+ for (i = 0; i < cnt; ++i) {
+ PJ_LOG(3, (THIS_FILE, " %-16.*s %c%c %s",
+ info[i].encoding_name.slen, info[i].encoding_name.ptr,
+ (info[i].dir & PJMEDIA_DIR_ENCODING? 'E' : ' '),
+ (info[i].dir & PJMEDIA_DIR_DECODING? 'D' : ' '),
+ dump_codec_info(&info[i])));
+ }
+
+ return PJ_SUCCESS;
+}
+
+static int encode_decode_test(pj_pool_t *pool, const char *codec_id,
+ pjmedia_vid_packing packing)
+{
+ const pj_str_t port_name = {"codec", 5};
+
+ pjmedia_vid_codec *codec=NULL;
+ pjmedia_port codec_port;
+ codec_port_data_t codec_port_data;
+ pjmedia_vid_codec_param codec_param;
+ const pjmedia_vid_codec_info *codec_info;
+ const char *packing_name;
+ pjmedia_vid_dev_index cap_idx, rdr_idx;
+ pjmedia_vid_port *capture=NULL, *renderer=NULL;
+ pjmedia_vid_port_param vport_param;
+ pjmedia_video_format_detail *vfd;
+ char codec_name[5];
+ pj_status_t status;
+ int rc = 0;
+
+ switch (packing) {
+ case PJMEDIA_VID_PACKING_PACKETS:
+ packing_name = "framed";
+ break;
+ case PJMEDIA_VID_PACKING_WHOLE:
+ packing_name = "whole";
+ break;
+ default:
+ packing_name = "unknown";
+ break;
+ }
+
+ PJ_LOG(3, (THIS_FILE, " encode decode test: codec=%s, packing=%s",
+ codec_id, packing_name));
+
+ /* Lookup codec */
+ {
+ pj_str_t codec_id_st;
+ unsigned info_cnt = 1;
+
+ /* Lookup codec */
+ pj_cstr(&codec_id_st, codec_id);
+ status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, &codec_id_st,
+ &info_cnt,
+ &codec_info, NULL);
+ if (status != PJ_SUCCESS) {
+ rc = 205; goto on_return;
+ }
+ }
+
+
+#if CAPTURE_DEV == -1
+ /* Lookup colorbar source */
+ status = pjmedia_vid_dev_lookup("Colorbar", "Colorbar generator", &cap_idx);
+ if (status != PJ_SUCCESS) {
+ rc = 206; goto on_return;
+ }
+#elif CAPTURE_DEV == -2
+ /* Lookup any first non-colorbar source */
+ {
+ unsigned i, cnt;
+ pjmedia_vid_dev_info info;
+
+ cap_idx = -1;
+ cnt = pjmedia_vid_dev_count();
+ for (i = 0; i < cnt; ++i) {
+ status = pjmedia_vid_dev_get_info(i, &info);
+ if (status != PJ_SUCCESS) {
+ rc = 206; goto on_return;
+ }
+ if (info.dir & PJMEDIA_DIR_CAPTURE &&
+ pj_ansi_stricmp(info.driver, "Colorbar"))
+ {
+ cap_idx = i;
+ break;
+ }
+ }
+
+ if (cap_idx == -1) {
+ status = PJ_ENOTFOUND;
+ rc = 206; goto on_return;
+ }
+ }
+#else
+ cap_idx = CAPTURE_DEV;
+#endif
+
+ /* Lookup SDL renderer */
+ status = pjmedia_vid_dev_lookup("SDL", "SDL renderer", &rdr_idx);
+ if (status != PJ_SUCCESS) {
+ rc = 207; goto on_return;
+ }
+
+ /* Prepare codec */
+ {
+ pj_str_t codec_id_st;
+ unsigned info_cnt = 1;
+ const pjmedia_vid_codec_info *codec_info;
+
+ /* Lookup codec */
+ pj_cstr(&codec_id_st, codec_id);
+ status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, &codec_id_st,
+ &info_cnt,
+ &codec_info, NULL);
+ if (status != PJ_SUCCESS) {
+ rc = 245; goto on_return;
+ }
+ status = pjmedia_vid_codec_mgr_get_default_param(NULL, codec_info,
+ &codec_param);
+ if (status != PJ_SUCCESS) {
+ rc = 246; goto on_return;
+ }
+
+ codec_param.packing = packing;
+
+ /* Open codec */
+ status = pjmedia_vid_codec_mgr_alloc_codec(NULL, codec_info,
+ &codec);
+ if (status != PJ_SUCCESS) {
+ rc = 250; goto on_return;
+ }
+
+ status = pjmedia_vid_codec_init(codec, pool);
+ if (status != PJ_SUCCESS) {
+ rc = 251; goto on_return;
+ }
+
+ status = pjmedia_vid_codec_open(codec, &codec_param);
+ if (status != PJ_SUCCESS) {
+ rc = 252; goto on_return;
+ }
+
+ /* After opened, codec will update the param, let's sync encoder &
+ * decoder format detail.
+ */
+ codec_param.dec_fmt.det = codec_param.enc_fmt.det;
+
+ /* Subscribe to codec events */
+ pjmedia_event_subscribe(NULL, &codec_on_event, &codec_port_data,
+ codec);
+ }
+
+ pjmedia_vid_port_param_default(&vport_param);
+
+ /* Create capture, set it to active (master) */
+ status = pjmedia_vid_dev_default_param(pool, cap_idx,
+ &vport_param.vidparam);
+ if (status != PJ_SUCCESS) {
+ rc = 220; goto on_return;
+ }
+ pjmedia_format_copy(&vport_param.vidparam.fmt, &codec_param.dec_fmt);
+ vport_param.vidparam.dir = PJMEDIA_DIR_CAPTURE;
+ vport_param.active = PJ_TRUE;
+
+ if (vport_param.vidparam.fmt.detail_type != PJMEDIA_FORMAT_DETAIL_VIDEO) {
+ rc = 221; goto on_return;
+ }
+
+ vfd = pjmedia_format_get_video_format_detail(&vport_param.vidparam.fmt,
+ PJ_TRUE);
+ if (vfd == NULL) {
+ rc = 225; goto on_return;
+ }
+
+ status = pjmedia_vid_port_create(pool, &vport_param, &capture);
+ if (status != PJ_SUCCESS) {
+ rc = 226; goto on_return;
+ }
+
+ /* Create renderer, set it to passive (slave) */
+ vport_param.active = PJ_FALSE;
+ vport_param.vidparam.dir = PJMEDIA_DIR_RENDER;
+ vport_param.vidparam.rend_id = rdr_idx;
+ vport_param.vidparam.disp_size = vfd->size;
+
+ status = pjmedia_vid_port_create(pool, &vport_param, &renderer);
+ if (status != PJ_SUCCESS) {
+ rc = 230; goto on_return;
+ }
+
+ /* Init codec port */
+ pj_bzero(&codec_port, sizeof(codec_port));
+ status = pjmedia_port_info_init2(&codec_port.info, &port_name, 0x1234,
+ PJMEDIA_DIR_ENCODING,
+ &codec_param.dec_fmt);
+ if (status != PJ_SUCCESS) {
+ rc = 260; goto on_return;
+ }
+
+ codec_port_data.codec = codec;
+ codec_port_data.rdr_port = renderer;
+ codec_port_data.enc_buf_size = codec_param.dec_fmt.det.vid.size.w *
+ codec_param.dec_fmt.det.vid.size.h * 4;
+ codec_port_data.enc_buf = pj_pool_alloc(pool,
+ codec_port_data.enc_buf_size);
+ codec_port_data.pack_buf_size = codec_port_data.enc_buf_size;
+ codec_port_data.pack_buf = pj_pool_alloc(pool,
+ codec_port_data.pack_buf_size);
+
+ codec_port.put_frame = &codec_put_frame;
+ codec_port.port_data.pdata = &codec_port_data;
+
+ /* Connect capture to codec port */
+ status = pjmedia_vid_port_connect(capture,
+ &codec_port,
+ PJ_FALSE);
+ if (status != PJ_SUCCESS) {
+ rc = 270; goto on_return;
+ }
+
+ PJ_LOG(3, (THIS_FILE, " starting codec test: %s<->%.*s %dx%d",
+ pjmedia_fourcc_name(codec_param.dec_fmt.id, codec_name),
+ codec_info->encoding_name.slen,
+ codec_info->encoding_name.ptr,
+ codec_param.dec_fmt.det.vid.size.w,
+ codec_param.dec_fmt.det.vid.size.h
+ ));
+
+ /* Start streaming.. */
+ status = pjmedia_vid_port_start(renderer);
+ if (status != PJ_SUCCESS) {
+ rc = 275; goto on_return;
+ }
+ status = pjmedia_vid_port_start(capture);
+ if (status != PJ_SUCCESS) {
+ rc = 280; goto on_return;
+ }
+
+ /* Sleep while the video is being displayed... */
+ pj_thread_sleep(10000);
+
+on_return:
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(3, (THIS_FILE, status, " error"));
+ }
+ if (capture)
+ pjmedia_vid_port_stop(capture);
+ if (renderer)
+ pjmedia_vid_port_stop(renderer);
+ if (capture)
+ pjmedia_vid_port_destroy(capture);
+ if (renderer)
+ pjmedia_vid_port_destroy(renderer);
+ if (codec) {
+ pjmedia_event_unsubscribe(NULL, &codec_on_event, &codec_port_data,
+ codec);
+ pjmedia_vid_codec_close(codec);
+ pjmedia_vid_codec_mgr_dealloc_codec(NULL, codec);
+ }
+
+ return rc;
+}
+
+int vid_codec_test(void)
+{
+ pj_pool_t *pool;
+ int rc = 0;
+ pj_status_t status;
+ int orig_log_level;
+
+ orig_log_level = pj_log_get_level();
+ pj_log_set_level(3);
+
+ PJ_LOG(3, (THIS_FILE, "Performing video codec tests.."));
+
+ pool = pj_pool_create(mem, "Vid codec test", 256, 256, 0);
+
+ status = pjmedia_vid_dev_subsys_init(mem);
+ if (status != PJ_SUCCESS)
+ return -10;
+
+#if PJMEDIA_HAS_FFMPEG_VID_CODEC
+ status = pjmedia_codec_ffmpeg_vid_init(NULL, mem);
+ if (status != PJ_SUCCESS)
+ return -20;
+#endif
+
+ rc = enum_codecs();
+ if (rc != 0)
+ goto on_return;
+
+ rc = encode_decode_test(pool, "h263-1998", PJMEDIA_VID_PACKING_WHOLE);
+ if (rc != 0)
+ goto on_return;
+
+ rc = encode_decode_test(pool, "h263-1998", PJMEDIA_VID_PACKING_PACKETS);
+ if (rc != 0)
+ goto on_return;
+
+on_return:
+#if PJMEDIA_HAS_FFMPEG_VID_CODEC
+ pjmedia_codec_ffmpeg_vid_deinit();
+#endif
+ pjmedia_vid_dev_subsys_shutdown();
+ pj_pool_release(pool);
+ pj_log_set_level(orig_log_level);
+
+ return rc;
+}
+
+
+#endif /* PJMEDIA_HAS_VIDEO */
diff --git a/pjmedia/src/test/vid_dev_test.c b/pjmedia/src/test/vid_dev_test.c
new file mode 100644
index 0000000..82f3def
--- /dev/null
+++ b/pjmedia/src/test/vid_dev_test.c
@@ -0,0 +1,303 @@
+/* $Id: vid_dev_test.c 4084 2012-04-25 07:13:05Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 "test.h"
+#include <pjmedia-audiodev/audiodev.h>
+#include <pjmedia-codec/ffmpeg_vid_codecs.h>
+#include <pjmedia/vid_codec.h>
+#include <pjmedia_videodev.h>
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+
+#define THIS_FILE "vid_dev_test.c"
+#define LOOP_DURATION 10
+
+static pj_bool_t is_quitting = PJ_FALSE;
+
+static const char *vid_dir_name(pjmedia_dir dir)
+{
+ switch (dir) {
+ case PJMEDIA_DIR_CAPTURE:
+ return "capture";
+ case PJMEDIA_DIR_RENDER:
+ return "render";
+ case PJMEDIA_DIR_CAPTURE_RENDER:
+ return "capture & render";
+ default:
+ return "unknown";
+ }
+}
+
+static int enum_devs(void)
+{
+ unsigned i, dev_cnt;
+ pj_status_t status;
+
+ PJ_LOG(3, (THIS_FILE, " Enum video devices:"));
+ dev_cnt = pjmedia_vid_dev_count();
+ for (i = 0; i < dev_cnt; ++i) {
+ pjmedia_vid_dev_info di;
+ status = pjmedia_vid_dev_get_info(i, &di);
+ if (status == PJ_SUCCESS) {
+ unsigned j;
+
+ PJ_LOG(3, (THIS_FILE, " %3d: %s (%s) - %s", i, di.name, di.driver,
+ vid_dir_name(di.dir)));
+
+ PJ_LOG(3,(THIS_FILE, " Supported formats:"));
+ for (j=0; j<di.fmt_cnt; ++j) {
+ const pjmedia_video_format_info *vfi;
+
+ vfi = pjmedia_get_video_format_info(NULL, di.fmt[j].id);
+ PJ_LOG(3,(THIS_FILE, " %s",
+ (vfi ? vfi->name : "unknown")));
+ }
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t vid_event_cb(pjmedia_event *event,
+ void *user_data)
+{
+ PJ_UNUSED_ARG(user_data);
+
+ if (event->type == PJMEDIA_EVENT_WND_CLOSED)
+ is_quitting = PJ_TRUE;
+
+ return PJ_SUCCESS;
+}
+
+static int capture_render_loopback(int cap_dev_id, int rend_dev_id,
+ const pjmedia_format *fmt)
+{
+ pj_pool_t *pool;
+ pjmedia_vid_port *capture=NULL, *renderer=NULL;
+ pjmedia_vid_dev_info cdi, rdi;
+ pjmedia_vid_port_param param;
+ pjmedia_video_format_detail *vfd;
+ pj_status_t status;
+ int rc = 0, i;
+
+ pool = pj_pool_create(mem, "vidloop", 1000, 1000, NULL);
+
+ status = pjmedia_vid_dev_get_info(cap_dev_id, &cdi);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ status = pjmedia_vid_dev_get_info(rend_dev_id, &rdi);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ PJ_LOG(3,(THIS_FILE,
+ " %s (%s) ===> %s (%s)\t%s\t%dx%d\t@%d:%d fps",
+ cdi.name, cdi.driver, rdi.name, rdi.driver,
+ pjmedia_get_video_format_info(NULL, fmt->id)->name,
+ fmt->det.vid.size.w, fmt->det.vid.size.h,
+ fmt->det.vid.fps.num, fmt->det.vid.fps.denum));
+
+ pjmedia_vid_port_param_default(&param);
+
+ /* Create capture, set it to active (master) */
+ status = pjmedia_vid_dev_default_param(pool, cap_dev_id,
+ &param.vidparam);
+ if (status != PJ_SUCCESS) {
+ rc = 100; goto on_return;
+ }
+ param.vidparam.dir = PJMEDIA_DIR_CAPTURE;
+ param.vidparam.fmt = *fmt;
+ param.active = PJ_TRUE;
+
+ if (param.vidparam.fmt.detail_type != PJMEDIA_FORMAT_DETAIL_VIDEO) {
+ rc = 103; goto on_return;
+ }
+
+ vfd = pjmedia_format_get_video_format_detail(&param.vidparam.fmt, PJ_TRUE);
+ if (vfd == NULL) {
+ rc = 105; goto on_return;
+ }
+
+ status = pjmedia_vid_port_create(pool, &param, &capture);
+ if (status != PJ_SUCCESS) {
+ rc = 110; goto on_return;
+ }
+
+ /* Create renderer, set it to passive (slave) */
+ status = pjmedia_vid_dev_default_param(pool, rend_dev_id,
+ &param.vidparam);
+ if (status != PJ_SUCCESS) {
+ rc = 120; goto on_return;
+ }
+
+ param.active = PJ_FALSE;
+ param.vidparam.dir = PJMEDIA_DIR_RENDER;
+ param.vidparam.rend_id = rend_dev_id;
+ param.vidparam.fmt = *fmt;
+ param.vidparam.disp_size = vfd->size;
+
+ status = pjmedia_vid_port_create(pool, &param, &renderer);
+ if (status != PJ_SUCCESS) {
+ rc = 130; goto on_return;
+ }
+
+ /* Set event handler */
+ pjmedia_event_subscribe(NULL, &vid_event_cb, NULL, renderer);
+
+ /* Connect capture to renderer */
+ status = pjmedia_vid_port_connect(
+ capture,
+ pjmedia_vid_port_get_passive_port(renderer),
+ PJ_FALSE);
+ if (status != PJ_SUCCESS) {
+ rc = 140; goto on_return;
+ }
+
+ /* Start streaming.. */
+ status = pjmedia_vid_port_start(renderer);
+ if (status != PJ_SUCCESS) {
+ rc = 150; goto on_return;
+ }
+ status = pjmedia_vid_port_start(capture);
+ if (status != PJ_SUCCESS) {
+ rc = 160; goto on_return;
+ }
+
+ /* Sleep while the webcam is being displayed... */
+ for (i = 0; i < LOOP_DURATION*10 && (!is_quitting); i++) {
+ pj_thread_sleep(100);
+ }
+
+on_return:
+ if (status != PJ_SUCCESS)
+ PJ_PERROR(3, (THIS_FILE, status, " error"));
+
+ if (capture)
+ pjmedia_vid_port_stop(capture);
+ if (renderer)
+ pjmedia_vid_port_stop(renderer);
+ if (capture)
+ pjmedia_vid_port_destroy(capture);
+ if (renderer) {
+ pjmedia_event_unsubscribe(NULL, &vid_event_cb, NULL, renderer);
+ pjmedia_vid_port_destroy(renderer);
+ }
+
+ pj_pool_release(pool);
+ return rc;
+}
+
+static int loopback_test(void)
+{
+ unsigned count, i;
+ pjmedia_format_id test_fmts[] = {
+ PJMEDIA_FORMAT_YUY2
+ };
+ pjmedia_rect_size test_sizes[] = {
+ {176,144}, /* QCIF */
+ {352,288}, /* CIF */
+ {704,576} /* 4CIF */
+ };
+ pjmedia_ratio test_fpses[] = {
+ {25, 1},
+ {30, 1},
+ };
+ pj_status_t status;
+
+ PJ_LOG(3, (THIS_FILE, " Loopback tests (prepare you webcams):"));
+
+ count = pjmedia_vid_dev_count();
+ for (i=0; i<count; ++i) {
+ pjmedia_vid_dev_info cdi;
+ unsigned j;
+
+ status = pjmedia_vid_dev_get_info(i, &cdi);
+ if (status != PJ_SUCCESS)
+ return -300;
+
+ /* Only interested with capture device */
+ if ((cdi.dir & PJMEDIA_DIR_CAPTURE) == 0)
+ continue;
+
+ for (j=i+1; j<count; ++j) {
+ pjmedia_vid_dev_info rdi;
+ unsigned k;
+
+ status = pjmedia_vid_dev_get_info(j, &rdi);
+ if (status != PJ_SUCCESS)
+ return -310;
+
+ /* Only interested with render device */
+ if ((rdi.dir & PJMEDIA_DIR_RENDER) == 0)
+ continue;
+
+ /* Test with the format, size, and fps combinations */
+ for (k=0; k<PJ_ARRAY_SIZE(test_fmts); ++k) {
+ unsigned l;
+
+ for (l=0; l<PJ_ARRAY_SIZE(test_sizes); ++l) {
+ unsigned m;
+
+ for (m=0; m<PJ_ARRAY_SIZE(test_fpses); ++m) {
+ pjmedia_format fmt;
+
+ pjmedia_format_init_video(&fmt, test_fmts[k],
+ test_sizes[l].w,
+ test_sizes[l].h,
+ test_fpses[m].num,
+ test_fpses[m].denum);
+
+ capture_render_loopback(i, j, &fmt);
+ }
+ }
+ } /* k */
+
+ }
+ }
+
+ return 0;
+}
+
+int vid_dev_test(void)
+{
+ int rc = 0;
+ pj_status_t status;
+
+ status = pjmedia_vid_dev_subsys_init(mem);
+ if (status != PJ_SUCCESS)
+ return -10;
+
+ rc = enum_devs();
+ if (rc != 0)
+ goto on_return;
+
+ rc = loopback_test();
+ if (rc != 0)
+ goto on_return;
+
+on_return:
+ pjmedia_vid_dev_subsys_shutdown();
+
+ return rc;
+}
+
+
+#endif /* PJMEDIA_HAS_VIDEO */
diff --git a/pjmedia/src/test/vid_port_test.c b/pjmedia/src/test/vid_port_test.c
new file mode 100644
index 0000000..42d6555
--- /dev/null
+++ b/pjmedia/src/test/vid_port_test.c
@@ -0,0 +1,250 @@
+/* $Id: vid_port_test.c 4084 2012-04-25 07:13:05Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 "test.h"
+#include <pjmedia-audiodev/audiodev.h>
+#include <pjmedia-codec/ffmpeg_vid_codecs.h>
+#include <pjmedia/vid_codec.h>
+#include <pjmedia_videodev.h>
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+
+#define THIS_FILE "vid_dev_test.c"
+#define LOOP_DURATION 6
+
+static pj_bool_t is_quitting = PJ_FALSE;
+
+static pj_status_t vid_event_cb(pjmedia_event *event,
+ void *user_data)
+{
+ PJ_UNUSED_ARG(user_data);
+
+ if (event->type == PJMEDIA_EVENT_WND_CLOSED)
+ is_quitting = PJ_TRUE;
+
+ return PJ_SUCCESS;
+}
+
+static int capture_render_loopback(pj_bool_t active,
+ int cap_dev_id, int rend_dev_id,
+ const pjmedia_format *fmt)
+{
+ pj_pool_t *pool;
+ pjmedia_vid_port *capture=NULL, *renderer=NULL;
+ pjmedia_vid_dev_info cdi, rdi;
+ pjmedia_vid_port_param param;
+ pjmedia_video_format_detail *vfd;
+ pj_status_t status;
+ int rc = 0, i;
+
+ pool = pj_pool_create(mem, "vidportloop", 1000, 1000, NULL);
+
+ status = pjmedia_vid_dev_get_info(cap_dev_id, &cdi);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ status = pjmedia_vid_dev_get_info(rend_dev_id, &rdi);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ PJ_LOG(3,(THIS_FILE,
+ " %s (%s) ===> %s (%s)\t%s\t%dx%d\t@%d:%d fps",
+ cdi.name, cdi.driver, rdi.name, rdi.driver,
+ pjmedia_get_video_format_info(NULL, fmt->id)->name,
+ fmt->det.vid.size.w, fmt->det.vid.size.h,
+ fmt->det.vid.fps.num, fmt->det.vid.fps.denum));
+
+ pjmedia_vid_port_param_default(&param);
+
+ /* Create capture, set it to active (master) */
+ status = pjmedia_vid_dev_default_param(pool, cap_dev_id,
+ &param.vidparam);
+ if (status != PJ_SUCCESS) {
+ rc = 100; goto on_return;
+ }
+ param.vidparam.dir = PJMEDIA_DIR_CAPTURE;
+ param.vidparam.fmt = *fmt;
+ param.active = (active? PJ_TRUE: PJ_FALSE);
+
+ if (param.vidparam.fmt.detail_type != PJMEDIA_FORMAT_DETAIL_VIDEO) {
+ rc = 103; goto on_return;
+ }
+
+ vfd = pjmedia_format_get_video_format_detail(&param.vidparam.fmt, PJ_TRUE);
+ if (vfd == NULL) {
+ rc = 105; goto on_return;
+ }
+
+ status = pjmedia_vid_port_create(pool, &param, &capture);
+ if (status != PJ_SUCCESS) {
+ rc = 110; goto on_return;
+ }
+
+ /* Create renderer, set it to passive (slave) */
+ status = pjmedia_vid_dev_default_param(pool, rend_dev_id,
+ &param.vidparam);
+ if (status != PJ_SUCCESS) {
+ rc = 120; goto on_return;
+ }
+
+ param.active = (active? PJ_FALSE: PJ_TRUE);
+ param.vidparam.dir = PJMEDIA_DIR_RENDER;
+ param.vidparam.rend_id = rend_dev_id;
+ param.vidparam.fmt = *fmt;
+ param.vidparam.disp_size = vfd->size;
+
+ status = pjmedia_vid_port_create(pool, &param, &renderer);
+ if (status != PJ_SUCCESS) {
+ rc = 130; goto on_return;
+ }
+
+ /* Set event handler */
+ pjmedia_event_subscribe(NULL, &vid_event_cb, NULL, renderer);
+
+ /* Connect capture to renderer */
+ status = pjmedia_vid_port_connect(
+ (active? capture: renderer),
+ pjmedia_vid_port_get_passive_port(active? renderer: capture),
+ PJ_FALSE);
+ if (status != PJ_SUCCESS) {
+ rc = 140; goto on_return;
+ }
+
+ /* Start streaming.. */
+ status = pjmedia_vid_port_start(renderer);
+ if (status != PJ_SUCCESS) {
+ rc = 150; goto on_return;
+ }
+ status = pjmedia_vid_port_start(capture);
+ if (status != PJ_SUCCESS) {
+ rc = 160; goto on_return;
+ }
+
+ /* Sleep while the webcam is being displayed... */
+ for (i = 0; i < LOOP_DURATION*10 && (!is_quitting); i++) {
+ pj_thread_sleep(100);
+ }
+
+on_return:
+ if (status != PJ_SUCCESS)
+ PJ_PERROR(3, (THIS_FILE, status, " error"));
+
+ if (capture)
+ pjmedia_vid_port_stop(capture);
+ if (renderer)
+ pjmedia_vid_port_stop(renderer);
+ if (capture)
+ pjmedia_vid_port_destroy(capture);
+ if (renderer) {
+ pjmedia_event_unsubscribe(NULL, &vid_event_cb, NULL, renderer);
+ pjmedia_vid_port_destroy(renderer);
+ }
+
+ pj_pool_release(pool);
+ return rc;
+}
+
+static int find_device(pjmedia_dir dir,
+ pj_bool_t has_callback)
+{
+ unsigned i, count = pjmedia_vid_dev_count();
+
+ for (i = 0; i < count; ++i) {
+ pjmedia_vid_dev_info cdi;
+
+ if (pjmedia_vid_dev_get_info(i, &cdi) != PJ_SUCCESS)
+ continue;
+ if ((cdi.dir & dir) != 0 && cdi.has_callback == has_callback)
+ return i;
+ }
+
+ return -999;
+}
+
+static int vidport_test(void)
+{
+ int i, j, k, l;
+ int cap_id, rend_id;
+ pjmedia_format_id test_fmts[] = {
+ PJMEDIA_FORMAT_RGBA,
+ PJMEDIA_FORMAT_I420
+ };
+
+ PJ_LOG(3, (THIS_FILE, " Video port tests:"));
+
+ /* Capturer's role: active/passive. */
+ for (i = 1; i >= 0; i--) {
+ /* Capturer's device has_callback: TRUE/FALSE. */
+ for (j = 1; j >= 0; j--) {
+ cap_id = find_device(PJMEDIA_DIR_CAPTURE, j);
+ if (cap_id < 0)
+ continue;
+
+ /* Renderer's device has callback: TRUE/FALSE. */
+ for (k = 1; k >= 0; k--) {
+ rend_id = find_device(PJMEDIA_DIR_RENDER, k);
+ if (rend_id < 0)
+ continue;
+
+ /* Check various formats to test format conversion. */
+ for (l = 0; l < PJ_ARRAY_SIZE(test_fmts); ++l) {
+ pjmedia_format fmt;
+
+ PJ_LOG(3,(THIS_FILE,
+ "capturer %s (stream: %s) ===> "
+ "renderer %s (stream: %s)",
+ (i? "active": "passive"),
+ (j? "active": "passive"),
+ (i? "passive": "active"),
+ (k? "active": "passive")));
+
+ pjmedia_format_init_video(&fmt, test_fmts[l],
+ 640, 480, 25, 1);
+ capture_render_loopback(i, cap_id, rend_id, &fmt);
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+int vid_port_test(void)
+{
+ int rc = 0;
+ pj_status_t status;
+
+ status = pjmedia_vid_dev_subsys_init(mem);
+ if (status != PJ_SUCCESS)
+ return -10;
+
+ rc = vidport_test();
+ if (rc != 0)
+ goto on_return;
+
+on_return:
+ pjmedia_vid_dev_subsys_shutdown();
+
+ return rc;
+}
+
+
+#endif /* PJMEDIA_HAS_VIDEO */
diff --git a/pjmedia/src/test/wince_main.c b/pjmedia/src/test/wince_main.c
new file mode 100644
index 0000000..c36c375
--- /dev/null
+++ b/pjmedia/src/test/wince_main.c
@@ -0,0 +1,72 @@
+/* $Id: wince_main.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 "test.h"
+#include <windows.h>
+#include <stdio.h>
+
+#define TITLE "PJMEDIA Test"
+#define CAPTION "This will start pjmedia test. Please do not use the PDA while the test is in progress. The test may take couple of minutes to complete, and you will be notified again when it completes"
+
+static FILE *fLog;
+
+static void log_writer_cb(int level, const char *data, int len)
+{
+ PJ_UNUSED_ARG(level);
+
+ fwrite(data, len, 1, fLog);
+}
+
+
+int WINAPI WinMain(HINSTANCE hInstance,
+ HINSTANCE hPrevInstance,
+ LPTSTR lpCmdLine,
+ int nCmdShow)
+{
+ int rc;
+
+ PJ_UNUSED_ARG(hInstance);
+ PJ_UNUSED_ARG(hPrevInstance);
+ PJ_UNUSED_ARG(lpCmdLine);
+ PJ_UNUSED_ARG(nCmdShow);
+
+ rc = MessageBox(0, TEXT(CAPTION), TEXT(TITLE), MB_OKCANCEL);
+ if (rc != IDOK)
+ return TRUE;
+
+ fLog = fopen("\\pjmedia-test.txt", "wt");
+ if (fLog == NULL) {
+ MessageBox(0, TEXT("Unable to create result file"), TEXT(TITLE), MB_OK);
+ return TRUE;
+ }
+ pj_log_set_log_func(&log_writer_cb);
+ rc = test_main();
+
+ fclose(fLog);
+
+ if (rc != 0) {
+ MessageBox(0, TEXT("Test failed"), TEXT(TITLE), MB_OK);
+ return TRUE;
+ }
+
+ MessageBox(0, TEXT("Test has been successful. Please see the result in \"\\pjmedia-test.txt\" file"),
+ TEXT(TITLE), 0);
+ return TRUE;
+}
+
diff --git a/pjmedia/src/test/wsola_test.c b/pjmedia/src/test/wsola_test.c
new file mode 100644
index 0000000..45f6ab7
--- /dev/null
+++ b/pjmedia/src/test/wsola_test.c
@@ -0,0 +1,373 @@
+/* $Id: wsola_test.c 3550 2011-05-05 05:33:27Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <pjmedia/wsola.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/os.h>
+#include <stdio.h>
+#include <assert.h>
+
+#define CLOCK_RATE 16000
+#define SAMPLES_PER_FRAME (10 * CLOCK_RATE / 1000)
+
+#define RESET() memset(buf1, 0, sizeof(buf1)), \
+ memset(buf2, 0, sizeof(buf2)), \
+ memset(frm1, 0, sizeof(frm1)), \
+ memset(frm2, 0, sizeof(frm2))
+
+#if 0
+void test_find_pitch(void)
+{
+ enum { ON = 111, FRM_PART_LEN=20 };
+ short buf2[SAMPLES_PER_FRAME*2], buf1[SAMPLES_PER_FRAME*2],
+ frm2[SAMPLES_PER_FRAME], frm1[SAMPLES_PER_FRAME];
+ short *ref, *pos;
+
+ /* Case 1. all contiguous */
+ RESET();
+ ref = buf1 + 10;
+ *ref = ON;
+ frm1[0] = ON;
+
+ pos = pjmedia_wsola_find_pitch(frm1, SAMPLES_PER_FRAME, NULL, 0,
+ buf1, SAMPLES_PER_FRAME*2, NULL, 0, PJ_TRUE);
+ assert(pos == ref);
+
+ /* Case 2: contiguous buffer, non-contiguous frame */
+ RESET();
+ ref = buf1 + 17;
+ *ref = ON;
+ *(ref+FRM_PART_LEN) = ON;
+ frm1[0] = ON;
+ frm2[0] = ON;
+
+ /* Noise */
+ buf1[0] = ON;
+
+ pos = pjmedia_wsola_find_pitch(frm1, FRM_PART_LEN, frm2, SAMPLES_PER_FRAME - FRM_PART_LEN,
+ buf1, SAMPLES_PER_FRAME*2, NULL, 0, PJ_TRUE);
+ assert(pos == ref);
+
+ /* Case 3: non-contiguous buffer, contiguous frame, found in buf1 */
+ RESET();
+ ref = buf1 + 17;
+ *ref = ON;
+ buf2[17] = ON;
+ frm1[0] = ON;
+ frm1[FRM_PART_LEN] = ON;
+
+ /* Noise */
+ buf1[0] = ON;
+
+ pos = pjmedia_wsola_find_pitch(frm1, SAMPLES_PER_FRAME, NULL, 0,
+ buf1, FRM_PART_LEN,
+ buf2, SAMPLES_PER_FRAME,
+ PJ_TRUE);
+
+ assert(pos == ref);
+}
+#endif
+
+int expand(pj_pool_t *pool, const char *filein, const char *fileout,
+ int expansion_rate100, int lost_rate10, int lost_burst)
+{
+ enum { LOST_RATE = 10 };
+ FILE *in, *out;
+ short frame[SAMPLES_PER_FRAME];
+ pjmedia_wsola *wsola;
+ pj_timestamp elapsed, zero;
+ unsigned samples;
+ int last_lost = 0;
+
+ /* Lost burst must be > 0 */
+ assert(lost_rate10==0 || lost_burst > 0);
+
+ in = fopen(filein, "rb");
+ if (!in) return 1;
+ out = fopen(fileout, "wb");
+ if (!out) return 1;
+
+ pjmedia_wsola_create(pool, CLOCK_RATE, SAMPLES_PER_FRAME, 1, 0, &wsola);
+
+ samples = 0;
+ elapsed.u64 = 0;
+
+ while (fread(frame, SAMPLES_PER_FRAME*2, 1, in) == 1) {
+ pj_timestamp t1, t2;
+
+ if (lost_rate10 == 0) {
+
+ /* Expansion */
+ pj_get_timestamp(&t1);
+ pjmedia_wsola_save(wsola, frame, 0);
+ pj_get_timestamp(&t2);
+
+ pj_sub_timestamp(&t2, &t1);
+ pj_add_timestamp(&elapsed, &t2);
+
+ fwrite(frame, SAMPLES_PER_FRAME*2, 1, out);
+
+ samples += SAMPLES_PER_FRAME;
+
+ if ((rand() % 100) < expansion_rate100) {
+
+ pj_get_timestamp(&t1);
+ pjmedia_wsola_generate(wsola, frame);
+ pj_get_timestamp(&t2);
+
+ pj_sub_timestamp(&t2, &t1);
+ pj_add_timestamp(&elapsed, &t2);
+
+ samples += SAMPLES_PER_FRAME;
+
+ fwrite(frame, SAMPLES_PER_FRAME*2, 1, out);
+ }
+
+ } else {
+ /* Lost */
+
+ if ((rand() % 10) < lost_rate10) {
+ int burst;
+
+ for (burst=0; burst<lost_burst; ++burst) {
+ pj_get_timestamp(&t1);
+ pjmedia_wsola_generate(wsola, frame);
+ pj_get_timestamp(&t2);
+
+ pj_sub_timestamp(&t2, &t1);
+ pj_add_timestamp(&elapsed, &t2);
+
+ samples += SAMPLES_PER_FRAME;
+
+ fwrite(frame, SAMPLES_PER_FRAME*2, 1, out);
+ }
+ last_lost = 1;
+ } else {
+ pj_get_timestamp(&t1);
+ pjmedia_wsola_save(wsola, frame, last_lost);
+ pj_get_timestamp(&t2);
+
+ pj_sub_timestamp(&t2, &t1);
+ pj_add_timestamp(&elapsed, &t2);
+
+ samples += SAMPLES_PER_FRAME;
+
+ fwrite(frame, SAMPLES_PER_FRAME*2, 1, out);
+ last_lost = 0;
+ }
+
+ }
+
+ }
+
+ zero.u64 = 0;
+ zero.u64 = pj_elapsed_usec(&zero, &elapsed);
+
+ zero.u64 = samples * PJ_INT64(1000000) / zero.u64;
+ assert(zero.u32.hi == 0);
+
+ PJ_LOG(3,("test.c", "Processing: %f Msamples per second",
+ zero.u32.lo/1000000.0));
+ PJ_LOG(3,("test.c", "CPU load for current settings: %f%%",
+ CLOCK_RATE * 100.0 / zero.u32.lo));
+
+ pjmedia_wsola_destroy(wsola);
+ fclose(in);
+ fclose(out);
+
+ return 0;
+}
+
+static void save_file(const char *file,
+ short frame[], unsigned count)
+{
+ FILE *f = fopen(file, "wb");
+ fwrite(frame, count, 2, f);
+ fclose(f);
+}
+
+int compress(pj_pool_t *pool,
+ const char *filein, const char *fileout,
+ int rate10)
+{
+ enum { BUF_CNT = SAMPLES_PER_FRAME * 10 };
+ FILE *in, *out;
+ pjmedia_wsola *wsola;
+ short buf[BUF_CNT];
+ pj_timestamp elapsed, zero;
+ unsigned samples = 0;
+
+ in = fopen(filein, "rb");
+ if (!in) return 1;
+ out = fopen(fileout, "wb");
+ if (!out) return 1;
+
+ pjmedia_wsola_create(pool, CLOCK_RATE, SAMPLES_PER_FRAME, 1, 0, &wsola);
+
+ elapsed.u64 = 0;
+
+ for (;;) {
+ unsigned size_del, count;
+ pj_timestamp t1, t2;
+ int i;
+
+ if (fread(buf, sizeof(buf), 1, in) != 1)
+ break;
+
+ count = BUF_CNT;
+ size_del = 0;
+ pj_get_timestamp(&t1);
+
+ for (i=0; i<rate10; ++i) {
+ unsigned to_del = SAMPLES_PER_FRAME;
+#if 0
+ /* Method 1: buf1 contiguous */
+ pjmedia_wsola_discard(wsola, buf, count, NULL, 0, &to_del);
+#elif 0
+ /* Method 2: split, majority in buf1 */
+ assert(count > SAMPLES_PER_FRAME);
+ pjmedia_wsola_discard(wsola, buf, count-SAMPLES_PER_FRAME,
+ buf+count-SAMPLES_PER_FRAME, SAMPLES_PER_FRAME,
+ &to_del);
+#elif 0
+ /* Method 3: split, majority in buf2 */
+ assert(count > SAMPLES_PER_FRAME);
+ pjmedia_wsola_discard(wsola, buf, SAMPLES_PER_FRAME,
+ buf+SAMPLES_PER_FRAME, count-SAMPLES_PER_FRAME,
+ &to_del);
+#elif 1
+ /* Method 4: split, each with small length */
+ enum { TOT_LEN = 3 * SAMPLES_PER_FRAME };
+ unsigned buf1_len = (rand() % TOT_LEN);
+ short *ptr = buf + count - TOT_LEN;
+ assert(count > TOT_LEN);
+ if (buf1_len==0) buf1_len=SAMPLES_PER_FRAME*2;
+ pjmedia_wsola_discard(wsola, ptr, buf1_len,
+ ptr+buf1_len, TOT_LEN-buf1_len,
+ &to_del);
+#endif
+ count -= to_del;
+ size_del += to_del;
+ }
+ pj_get_timestamp(&t2);
+
+ samples += BUF_CNT;
+
+ pj_sub_timestamp(&t2, &t1);
+ pj_add_timestamp(&elapsed, &t2);
+
+ assert(size_del >= SAMPLES_PER_FRAME);
+
+ fwrite(buf, count, 2, out);
+ }
+
+ pjmedia_wsola_destroy(wsola);
+ fclose(in);
+ fclose(out);
+
+ zero.u64 = 0;
+ zero.u64 = pj_elapsed_usec(&zero, &elapsed);
+
+ zero.u64 = samples * PJ_INT64(1000000) / zero.u64;
+ assert(zero.u32.hi == 0);
+
+ PJ_LOG(3,("test.c", "Processing: %f Msamples per second",
+ zero.u32.lo/1000000.0));
+ PJ_LOG(3,("test.c", "CPU load for current settings: %f%%",
+ CLOCK_RATE * 100.0 / zero.u32.lo));
+
+ return 0;
+}
+
+
+static void mem_test(pj_pool_t *pool)
+{
+ char unused[1024];
+ short *frame = pj_pool_alloc(pool, 240+4*160);
+ pj_timestamp elapsed, zero, t1, t2;
+ unsigned samples = 0;
+
+ elapsed.u64 = 0;
+ while (samples < 50000000) {
+
+ pj_get_timestamp(&t1);
+ pjmedia_move_samples(frame, frame+160, 240+2*160);
+ pj_get_timestamp(&t2);
+ pj_sub_timestamp(&t2, &t1);
+
+ elapsed.u64 += t2.u64;
+
+ memset(unused, 0, sizeof(unused));
+ samples += 160;
+ }
+
+
+
+
+ zero.u64 = 0;
+ zero.u64 = pj_elapsed_usec(&zero, &elapsed);
+
+ zero.u64 = samples * PJ_INT64(1000000) / zero.u64;
+ assert(zero.u32.hi == 0);
+
+ PJ_LOG(3,("test.c", "Processing: %f Msamples per second",
+ zero.u32.lo/1000000.0));
+ PJ_LOG(3,("test.c", "CPU load for current settings: %f%%",
+ CLOCK_RATE * 100.0 / zero.u32.lo));
+
+}
+
+int main()
+{
+ pj_caching_pool cp;
+ pj_pool_t *pool;
+ int i, rc;
+
+ //test_find_pitch();
+
+ pj_init();
+ pj_caching_pool_init(&cp, NULL, 0);
+ pool = pj_pool_create(&cp.factory, "", 1000, 1000, NULL);
+
+ srand(2);
+
+ rc = expand(pool, "galileo16.pcm", "temp1.pcm", 20, 0, 0);
+ rc = compress(pool, "temp1.pcm", "output.pcm", 1);
+
+ for (i=0; i<2; ++i) {
+ rc = expand(pool, "output.pcm", "temp1.pcm", 20, 0, 0);
+ rc = compress(pool, "temp1.pcm", "output.pcm", 1);
+ }
+
+ if (rc != 0) {
+ puts("Error");
+ return 1;
+ }
+
+#if 0
+ {
+ char s[10];
+ puts("Press ENTER to quit");
+ fgets(s, sizeof(s), stdin);
+ }
+#endif
+
+ return 0;
+}