Migration of current video works from private repository to this repository. This closed #1176
git-svn-id: https://svn.pjsip.org/repos/pjproject/branches/projects/2.0-dev@3392 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjmedia/build/Makefile b/pjmedia/build/Makefile
index b0e2014..6f30e20 100644
--- a/pjmedia/build/Makefile
+++ b/pjmedia/build/Makefile
@@ -18,6 +18,7 @@
export PJMEDIA_CODEC_LIB:=../lib/libpjmedia-codec-$(TARGET_NAME)$(LIBEXT)
export PJSDP_LIB:=../lib/libpjsdp-$(TARGET_NAME)$(LIBEXT)
export PJMEDIA_AUDIODEV_LIB:=../lib/libpjmedia-audiodev-$(TARGET_NAME)$(LIBEXT)
+export PJMEDIA_VIDEODEV_LIB:=../lib/libpjmedia-videodev-$(TARGET_NAME)$(LIBEXT)
###############################################################################
@@ -34,6 +35,7 @@
$(HOST_CXXFLAGS) $(CXXFLAGS)
export _LDFLAGS := $(subst /,$(HOST_PSEP),$(PJMEDIA_LIB)) \
$(subst /,$(HOST_PSEP),$(PJMEDIA_AUDIODEV_LIB)) \
+ $(subst /,$(HOST_PSEP),$(PJMEDIA_VIDEODEV_LIB)) \
$(subst /,$(HOST_PSEP),$(PJMEDIA_CODEC_LIB)) \
$(subst /,$(HOST_PSEP),$(PJLIB_LIB)) \
$(subst /,$(HOST_PSEP),$(PJLIB_UTIL_LIB)) \
@@ -49,9 +51,12 @@
#
export PJMEDIA_SRCDIR = ../src/pjmedia
export PJMEDIA_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \
- alaw_ulaw.o alaw_ulaw_table.o bidirectional.o clock_thread.o codec.o \
- conference.o conf_switch.o delaybuf.o echo_common.o \
+ alaw_ulaw.o alaw_ulaw_table.o avi_player.o \
+ bidirectional.o clock_thread.o codec.o conference.o \
+ conf_switch.o converter.o converter_libswscale.o \
+ delaybuf.o echo_common.o \
echo_port.o echo_suppress.o endpoint.o errno.o \
+ format.o ffmpeg_util.o \
g711.o jbuf.o master_port.o mem_capture.o mem_player.o \
null_port.o plc_common.o port.o splitcomb.o \
resample_resample.o resample_libsamplerate.o \
@@ -60,7 +65,7 @@
sound_legacy.o sound_port.o stereo_port.o \
stream.o tonegen.o transport_adapter_sample.o \
transport_ice.o transport_loop.o \
- transport_srtp.o transport_udp.o \
+ transport_srtp.o transport_udp.o vid_codec.o videoport.o \
wav_player.o wav_playlist.o wav_writer.o wave.o \
wsola.o
@@ -78,6 +83,15 @@
###############################################################################
+# Defines for building PJMEDIA-VIDEODEV library
+#
+export PJMEDIA_VIDEODEV_SRCDIR = ../src/pjmedia-videodev
+export PJMEDIA_VIDEODEV_OBJS += videodev.o ffmpeg_dev.o sdl_dev.o colorbar_dev.o \
+ v4l2_dev.o
+export PJMEDIA_VIDEODEV_CFLAGS += $(_CFLAGS)
+
+
+###############################################################################
# Defines for building PJSDP library
# Note that SDP functionality is already INCLUDED in PJMEDIA.
# The PJSDP library should only be used for applications that want SDP
@@ -93,7 +107,7 @@
# Defines for building PJMEDIA-Codec library
#
export PJMEDIA_CODEC_SRCDIR = ../src/pjmedia-codec
-export PJMEDIA_CODEC_OBJS += \
+export PJMEDIA_CODEC_OBJS += ffmpeg_codecs.o \
$(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \
ipp_codecs.o $(CODEC_OBJS)
export PJMEDIA_CODEC_CFLAGS += $(_CFLAGS) $(GSM_CFLAGS) $(SPEEX_CFLAGS) \
@@ -104,7 +118,8 @@
# Defines for building test application
#
export PJMEDIA_TEST_SRCDIR = ../src/test
-export PJMEDIA_TEST_OBJS += codec_vectors.o jbuf_test.o main.o mips_test.o rtp_test.o test.o
+export PJMEDIA_TEST_OBJS += codec_vectors.o jbuf_test.o main.o mips_test.o \
+ vid_dev_test.o vid_codec_test.o rtp_test.o test.o
export PJMEDIA_TEST_OBJS += sdp_neg_test.o
export PJMEDIA_TEST_CFLAGS += $(_CFLAGS)
export PJMEDIA_TEST_LDFLAGS += $(_LDFLAGS)
@@ -117,7 +132,7 @@
#
# $(TARGET) is defined in os-$(OS_NAME).mak file in current directory.
#
-TARGETS := pjmedia pjmedia-audiodev pjmedia-codec pjsdp pjmedia-test
+TARGETS := pjmedia pjmedia-videodev pjmedia-audiodev pjmedia-codec pjsdp pjmedia-test
all: $(TARGETS)
@@ -132,7 +147,7 @@
dep: depend
distclean: realclean
-.PHONY: dep depend pjmedia pjmedia-codec pjmedia-audiodev pjmedia-test clean realclean distclean
+.PHONY: dep depend pjmedia pjmedia-codec pjmedia-videodev pjmedia-audiodev pjmedia-test clean realclean distclean
pjmedia:
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA app=pjmedia $(PJMEDIA_LIB)
@@ -140,6 +155,9 @@
pjmedia-codec:
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_CODEC app=pjmedia-codec $(PJMEDIA_CODEC_LIB)
+pjmedia-videodev:
+ $(MAKE) -f $(RULES_MAK) APP=PJMEDIA_VIDEODEV app=pjmedia-videodev $(PJMEDIA_VIDEODEV_LIB)
+
pjmedia-audiodev:
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_AUDIODEV app=pjmedia-audiodev $(PJMEDIA_AUDIODEV_LIB)
@@ -166,18 +184,21 @@
clean:
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA app=pjmedia $@
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_CODEC app=pjmedia-codec $@
+ $(MAKE) -f $(RULES_MAK) APP=PJMEDIA_VIDEODEV app=pjmedia-videodev $@
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_AUDIODEV app=pjmedia-audiodev $@
$(MAKE) -f $(RULES_MAK) APP=PJSDP app=pjsdp $@
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_TEST app=pjmedia-test $@
realclean:
$(subst @@,$(subst /,$(HOST_PSEP),.pjmedia-$(TARGET_NAME).depend),$(HOST_RMR))
+ $(subst @@,$(subst /,$(HOST_PSEP),.pjmedia-videodev-$(TARGET_NAME).depend),$(HOST_RMR))
$(subst @@,$(subst /,$(HOST_PSEP),.pjmedia-audiodev-$(TARGET_NAME).depend),$(HOST_RMR))
$(subst @@,$(subst /,$(HOST_PSEP),.pjmedia-codec-$(TARGET_NAME).depend),$(HOST_RMR))
$(subst @@,$(subst /,$(HOST_PSEP),.pjmedia-test-$(TARGET_NAME).depend),$(HOST_RMR))
$(subst @@,$(subst /,$(HOST_PSEP),.pjsdp-$(TARGET_NAME).depend),$(HOST_RMR))
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA app=pjmedia $@
+ $(MAKE) -f $(RULES_MAK) APP=PJMEDIA_VIDEODEV app=pjmedia-videodev $@
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_AUDIODEV app=pjmedia-audiodev $@
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_CODEC app=pjmedia-codec $@
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_TEST app=pjmedia-test $@
@@ -185,6 +206,7 @@
depend:
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA app=pjmedia $@
+ $(MAKE) -f $(RULES_MAK) APP=PJMEDIA_VIDEODEV app=pjmedia-videodev $@
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_AUDIODEV app=pjmedia-audiodev $@
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_CODEC app=pjmedia-codec $@
$(MAKE) -f $(RULES_MAK) APP=PJMEDIA_TEST app=pjmedia-test $@
diff --git a/pjmedia/build/os-auto.mak.in b/pjmedia/build/os-auto.mak.in
index a329f76..11c96bc 100644
--- a/pjmedia/build/os-auto.mak.in
+++ b/pjmedia/build/os-auto.mak.in
@@ -1,7 +1,21 @@
# @configure_input@
+# SDL flags
+SDL_CFLAGS = @ac_sdl_cflags@
+SDL_LDFLAGS = @ac_sdl_ldflags@
+
+# FFMPEG dlags
+FFMPEG_CFLAGS = @ac_ffmpeg_cflags@
+FFMPEG_LDFLAGS = @ac_ffmpeg_ldflags@
+
+# Video4Linux2
+V4L2_CFLAGS = @ac_v4l2_cflags@
+V4L2_LDFLAGS = @ac_v4l2_ldflags@
+
# PJMEDIA features exclusion
-export CFLAGS += @ac_no_small_filter@ @ac_no_large_filter@ @ac_no_speex_aec@
+export CFLAGS += @ac_no_small_filter@ @ac_no_large_filter@ @ac_no_speex_aec@ \
+ $(SDL_CFLAGS) $(FFMPEG_CFLAGS) $(V4L2_CFLAGS)
+export LDFLAGS += $(SDL_LDFLAGS) $(FFMPEG_LDFLAGS) $(V4L2_LDFLAGS)
# Define the desired sound device backend
# Valid values are:
diff --git a/pjmedia/include/pjmedia-audiodev/audiodev.h b/pjmedia/include/pjmedia-audiodev/audiodev.h
index ba28bc0..2a0dc67 100644
--- a/pjmedia/include/pjmedia-audiodev/audiodev.h
+++ b/pjmedia/include/pjmedia-audiodev/audiodev.h
@@ -26,6 +26,8 @@
*/
#include <pjmedia-audiodev/config.h>
#include <pjmedia-audiodev/errno.h>
+#include <pjmedia/format.h>
+#include <pjmedia/frame.h>
#include <pjmedia/types.h>
#include <pj/pool.h>
diff --git a/pjmedia/include/pjmedia-audiodev/config.h b/pjmedia/include/pjmedia-audiodev/config.h
index 5e5fa75..b08cefb 100644
--- a/pjmedia/include/pjmedia-audiodev/config.h
+++ b/pjmedia/include/pjmedia-audiodev/config.h
@@ -21,8 +21,8 @@
#define __PJMEDIA_AUDIODEV_CONFIG_H__
/**
- * @file audiodev.h
- * @brief Audio device API.
+ * @file config.h
+ * @brief Audio config.
*/
#include <pjmedia/types.h>
#include <pj/pool.h>
diff --git a/pjmedia/include/pjmedia-codec.h b/pjmedia/include/pjmedia-codec.h
index e6d211d..942d529 100644
--- a/pjmedia/include/pjmedia-codec.h
+++ b/pjmedia/include/pjmedia-codec.h
@@ -26,6 +26,7 @@
*/
#include <pjmedia-codec/l16.h>
+#include <pjmedia-codec/ffmpeg_codecs.h>
#include <pjmedia-codec/gsm.h>
#include <pjmedia-codec/speex.h>
#include <pjmedia-codec/ilbc.h>
diff --git a/pjmedia/include/pjmedia-codec/config.h b/pjmedia/include/pjmedia-codec/config.h
index 3dbb5c5..1480e22 100644
--- a/pjmedia/include/pjmedia-codec/config.h
+++ b/pjmedia/include/pjmedia-codec/config.h
@@ -34,7 +34,6 @@
#include <pjmedia/types.h>
-
/*
* Include config_auto.h if autoconf is used (PJ_AUTOCONF is set)
*/
@@ -42,6 +41,7 @@
# include <pjmedia-codec/config_auto.h>
#endif
+
/**
* Unless specified otherwise, L16 codec is included by default.
*/
@@ -343,8 +343,20 @@
to control which implementation to be used.
#endif
+
+/**
+ * Specify if FFMPEG codecs are available.
+ *
+ * Default: PJMEDIA_HAS_LIBAVCODEC
+ */
+#ifndef PJMEDIA_HAS_FFMPEG_CODEC
+# define PJMEDIA_HAS_FFMPEG_CODEC PJMEDIA_HAS_LIBAVCODEC
+#endif
+
/**
* @}
*/
+
+
#endif /* __PJMEDIA_CODEC_CONFIG_H__ */
diff --git a/pjmedia/include/pjmedia-codec/ffmpeg_codecs.h b/pjmedia/include/pjmedia-codec/ffmpeg_codecs.h
new file mode 100644
index 0000000..d928044
--- /dev/null
+++ b/pjmedia/include/pjmedia-codec/ffmpeg_codecs.h
@@ -0,0 +1,62 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 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_CODECS_FFMPEG_H__
+#define __PJMEDIA_CODECS_FFMPEG_H__
+
+
+#include <pjmedia-codec/types.h>
+#include <pjmedia/vid_codec.h>
+
+PJ_BEGIN_DECL
+
+
+/**
+ * Initialize and register FFMPEG codecs factory to pjmedia endpoint.
+ *
+ * @param mgr The video codec manager instance where this codec will
+ * be registered to. Specify NULL to use default instance
+ * (in that case, an instance of video codec manager must
+ * have been created beforehand).
+ * @param pf Pool factory.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_codec_ffmpeg_init(pjmedia_vid_codec_mgr *mgr,
+ pj_pool_factory *pf);
+
+
+/**
+ * Unregister FFMPEG codecs factory from the video codec manager and
+ * deinitialize the codecs library.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_codec_ffmpeg_deinit(void);
+
+
+PJ_END_DECL
+
+
+/**
+ * @}
+ */
+
+#endif /* __PJMEDIA_CODECS_FFMPEG_H__ */
+
diff --git a/pjmedia/include/pjmedia-codec/h263_packetizer.h b/pjmedia/include/pjmedia-codec/h263_packetizer.h
new file mode 100644
index 0000000..4208fc0
--- /dev/null
+++ b/pjmedia/include/pjmedia-codec/h263_packetizer.h
@@ -0,0 +1,140 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 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
+ */
+#ifndef __PJMEDIA_H263_PACKETIZER_H__
+#define __PJMEDIA_H263_PACKETIZER_H__
+
+
+/**
+ * @file h263_packetizer.h
+ * @brief Packetizes H.263 bitstream into RTP payload.
+ */
+
+#include <pj/errno.h>
+
+PJ_BEGIN_DECL
+
+/**
+ * Find synchronization point (PSC, slice, GSBC, EOS, EOSBS) in H.263
+ * bitstream, in reversed manner.
+ */
+PJ_INLINE(pj_uint8_t*) pjmedia_h263_find_sync_point_rev(pj_uint8_t *data,
+ pj_size_t data_len)
+{
+ pj_uint8_t *p = data+data_len-1;
+
+ while (p > data && *p && *(p+1))
+ --p;
+
+ if (p == data)
+ return (data + data_len);
+
+ return p;
+}
+
+/**
+ * Generate an RTP payload from H.263 frame bitstream, in-place processing.
+ */
+PJ_INLINE(pj_status_t) pjmedia_h263_packetize(pj_uint8_t *buf,
+ pj_size_t buf_len,
+ unsigned *pos,
+ int max_payload_len,
+ const pj_uint8_t **payload,
+ pj_size_t *payload_len)
+{
+ pj_uint8_t *p, *p_end;
+
+ p = buf + *pos;
+ p_end = buf + buf_len;
+
+ /* Put two octets payload header */
+ if ((p_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!
+ */
+ pj_assert(*pos>=2);
+ 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 (p_end-p > max_payload_len) {
+ p_end = pjmedia_h263_find_sync_point_rev(p+2, max_payload_len-2);
+ }
+
+ *payload = p;
+ *payload_len = p_end-p;
+ *pos = p_end - buf;
+
+ return PJ_SUCCESS;
+}
+
+/**
+ * Append RTP payload to a H.263 picture bitstream.
+ */
+PJ_INLINE(pj_status_t) pjmedia_h263_unpacketize(const pj_uint8_t *payload,
+ pj_size_t payload_len,
+ pj_uint8_t *bits,
+ pj_size_t *bits_len)
+{
+ pj_uint8_t P, V, PLEN;
+ const pj_uint8_t *p=payload;
+ pj_size_t max_len = *bits_len;
+
+ P = *p & 0x04;
+ V = *p & 0x02;
+ PLEN = ((*p & 0x01) << 5) + ((*(p+1) & 0xF8)>>3);
+
+ /* Get bitstream pointer */
+ p += 2;
+ if (V)
+ p += 1; /* Skip VRC data */
+ if (PLEN)
+ p += PLEN; /* Skip extra picture header data */
+
+ /* Get bitstream length */
+ payload_len -= (p-payload);
+
+ *bits_len = payload_len + (P?2:0);
+ PJ_ASSERT_RETURN(max_len >= *bits_len, PJ_ETOOSMALL);
+
+ /* Add two zero octets when payload flagged with sync point */
+ if (P) {
+ *bits++ = 0;
+ *bits++ = 0;
+ }
+
+ /* Add the bitstream */
+ pj_memcpy(bits, p, payload_len);
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_END_DECL
+
+
+#endif /* __PJMEDIA_H263_PACKETIZER_H__ */
diff --git a/pjmedia/include/pjmedia-videodev/config.h b/pjmedia/include/pjmedia-videodev/config.h
new file mode 100644
index 0000000..6ee3b3d
--- /dev/null
+++ b/pjmedia/include/pjmedia-videodev/config.h
@@ -0,0 +1,160 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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
+ */
+#ifndef __PJMEDIA_VIDEODEV_CONFIG_H__
+#define __PJMEDIA_VIDEODEV_CONFIG_H__
+
+/**
+ * @file config.h
+ * @brief Video config.
+ */
+#include <pjmedia/types.h>
+#include <pj/pool.h>
+
+
+PJ_BEGIN_DECL
+
+/**
+ * @defgroup video_device_api Video Device API
+ * @brief PJMEDIA video device abstraction API.
+ */
+
+/**
+ * @defgroup s1_video_device_config Compile time configurations
+ * @ingroup video_device_api
+ * @brief Compile time configurations
+ * @{
+ */
+
+/**
+ * This setting controls whether SDL support should be included.
+ *
+ * Default: 0 (or detected by configure)
+ */
+#ifndef PJMEDIA_VIDEO_DEV_HAS_SDL
+# define PJMEDIA_VIDEO_DEV_HAS_SDL 0
+#endif
+
+
+/**
+ * This setting controls whether Direct Show support should be included.
+ *
+ * Default: 0 (unfinished)
+ */
+#ifndef PJMEDIA_VIDEO_DEV_HAS_DSHOW
+# define PJMEDIA_VIDEO_DEV_HAS_DSHOW 0 //PJ_WIN32
+#endif
+
+
+/**
+ * This setting controls whether colorbar source support should be included.
+ *
+ * Default: 1
+ */
+#ifndef PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC
+# define PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC 1
+#endif
+
+
+/**
+ * This setting controls whether ffmpeg support should be included.
+ *
+ * Default: 0 (unfinished)
+ */
+#ifndef PJMEDIA_VIDEO_DEV_HAS_FFMPEG
+# define PJMEDIA_VIDEO_DEV_HAS_FFMPEG 0
+#endif
+
+
+/**
+ * Video4Linux2
+ *
+ * Default: 0 (or detected by configure)
+ */
+#ifndef PJMEDIA_VIDEO_DEV_HAS_V4L2
+# define PJMEDIA_VIDEO_DEV_HAS_V4L2 0
+#endif
+
+/**
+ * @}
+ */
+
+PJ_END_DECL
+
+
+#endif /* __PJMEDIA_VIDEODEV_CONFIG_H__ */
+
+/*
+ --------------------- DOCUMENTATION FOLLOWS ---------------------------
+ */
+
+/**
+ * @addtogroup video_device_api Video Device API
+ * @{
+
+PJMEDIA Video Device API is a cross-platform video API appropriate for use with
+VoIP applications and many other types of video streaming applications.
+
+The API abstracts many different video API's on various platforms, such as:
+ - native Direct Show video for Win32 and Windows Mobile devices
+ - null-video implementation
+ - and more to be implemented in the future
+
+The Video Device API/library is an evolution from PJMEDIA @ref PJMED_SND and
+contains many enhancements:
+
+ - Forward compatibility:
+\n
+ The new API has been designed to be extensible, it will support new API's as
+ well as new features that may be introduced in the future without breaking
+ compatibility with applications that use this API as well as compatibility
+ with existing device implementations.
+
+ - Device capabilities:
+\n
+ At the heart of the API is device capabilities management, where all possible
+ video capabilities of video devices should be able to be handled in a generic
+ manner. With this framework, new capabilities that may be discovered in the
+ future can be handled in manner without breaking existing applications.
+
+ - Built-in features:
+\n
+ The device capabilities framework enables applications to use and control
+ video features built-in in the device, such as:
+ - built-in formats,
+ - etc.
+
+ - Codec support:
+\n
+ Some video devices support built-in hardware video codecs, and application
+ can use the video device in encoded mode to make use of these hardware
+ codecs.
+
+ - Multiple backends:
+\n
+ The new API supports multiple video backends (called factories or drivers in
+ the code) to be active simultaneously, and video backends may be added or
+ removed during run-time.
+
+*/
+
+
+/**
+ * @}
+ */
+
diff --git a/pjmedia/include/pjmedia-videodev/errno.h b/pjmedia/include/pjmedia-videodev/errno.h
new file mode 100644
index 0000000..8c6e89d
--- /dev/null
+++ b/pjmedia/include/pjmedia-videodev/errno.h
@@ -0,0 +1,159 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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
+ */
+#ifndef __PJMEDIA_VIDEODEV_VIDEODEV_ERRNO_H__
+#define __PJMEDIA_VIDEODEV_VIDEODEV_ERRNO_H__
+
+/**
+ * @file errno.h Error Codes
+ * @brief Videodev specific error codes.
+ */
+
+#include <pjmedia-videodev/config.h>
+#include <pj/errno.h>
+
+/**
+ * @defgroup error_codes Error Codes
+ * @ingroup video_device_api
+ * @brief Video device library specific error codes.
+ * @{
+ */
+
+
+PJ_BEGIN_DECL
+
+
+/**
+ * Start of error code relative to PJ_ERRNO_START_USER.
+ * This value is 470000.
+ */
+#define PJMEDIA_VIDEODEV_ERRNO_START \
+ (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*6)
+#define PJMEDIA_VIDEODEV_ERRNO_END \
+ (PJMEDIA_VIDEODEV_ERRNO_START + PJ_ERRNO_SPACE_SIZE - 1)
+
+
+/************************************************************
+ * Video Device API error codes
+ ***********************************************************/
+/**
+ * @hideinitializer
+ * General/unknown error.
+ */
+#define PJMEDIA_EVID_ERR (PJMEDIA_VIDEODEV_ERRNO_START+1) /* 470001 */
+
+/**
+ * @hideinitializer
+ * Unknown error from video driver
+ */
+#define PJMEDIA_EVID_SYSERR (PJMEDIA_VIDEODEV_ERRNO_START+2) /* 470002 */
+
+/**
+ * @hideinitializer
+ * Video subsystem not initialized
+ */
+#define PJMEDIA_EVID_INIT (PJMEDIA_VIDEODEV_ERRNO_START+3) /* 470003 */
+
+/**
+ * @hideinitializer
+ * Invalid video device
+ */
+#define PJMEDIA_EVID_INVDEV (PJMEDIA_VIDEODEV_ERRNO_START+4) /* 470004 */
+
+/**
+ * @hideinitializer
+ * Found no devices
+ */
+#define PJMEDIA_EVID_NODEV (PJMEDIA_VIDEODEV_ERRNO_START+5) /* 470005 */
+
+/**
+ * @hideinitializer
+ * Unable to find default device
+ */
+#define PJMEDIA_EVID_NODEFDEV (PJMEDIA_VIDEODEV_ERRNO_START+6) /* 470006 */
+
+/**
+ * @hideinitializer
+ * Device not ready
+ */
+#define PJMEDIA_EVID_NOTREADY (PJMEDIA_VIDEODEV_ERRNO_START+7) /* 470007 */
+
+/**
+ * @hideinitializer
+ * The video capability is invalid or not supported
+ */
+#define PJMEDIA_EVID_INVCAP (PJMEDIA_VIDEODEV_ERRNO_START+8) /* 470008 */
+
+/**
+ * @hideinitializer
+ * The operation is invalid or not supported
+ */
+#define PJMEDIA_EVID_INVOP (PJMEDIA_VIDEODEV_ERRNO_START+9) /* 470009 */
+
+/**
+ * @hideinitializer
+ * Bad or invalid video device format
+ */
+#define PJMEDIA_EVID_BADFORMAT (PJMEDIA_VIDEODEV_ERRNO_START+10) /* 4700010 */
+
+/**
+ * @hideinitializer
+ * Invalid video device sample format
+ */
+#define PJMEDIA_EVID_SAMPFORMAT (PJMEDIA_VIDEODEV_ERRNO_START+11) /* 4700011 */
+
+/**
+ * @hideinitializer
+ * Bad latency setting
+ */
+#define PJMEDIA_EVID_BADLATENCY (PJMEDIA_VIDEODEV_ERRNO_START+12) /* 4700012 */
+
+/**
+ * @hideinitializer
+ * Bad/unsupported video size
+ */
+#define PJMEDIA_EVID_BADSIZE (PJMEDIA_VIDEODEV_ERRNO_START+13) /* 4700013 */
+
+
+/**
+ * Get error message for the specified error code. Note that this
+ * function is only able to decode PJMEDIA Videodev specific error code.
+ * Application should use pj_strerror(), which should be able to
+ * decode all error codes belonging to all subsystems (e.g. pjlib,
+ * pjmedia, pjsip, etc).
+ *
+ * @param status The error code.
+ * @param buffer The buffer where to put the error message.
+ * @param bufsize Size of the buffer.
+ *
+ * @return The error message as NULL terminated string,
+ * wrapped with pj_str_t.
+ */
+PJ_DECL(pj_str_t) pjmedia_videodev_strerror(pj_status_t status, char *buffer,
+ pj_size_t bufsize);
+
+
+PJ_END_DECL
+
+/**
+ * @}
+ */
+
+
+#endif /* __PJMEDIA_VIDEODEV_VIDEODEV_ERRNO_H__ */
+
diff --git a/pjmedia/include/pjmedia-videodev/videodev.h b/pjmedia/include/pjmedia-videodev/videodev.h
new file mode 100644
index 0000000..2b4d894
--- /dev/null
+++ b/pjmedia/include/pjmedia-videodev/videodev.h
@@ -0,0 +1,574 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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
+ */
+#ifndef __PJMEDIA_VIDEODEV_VIDEODEV_H__
+#define __PJMEDIA_VIDEODEV_VIDEODEV_H__
+
+/**
+ * @file videodev.h
+ * @brief Video device API.
+ */
+#include <pjmedia-videodev/config.h>
+#include <pjmedia-videodev/errno.h>
+#include <pjmedia/frame.h>
+#include <pjmedia/format.h>
+#include <pj/pool.h>
+
+
+PJ_BEGIN_DECL
+
+/**
+ * @defgroup video_device_reference Video Device API Reference
+ * @ingroup video_device_api
+ * @brief API Reference
+ * @{
+ */
+
+/**
+ * Type for device index.
+ */
+typedef pj_int32_t pjmedia_vid_dev_index;
+
+/**
+ * Device index constants.
+ */
+enum
+{
+ /**
+ * Constant to denote default capture device
+ */
+ PJMEDIA_VID_DEFAULT_CAPTURE_DEV = -1,
+
+ /**
+ * Constant to denote default render device
+ */
+ PJMEDIA_VID_DEFAULT_RENDER_DEV = -2,
+
+ /**
+ * Constant to denote invalid device index.
+ */
+ PJMEDIA_VID_INVALID_DEV = -3
+};
+
+
+/**
+ * This enumeration identifies various video device capabilities. These video
+ * capabilities indicates what features are supported by the underlying
+ * video device implementation.
+ *
+ * Applications get these capabilities in the #pjmedia_vid_dev_info structure.
+ *
+ * Application can also set the specific features/capabilities when opening
+ * the video stream by setting the \a flags member of #pjmedia_vid_param
+ * structure.
+ *
+ * Once video stream is running, application can also retrieve or set some
+ * specific video capability, by using #pjmedia_vid_stream_get_cap() and
+ * #pjmedia_vid_stream_set_cap() and specifying the desired capability. The
+ * value of the capability is specified as pointer, and application needs to
+ * supply the pointer with the correct value, according to the documentation
+ * of each of the capability.
+ */
+typedef enum pjmedia_vid_dev_cap
+{
+ /**
+ * Support for video formats. The value of this capability
+ * is represented by #pjmedia_format structure.
+ */
+ PJMEDIA_VID_DEV_CAP_FORMAT = 1,
+
+ /**
+ * Support for video input scaling
+ */
+ PJMEDIA_VID_DEV_CAP_INPUT_SCALE = 2,
+
+ /**
+ * The application can provide a window for the renderer to
+ * display the video.
+ */
+ PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW = 4,
+
+ /**
+ * Support for resizing video output. This capability SHOULD be
+ * implemented by renderer, to alter the video output dimension on the fly.
+ * Value is pjmedia_rect_size.
+ */
+ PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE = 8,
+
+ /**
+ * End of capability
+ */
+ PJMEDIA_VID_DEV_CAP_MAX = 16384
+
+} pjmedia_vid_dev_cap;
+
+/**
+ * Device information structure returned by #pjmedia_vid_dev_get_info().
+ */
+typedef struct pjmedia_vid_dev_info
+{
+ /** The device name */
+ char name[64];
+
+ /** The underlying driver name */
+ char driver[32];
+
+ /**
+ * The supported direction of the video device, i.e. whether it supports
+ * capture only, render only, or both.
+ */
+ pjmedia_dir dir;
+
+ /** Specify whether the device supports callback */
+ pj_bool_t has_callback;
+
+ /** Device capabilities, as bitmask combination of #pjmedia_vid_dev_cap */
+ unsigned caps;
+
+ /** Number of video formats supported by this device */
+ unsigned fmt_cnt;
+
+ /**
+ * Array of supported video formats. Some fields in each supported video
+ * format may be set to zero or of "unknown" value, to indicate that the
+ * value is unknown or should be ignored. When these value are not set
+ * to zero, it indicates that the exact format combination is being used.
+ */
+ pjmedia_format *fmt;
+
+} pjmedia_vid_dev_info;
+
+
+/** Forward declaration for pjmedia_vid_stream */
+typedef struct pjmedia_vid_stream pjmedia_vid_stream;
+
+typedef enum pjmedia_event_type
+{
+ PJMEDIA_EVENT_NONE,
+ PJMEDIA_EVENT_MOUSEBUTTONDOWN,
+ PJMEDIA_EVENT_WINDOW_RESIZE,
+ PJMEDIA_EVENT_WINDOW_FULLSCREEN,
+ PJMEDIA_EVENT_WINDOW_CLOSE,
+} pjmedia_event_type;
+
+typedef struct pjmedia_vid_event
+{
+ pjmedia_event_type event_type;
+ long lparam;
+ void *pparam;
+} pjmedia_vid_event;
+
+
+typedef struct pjmedia_vid_cb
+{
+ /**
+ * This callback is called by capturer stream when it has captured the
+ * whole packet worth of video samples.
+ *
+ * @param stream The video stream.
+ * @param user_data User data associated with the stream.
+ * @param frame Captured frame.
+ *
+ * @return Returning non-PJ_SUCCESS will cause the video
+ * stream to stop
+ */
+ pj_status_t (*capture_cb)(pjmedia_vid_stream *stream,
+ void *user_data,
+ pjmedia_frame *frame);
+
+ /**
+ * This callback is called by renderer stream when it needs additional
+ * data to be rendered by the device. Application must fill in the whole
+ * of output buffer with video samples.
+ *
+ * The frame argument contains the following values:
+ * - timestamp Rendering timestamp, in samples.
+ * - buf Buffer to be filled out by application.
+ * - size The size requested in bytes, which will be equal
+ * to the size of one whole packet.
+ *
+ * @param stream The video stream.
+ * @param user_data User data associated with the stream.
+ * @param frame Video frame, which buffer is to be filled in by
+ * the application.
+ *
+ * @return Returning non-PJ_SUCCESS will cause the video
+ * stream to stop
+ */
+ pj_status_t (*render_cb)(pjmedia_vid_stream *stream,
+ void *user_data,
+ pjmedia_frame *frame);
+
+ /**
+ * This callback is called by the stream to report the occurence of an
+ * event to the application.
+ *
+ * @param stream The video stream.
+ * @param user_data User data associated with the stream.
+ * @param event The event.
+ *
+ * @return Return PJ_SUCCESS will invoke the video stream's
+ * default event-handler (if any), otherwise the
+ * video stream will ignore the particular event.
+ */
+ pj_status_t (*on_event_cb)(pjmedia_vid_stream *stream,
+ void *user_data,
+ pjmedia_vid_event *event);
+
+} pjmedia_vid_cb;
+
+
+/**
+ * This structure specifies the parameters to open the video stream.
+ */
+typedef struct pjmedia_vid_param
+{
+ /**
+ * The video direction. This setting is mandatory.
+ */
+ pjmedia_dir dir;
+
+ /**
+ * The video capture device ID. This setting is mandatory if the video
+ * direction includes input/capture direction.
+ */
+ pjmedia_vid_dev_index cap_id;
+
+ /**
+ * The video render device ID. This setting is mandatory if the video
+ * direction includes output/render direction.
+ */
+ pjmedia_vid_dev_index rend_id;
+
+ /**
+ * Video clock rate. This setting is mandatory if the video
+ * direction includes input/capture direction
+ */
+ unsigned clock_rate;
+
+ /**
+ * Video frame rate. This setting is mandatory if the video
+ * direction includes input/capture direction
+ */
+ pjmedia_ratio frame_rate;
+
+ /**
+ * This flags specifies which of the optional settings are valid in this
+ * structure. The flags is bitmask combination of pjmedia_vid_dev_cap.
+ */
+ unsigned flags;
+
+ /**
+ * Set the video format. This setting is mandatory.
+ */
+ pjmedia_format fmt;
+
+ /**
+ * Window for the renderer to display the video. This setting is optional,
+ * and will only be used if PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW is set in
+ * the flags.
+ */
+ void *window;
+
+ /**
+ * Video display size. This setting is optional, and will only be used
+ * if PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE is set in the flags.
+ */
+ pjmedia_rect_size disp_size;
+
+} pjmedia_vid_param;
+
+
+/** Forward declaration for video device factory */
+typedef struct pjmedia_vid_dev_factory pjmedia_vid_dev_factory;
+
+/* typedef for factory creation function */
+typedef pjmedia_vid_dev_factory*
+(*pjmedia_vid_dev_factory_create_func_ptr)(pj_pool_factory*);
+
+
+/**
+ * Get string info for the specified capability.
+ *
+ * @param cap The capability ID.
+ * @param p_desc Optional pointer which will be filled with longer
+ * description about the capability.
+ *
+ * @return Capability name.
+ */
+PJ_DECL(const char*) pjmedia_vid_dev_cap_name(pjmedia_vid_dev_cap cap,
+ const char **p_desc);
+
+
+/**
+ * Set a capability field value in #pjmedia_vid_param structure. This will
+ * also set the flags field for the specified capability in the structure.
+ *
+ * @param param The structure.
+ * @param cap The video capability which value is to be set.
+ * @param pval Pointer to value. Please see the type of value to
+ * be supplied in the pjmedia_vid_dev_cap documentation.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_param_set_cap(pjmedia_vid_param *param,
+ pjmedia_vid_dev_cap cap,
+ const void *pval);
+
+
+/**
+ * Get a capability field value from #pjmedia_vid_param structure. This
+ * function will return PJMEDIA_EVID_INVCAP error if the flag for that
+ * capability is not set in the flags field in the structure.
+ *
+ * @param param The structure.
+ * @param cap The video capability which value is to be retrieved.
+ * @param pval Pointer to value. Please see the type of value to
+ * be supplied in the pjmedia_vid_dev_cap documentation.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_param_get_cap(const pjmedia_vid_param *param,
+ pjmedia_vid_dev_cap cap,
+ void *pval);
+
+/**
+ * Initialize the video subsystem. This will register all supported video
+ * device factories to the video subsystem. This function may be called
+ * more than once, but each call to this function must have the
+ * corresponding #pjmedia_vid_subsys_shutdown() call.
+ *
+ * @param pf The pool factory.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_subsys_init(pj_pool_factory *pf);
+
+
+/**
+ * Get the pool factory registered to the video subsystem.
+ *
+ * @return The pool factory.
+ */
+PJ_DECL(pj_pool_factory*) pjmedia_vid_subsys_get_pool_factory(void);
+
+
+/**
+ * Shutdown the video subsystem. This will destroy all video device factories
+ * registered in the video subsystem. Note that currently opened video streams
+ * may or may not be closed, depending on the implementation of the video
+ * device factories.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_subsys_shutdown(void);
+
+
+/**
+ * Register a supported video device factory to the video subsystem. This
+ * function can only be called after calling #pjmedia_vid_subsys_init().
+ *
+ * @param vdf The video device factory.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t)
+pjmedia_vid_register_factory(pjmedia_vid_dev_factory_create_func_ptr vdf);
+
+
+/**
+ * Unregister an video device factory from the video subsystem. This
+ * function can only be called after calling #pjmedia_vid_subsys_init().
+ * Devices from this factory will be unlisted. If a device from this factory
+ * is currently in use, then the behavior is undefined.
+ *
+ * @param vdf The video device factory.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t)
+pjmedia_vid_unregister_factory(pjmedia_vid_dev_factory_create_func_ptr vdf);
+
+
+/**
+ * Get the number of video devices installed in the system.
+ *
+ * @return The number of video devices installed in the system.
+ */
+PJ_DECL(unsigned) pjmedia_vid_dev_count(void);
+
+
+/**
+ * Get device information.
+ *
+ * @param id The video device ID.
+ * @param info The device information which will be filled in by this
+ * function once it returns successfully.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_dev_get_info(pjmedia_vid_dev_index id,
+ pjmedia_vid_dev_info *info);
+
+
+/**
+ * Lookup device index based on the driver and device name.
+ *
+ * @param drv_name The driver name.
+ * @param dev_name The device name.
+ * @param id Pointer to store the returned device ID.
+ *
+ * @return PJ_SUCCESS if the device can be found.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_dev_lookup(const char *drv_name,
+ const char *dev_name,
+ pjmedia_vid_dev_index *id);
+
+
+/**
+ * Initialize the video device parameters with default values for the
+ * specified device.
+ *
+ * @param id The video device ID.
+ * @param param The video device parameters which will be initialized
+ * by this function once it returns successfully.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_dev_default_param(pj_pool_t *pool,
+ pjmedia_vid_dev_index id,
+ pjmedia_vid_param *param);
+
+
+/**
+ * Open video stream object using the specified parameters.
+ *
+ * @param param Sound device parameters to be used for the stream.
+ * @param cb Pointer to structure containing video stream
+ * callbacks.
+ * @param user_data Arbitrary user data, which will be given back in the
+ * callbacks.
+ * @param p_strm Pointer to receive the video stream.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_stream_create(const pjmedia_vid_param *param,
+ const pjmedia_vid_cb *cb,
+ void *user_data,
+ pjmedia_vid_stream **p_strm);
+
+/**
+ * Get the running parameters for the specified video stream.
+ *
+ * @param strm The video stream.
+ * @param param Video stream parameters to be filled in by this
+ * function once it returns successfully.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_stream_get_param(pjmedia_vid_stream *strm,
+ pjmedia_vid_param *param);
+
+/**
+ * Get the value of a specific capability of the video stream.
+ *
+ * @param strm The video stream.
+ * @param cap The video capability which value is to be retrieved.
+ * @param value Pointer to value to be filled in by this function
+ * once it returns successfully. Please see the type
+ * of value to be supplied in the pjmedia_vid_dev_cap
+ * documentation.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_stream_get_cap(pjmedia_vid_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ void *value);
+
+/**
+ * Set the value of a specific capability of the video stream.
+ *
+ * @param strm The video stream.
+ * @param cap The video capability which value is to be set.
+ * @param value Pointer to value. Please see the type of value to
+ * be supplied in the pjmedia_vid_dev_cap documentation.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_stream_set_cap(pjmedia_vid_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ const void *value);
+
+/**
+ * Start the stream.
+ *
+ * @param strm The video stream.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_stream_start(pjmedia_vid_stream *strm);
+
+/* Get/put frame API for passive stream */
+PJ_DECL(pj_status_t) pjmedia_vid_stream_get_frame(pjmedia_vid_stream *strm,
+ pjmedia_frame *frame);
+
+PJ_DECL(pj_status_t) pjmedia_vid_stream_put_frame(pjmedia_vid_stream *strm,
+ const pjmedia_frame *frame);
+
+/**
+ * Stop the stream.
+ *
+ * @param strm The video stream.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_stream_stop(pjmedia_vid_stream *strm);
+
+/**
+ * Destroy the stream.
+ *
+ * @param strm The video stream.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_stream_destroy(pjmedia_vid_stream *strm);
+
+
+/**
+ * @}
+ */
+
+PJ_END_DECL
+
+
+#endif /* __PJMEDIA_VIDEODEV_VIDEODEV_H__ */
diff --git a/pjmedia/include/pjmedia-videodev/videodev_imp.h b/pjmedia/include/pjmedia-videodev/videodev_imp.h
new file mode 100644
index 0000000..e16b90a
--- /dev/null
+++ b/pjmedia/include/pjmedia-videodev/videodev_imp.h
@@ -0,0 +1,192 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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
+ */
+#ifndef __VIDEODEV_IMP_H__
+#define __VIDEODEV_IMP_H__
+
+#include <pjmedia-videodev/videodev.h>
+
+/**
+ * @defgroup s8_video_device_implementors_api Video Device Implementors API
+ * @ingroup video_device_api
+ * @brief API for video device implementors
+ * @{
+ */
+
+/**
+ * Video device factory operations.
+ */
+typedef struct pjmedia_vid_dev_factory_op
+{
+ /**
+ * Initialize the video device factory.
+ *
+ * @param f The video device factory.
+ */
+ pj_status_t (*init)(pjmedia_vid_dev_factory *f);
+
+ /**
+ * Close this video device factory and release all resources back to the
+ * operating system.
+ *
+ * @param f The video device factory.
+ */
+ pj_status_t (*destroy)(pjmedia_vid_dev_factory *f);
+
+ /**
+ * Get the number of video devices installed in the system.
+ *
+ * @param f The video device factory.
+ */
+ unsigned (*get_dev_count)(pjmedia_vid_dev_factory *f);
+
+ /**
+ * Get the video device information and capabilities.
+ *
+ * @param f The video device factory.
+ * @param index Device index.
+ * @param info The video device information structure which will be
+ * initialized by this function once it returns
+ * successfully.
+ */
+ pj_status_t (*get_dev_info)(pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_dev_info *info);
+
+ /**
+ * Initialize the specified video device parameter with the default
+ * values for the specified device.
+ *
+ * @param f The video device factory.
+ * @param index Device index.
+ * @param param The video device parameter.
+ */
+ pj_status_t (*default_param)(pj_pool_t *pool,
+ pjmedia_vid_dev_factory *f,
+ unsigned index,
+ pjmedia_vid_param *param);
+
+ /**
+ * Open the video device and create video stream. See
+ * #pjmedia_vid_stream_create()
+ */
+ pj_status_t (*create_stream)(pjmedia_vid_dev_factory *f,
+ const pjmedia_vid_param *param,
+ const pjmedia_vid_cb *cb,
+ void *user_data,
+ pjmedia_vid_stream **p_vid_strm);
+
+} pjmedia_vid_dev_factory_op;
+
+
+/**
+ * This structure describes a video device factory.
+ */
+struct pjmedia_vid_dev_factory
+{
+ /** Internal data to be initialized by video subsystem. */
+ struct {
+ /** Driver index */
+ unsigned drv_idx;
+ } sys;
+
+ /** Operations */
+ pjmedia_vid_dev_factory_op *op;
+};
+
+
+/**
+ * Video stream operations.
+ */
+typedef struct pjmedia_vid_stream_op
+{
+ /**
+ * See #pjmedia_vid_stream_get_param()
+ */
+ pj_status_t (*get_param)(pjmedia_vid_stream *strm,
+ pjmedia_vid_param *param);
+
+ /**
+ * See #pjmedia_vid_stream_get_cap()
+ */
+ pj_status_t (*get_cap)(pjmedia_vid_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ void *value);
+
+ /**
+ * See #pjmedia_vid_stream_set_cap()
+ */
+ pj_status_t (*set_cap)(pjmedia_vid_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ const void *value);
+
+ /**
+ * See #pjmedia_vid_stream_start()
+ */
+ pj_status_t (*start)(pjmedia_vid_stream *strm);
+
+ /**
+ * See #pjmedia_vid_stream_get_frame()
+ */
+ pj_status_t (*get_frame)(pjmedia_vid_stream *strm,
+ pjmedia_frame *frame);
+
+ /**
+ * See #pjmedia_vid_stream_put_frame()
+ */
+ pj_status_t (*put_frame)(pjmedia_vid_stream *strm,
+ const pjmedia_frame *frame);
+
+ /**
+ * See #pjmedia_vid_stream_stop().
+ */
+ pj_status_t (*stop)(pjmedia_vid_stream *strm);
+
+ /**
+ * See #pjmedia_vid_stream_destroy().
+ */
+ pj_status_t (*destroy)(pjmedia_vid_stream *strm);
+
+} pjmedia_vid_stream_op;
+
+
+/**
+ * This structure describes the video device stream.
+ */
+struct pjmedia_vid_stream
+{
+ /** Internal data to be initialized by video subsystem */
+ struct {
+ /** Driver index */
+ unsigned drv_idx;
+ } sys;
+
+ /** Operations */
+ pjmedia_vid_stream_op *op;
+};
+
+
+
+
+/**
+ * @}
+ */
+
+
+
+#endif /* __VIDEODEV_IMP_H__ */
diff --git a/pjmedia/include/pjmedia.h b/pjmedia/include/pjmedia.h
index 55e8977..8187b4c 100644
--- a/pjmedia/include/pjmedia.h
+++ b/pjmedia/include/pjmedia.h
@@ -1,6 +1,6 @@
/* $Id$ */
/*
- * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2008-2010 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
@@ -24,19 +24,21 @@
* @file pjmedia.h
* @brief PJMEDIA main header file.
*/
-
-#include <pjmedia/types.h>
#include <pjmedia/alaw_ulaw.h>
+#include <pjmedia/avi_stream.h>
#include <pjmedia/bidirectional.h>
#include <pjmedia/circbuf.h>
#include <pjmedia/clock.h>
#include <pjmedia/codec.h>
#include <pjmedia/conference.h>
+#include <pjmedia/converter.h>
#include <pjmedia/delaybuf.h>
#include <pjmedia/echo.h>
#include <pjmedia/echo_port.h>
-#include <pjmedia/errno.h>
#include <pjmedia/endpoint.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/frame.h>
+#include <pjmedia/format.h>
#include <pjmedia/g711.h>
#include <pjmedia/jbuf.h>
#include <pjmedia/master_port.h>
@@ -64,6 +66,8 @@
#include <pjmedia/transport_loop.h>
#include <pjmedia/transport_srtp.h>
#include <pjmedia/transport_udp.h>
+#include <pjmedia/videoport.h>
+#include <pjmedia/vid_codec.h>
#include <pjmedia/wav_playlist.h>
#include <pjmedia/wav_port.h>
#include <pjmedia/wave.h>
diff --git a/pjmedia/include/pjmedia/avi.h b/pjmedia/include/pjmedia/avi.h
new file mode 100644
index 0000000..685f00e
--- /dev/null
+++ b/pjmedia/include/pjmedia/avi.h
@@ -0,0 +1,202 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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
+ */
+#ifndef __PJMEDIA_AVI_H__
+#define __PJMEDIA_AVI_H__
+
+
+/**
+ * @file avi.h
+ * @brief AVI file manipulation.
+ */
+
+/**
+ * @defgroup PJMEDIA_FILE_FORMAT File Formats
+ * @brief Supported file formats
+ */
+
+
+/**
+ * @defgroup PJMEDIA_AVI AVI Header
+ * @ingroup PJMEDIA_FILE_FORMAT
+ * @brief Representation of RIFF/AVI file format
+ * @{
+ *
+ * This the the low level representation of RIFF/AVI file format. For
+ * higher abstraction, please see \ref PJMEDIA_FILE_PLAY and
+ * \ref PJMEDIA_FILE_REC.
+ */
+
+
+PJ_BEGIN_DECL
+
+#define PJMEDIA_AVI_MAX_NUM_STREAMS 4
+
+static const char avi_tags[][4] = {
+ { 'R', 'I', 'F', 'F' }, { 'A', 'V', 'I', ' ' },
+ { 'h', 'd', 'r', 'l' }, { 'a', 'v', 'i', 'h' },
+ { 's', 't', 'r', 'l' }, { 's', 't', 'r', 'h' },
+ { 'a', 'u', 'd', 's' }, { 'v', 'i', 'd', 's' },
+ { 's', 't', 'r', 'f' }, { 'm', 'o', 'v', 'i' },
+ { 'L', 'I', 'S', 'T' }, { 'J', 'U', 'N', 'K' },
+};
+
+typedef enum {
+ PJMEDIA_AVI_RIFF_TAG = 0,
+ PJMEDIA_AVI_AVI_TAG,
+ PJMEDIA_AVI_HDRL_TAG,
+ PJMEDIA_AVI_AVIH_TAG,
+ PJMEDIA_AVI_STRL_TAG,
+ PJMEDIA_AVI_STRH_TAG,
+ PJMEDIA_AVI_AUDS_TAG,
+ PJMEDIA_AVI_VIDS_TAG,
+ PJMEDIA_AVI_STRF_TAG,
+ PJMEDIA_AVI_MOVI_TAG,
+ PJMEDIA_AVI_LIST_TAG,
+ PJMEDIA_AVI_JUNK_TAG,
+} pjmedia_avi_tag;
+
+
+/**
+ * These types describe the simpler/canonical version of an AVI file.
+ * They do not support the full AVI RIFF format specification.
+ */
+#pragma pack(2)
+
+/** This structure describes RIFF AVI file header */
+typedef struct riff_hdr_t {
+ pj_uint32_t riff; /**< "RIFF" ASCII tag. */
+ pj_uint32_t file_len; /**< File length minus 8 bytes */
+ pj_uint32_t avi; /**< "AVI" ASCII tag. */
+} riff_hdr_t;
+
+/** This structure describes avih header */
+typedef struct avih_hdr_t {
+ pj_uint32_t list_tag;
+ pj_uint32_t list_sz;
+ pj_uint32_t hdrl_tag;
+ pj_uint32_t avih;
+ pj_uint32_t size;
+ pj_uint32_t msec_per_frame; /**< microsecs between frames */
+ pj_uint32_t max_Bps;
+ pj_uint32_t pad;
+ pj_uint32_t flags;
+ pj_uint32_t tot_frames;
+ pj_uint32_t init_frames;
+ pj_uint32_t num_streams;
+ pj_uint32_t buf_size;
+ pj_uint32_t width;
+ pj_uint32_t height;
+ pj_uint32_t reserved[4];
+} avih_hdr_t;
+
+/** This structure describes strl header */
+typedef struct strl_hdr_t {
+ pj_uint32_t list_tag;
+ pj_uint32_t list_sz;
+ pj_uint32_t strl_tag;
+
+ pj_uint32_t strh;
+ pj_uint32_t strh_size;
+ pj_uint32_t data_type;
+ pj_uint32_t codec;
+ pj_uint32_t flags;
+ pj_uint32_t bogus_priority_language; /**< Do not access this data */
+ pj_uint32_t init_frames;
+ pj_uint32_t scale;
+ pj_uint32_t rate;
+ pj_uint32_t start;
+ pj_uint32_t length;
+ pj_uint32_t buf_size;
+ pj_uint32_t quality;
+ pj_uint32_t sample_size;
+ pj_uint32_t bogus_frame[2]; /**< Do not access this data */
+} strl_hdr_t;
+
+typedef struct {
+ pj_uint32_t strf;
+ pj_uint32_t strf_size;
+ pj_uint16_t fmt_tag; /**< 1 for PCM */
+ pj_uint16_t nchannels; /**< Number of channels. */
+ pj_uint32_t sample_rate; /**< Sampling rate. */
+ pj_uint32_t bytes_per_sec; /**< Average bytes per second. */
+ pj_uint16_t block_align; /**< nchannels * bits / 8 */
+ pj_uint16_t bits_per_sample; /**< Bits per sample. */
+ pj_uint16_t extra_size;
+} strf_audio_hdr_t;
+
+/**
+ * Sizes of strf_audio_hdr_t struct, started by the size (in bytes) of
+ * 32-bits struct members, alternated with the size of 16-bits members.
+ */
+static const pj_uint8_t strf_audio_hdr_sizes [] = {8, 4, 8, 6};
+
+typedef struct {
+ pj_uint32_t strf;
+ pj_uint32_t strf_size;
+ pj_uint32_t biSize;
+ pj_int32_t biWidth;
+ pj_int32_t biHeight;
+ pj_uint16_t biPlanes;
+ pj_uint16_t biBitCount;
+ pj_uint32_t biCompression;
+ pj_uint32_t biSizeImage;
+ pj_int32_t biXPelsPerMeter;
+ pj_int32_t biYPelsPerMeter;
+ pj_uint32_t biClrUsed;
+ pj_uint32_t biClrImportant;
+} strf_video_hdr_t;
+
+static const pj_uint8_t strf_video_hdr_sizes [] = {20, 4, 24};
+
+struct pjmedia_avi_hdr
+{
+ riff_hdr_t riff_hdr;
+ avih_hdr_t avih_hdr;
+ strl_hdr_t strl_hdr[PJMEDIA_AVI_MAX_NUM_STREAMS];
+ union {
+ strf_audio_hdr_t strf_audio_hdr;
+ strf_video_hdr_t strf_video_hdr;
+ } strf_hdr[PJMEDIA_AVI_MAX_NUM_STREAMS];
+};
+
+#pragma pack()
+
+/**
+ * @see pjmedia_avi_hdr
+ */
+typedef struct pjmedia_avi_hdr pjmedia_avi_hdr;
+
+/**
+ * This structure describes generic RIFF subchunk header.
+ */
+typedef struct pjmedia_avi_subchunk
+{
+ pj_uint32_t id; /**< Subchunk ASCII tag. */
+ pj_uint32_t len; /**< Length following this field */
+} pjmedia_avi_subchunk;
+
+
+PJ_END_DECL
+
+/**
+ * @}
+ */
+
+
+#endif /* __PJMEDIA_AVI_H__ */
diff --git a/pjmedia/include/pjmedia/avi_stream.h b/pjmedia/include/pjmedia/avi_stream.h
new file mode 100644
index 0000000..0c534ce
--- /dev/null
+++ b/pjmedia/include/pjmedia/avi_stream.h
@@ -0,0 +1,132 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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
+ */
+#ifndef __PJMEDIA_AVI_STREAM_H__
+#define __PJMEDIA_AVI_STREAM_H__
+
+/**
+ * @file avi_stream.h
+ * @brief AVI file player.
+ */
+#include <pjmedia/port.h>
+
+
+
+PJ_BEGIN_DECL
+
+
+/**
+ * @defgroup PJMEDIA_FILE_PLAY AVI File Player
+ * @ingroup PJMEDIA_PORT
+ * @brief Video and audio playback from AVI file
+ * @{
+ */
+
+/**
+ * AVI file player options.
+ */
+enum pjmedia_avi_file_player_option
+{
+ /**
+ * Tell the file player to return NULL frame when the whole
+ * file has been played.
+ */
+ PJMEDIA_AVI_FILE_NO_LOOP = 1
+};
+
+typedef pjmedia_port pjmedia_avi_stream;
+
+typedef struct pjmedia_avi_streams pjmedia_avi_streams;
+
+/**
+ * Create avi streams to play an AVI file. AVI player supports
+ * reading AVI file with uncompressed video format and
+ * 16 bit PCM or compressed G.711 A-law/U-law audio format.
+ *
+ * @param pool Pool to create the streams.
+ * @param filename File name to open.
+ * @param flags Avi streams creation flags.
+ * @param p_streams Pointer to receive the avi streams instance.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t)
+pjmedia_avi_player_create_streams(pj_pool_t *pool,
+ const char *filename,
+ unsigned flags,
+ pjmedia_avi_streams **p_streams);
+
+PJ_DECL(pj_uint8_t)
+pjmedia_avi_streams_get_num_streams(pjmedia_avi_streams *streams);
+
+PJ_DECL(pjmedia_avi_stream *)
+pjmedia_avi_streams_get_stream(pjmedia_avi_streams *streams,
+ pj_uint8_t idx);
+
+PJ_DECL(pjmedia_avi_stream *)
+pjmedia_avi_streams_get_stream_by_media(pjmedia_avi_streams *streams,
+ pj_uint8_t start_idx,
+ pjmedia_type media_type);
+
+PJ_INLINE(pjmedia_port *)
+pjmedia_avi_stream_get_port(pjmedia_avi_stream *stream)
+{
+ return (pjmedia_port *)stream;
+}
+
+/**
+ * Get the data length, in bytes.
+ *
+ * @param port The AVI stream.
+ *
+ * @return The length of the data, in bytes. Upon error it will
+ * return negative value.
+ */
+PJ_DECL(pj_ssize_t) pjmedia_avi_stream_get_len(pjmedia_avi_stream *stream);
+
+
+/**
+ * Register a callback to be called when the file reading has reached the
+ * end of file. If the file is set to play repeatedly, then the callback
+ * will be called multiple times. Note that only one callback can be
+ * registered for each AVI stream.
+ *
+ * @param port The AVI stream.
+ * @param user_data User data to be specified in the callback
+ * @param cb Callback to be called. If the callback returns non-
+ * PJ_SUCCESS, the playback will stop. Note that if
+ * application destroys the file port in the callback,
+ * it must return non-PJ_SUCCESS here.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(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));
+
+/**
+ * @}
+ */
+
+
+PJ_END_DECL
+
+
+#endif /* __PJMEDIA_AVI_STREAM_H__ */
diff --git a/pjmedia/include/pjmedia/circbuf.h b/pjmedia/include/pjmedia/circbuf.h
index 708efba..ffbc41b 100644
--- a/pjmedia/include/pjmedia/circbuf.h
+++ b/pjmedia/include/pjmedia/circbuf.h
@@ -29,6 +29,7 @@
#include <pj/assert.h>
#include <pj/errno.h>
#include <pj/pool.h>
+#include <pjmedia/frame.h>
/**
* @defgroup PJMED_CIRCBUF Circular Buffer
diff --git a/pjmedia/include/pjmedia/clock.h b/pjmedia/include/pjmedia/clock.h
index 2784838..8ca28f9 100644
--- a/pjmedia/include/pjmedia/clock.h
+++ b/pjmedia/include/pjmedia/clock.h
@@ -118,7 +118,12 @@
/**
- * Create media clock.
+ * Create media clock. This creates a media clock object that will run
+ * periodically at an interval that is calculated from the audio parameters.
+ * Once created, application must call #pjmedia_clock_start() to actually
+ * start the clock.
+ *
+ * @see pjmedia_clock_create2()
*
* @param pool Pool to allocate memory.
* @param clock_rate Number of samples per second.
@@ -143,6 +148,32 @@
void *user_data,
pjmedia_clock **p_clock);
+
+/**
+ * Create media clock. This creates a media clock object that will run
+ * periodically at the specified interval. Once created, application must
+ * call #pjmedia_clock_start() to actually start the clock.
+ *
+ * @param pool Pool to allocate memory.
+ * @param usec_interval The frame interval, in microseconds.
+ * @param clock_rate The media clock rate, to determine timestamp
+ * increment for each call.
+ * @param options Bitmask of pjmedia_clock_options.
+ * @param cb Callback to be called for each clock tick.
+ * @param user_data User data, which will be passed to the callback.
+ * @param p_clock Pointer to receive the clock instance.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error
+ * code.
+ */
+PJ_DECL(pj_status_t) pjmedia_clock_create2(pj_pool_t *pool,
+ unsigned usec_interval,
+ unsigned clock_rate,
+ unsigned options,
+ pjmedia_clock_callback *cb,
+ void *user_data,
+ pjmedia_clock **p_clock);
+
/**
* Start the clock. For clock created with asynchronous flag set to TRUE,
* this may start a worker thread for the clock (depending on the
diff --git a/pjmedia/include/pjmedia/config.h b/pjmedia/include/pjmedia/config.h
index 4d4f7ed..8491297 100644
--- a/pjmedia/include/pjmedia/config.h
+++ b/pjmedia/include/pjmedia/config.h
@@ -880,6 +880,93 @@
#endif
+/*
+ * .... new stuffs ...
+ */
+
+/**
+ * Specify if FFMPEG is available. The value here will be used as the default
+ * value for other FFMPEG settings below.
+ *
+ * Default: 0
+ */
+#ifndef PJMEDIA_HAS_FFMPEG
+# define PJMEDIA_HAS_FFMPEG 0
+#endif
+
+/**
+ * Specify if FFMPEG libavformat is available.
+ *
+ * Default: PJMEDIA_HAS_FFMPEG (or detected by configure)
+ */
+#ifndef PJMEDIA_HAS_LIBAVFORMAT
+# define PJMEDIA_HAS_LIBAVFORMAT PJMEDIA_HAS_FFMPEG
+#endif
+
+/**
+ * Specify if FFMPEG libavformat is available.
+ *
+ * Default: PJMEDIA_HAS_FFMPEG (or detected by configure)
+ */
+#ifndef PJMEDIA_HAS_LIBAVCODEC
+# define PJMEDIA_HAS_LIBAVCODEC PJMEDIA_HAS_FFMPEG
+#endif
+
+/**
+ * Specify if FFMPEG libavutil is available.
+ *
+ * Default: PJMEDIA_HAS_FFMPEG (or detected by configure)
+ */
+#ifndef PJMEDIA_HAS_LIBAVUTIL
+# define PJMEDIA_HAS_LIBAVUTIL PJMEDIA_HAS_FFMPEG
+#endif
+
+/**
+ * Specify if FFMPEG libswscale is available.
+ *
+ * Default: PJMEDIA_HAS_FFMPEG (or detected by configure)
+ */
+#ifndef PJMEDIA_HAS_LIBSWSCALE
+# define PJMEDIA_HAS_LIBSWSCALE PJMEDIA_HAS_FFMPEG
+#endif
+
+/**
+ * Specify if FFMPEG libavdevice is available.
+ *
+ * Default: PJMEDIA_HAS_FFMPEG (or detected by configure)
+ */
+#ifndef PJMEDIA_HAS_LIBAVDEVICE
+# define PJMEDIA_HAS_LIBAVDEVICE PJMEDIA_HAS_FFMPEG
+#endif
+
+/**
+ * Specify if FFMPEG libavcore is available.
+ *
+ * Default: PJMEDIA_HAS_FFMPEG (or detected by configure)
+ */
+#ifndef PJMEDIA_HAS_LIBAVCORE
+# define PJMEDIA_HAS_LIBAVCORE PJMEDIA_HAS_FFMPEG
+#endif
+
+/**
+ * Maximum video planes.
+ *
+ * Default: 4
+ */
+#ifndef PJMEDIA_MAX_VIDEO_PLANES
+# define PJMEDIA_MAX_VIDEO_PLANES 4
+#endif
+
+/**
+ * Maximum number of video formats.
+ *
+ * Default: 32
+ */
+#ifndef PJMEDIA_MAX_VIDEO_FORMATS
+# define PJMEDIA_MAX_VIDEO_FORMATS 32
+#endif
+
+
/**
* @}
*/
diff --git a/pjmedia/include/pjmedia/converter.h b/pjmedia/include/pjmedia/converter.h
new file mode 100644
index 0000000..8a9dd11
--- /dev/null
+++ b/pjmedia/include/pjmedia/converter.h
@@ -0,0 +1,322 @@
+/* $Id$ */
+/*
+ * 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
+ */
+#ifndef __PJMEDIA_CONVERTER_H__
+#define __PJMEDIA_CONVERTER_H__
+
+
+/**
+ * @file pjmedia/converter.h Format conversion utilities
+ * @brief Format conversion utilities
+ */
+
+#include <pjmedia/frame.h>
+#include <pjmedia/format.h>
+#include <pj/list.h>
+#include <pj/pool.h>
+
+
+/**
+ * @defgroup PJMEDIA_CONVERTER Format converter
+ * @ingroup PJMEDIA_FRAME_OP
+ * @brief Audio and video converter utilities
+ * @{
+ */
+
+PJ_BEGIN_DECL
+
+/**
+ * This describes conversion parameter. It specifies the source and
+ * destination formats of the conversion.
+ */
+typedef struct pjmedia_conversion_param
+{
+ pjmedia_format src; /**< Source format. */
+ pjmedia_format dst; /**< Destination format. */
+} pjmedia_conversion_param;
+
+
+/** Forward declaration of factory operation structure */
+typedef struct pjmedia_converter_factory_op pjmedia_converter_factory_op;
+
+/**
+ * Converter priority guides. Converter priority determines which converter
+ * instance to be used if more than one converters are able to perform the
+ * requested conversion. Converter implementor can use this value to order
+ * the preference based on attributes such as quality or performance. Higher
+ * number indicates higher priority.
+ */
+typedef enum pjmedia_converter_priority_guide
+{
+ /** Lowest priority. */
+ PJMEDIA_CONVERTER_PRIORITY_LOWEST = 0,
+
+ /** Normal priority. */
+ PJMEDIA_CONVERTER_PRIORITY_NORMAL = 15000,
+
+ /** Highest priority. */
+ PJMEDIA_CONVERTER_PRIORITY_HIGHEST = 32000
+} pjmedia_converter_priority_guide;
+
+/**
+ * Converter factory. The converter factory registers a callback function
+ * to create converters.
+ */
+typedef struct pjmedia_converter_factory
+{
+ /**
+ * Standard list members.
+ */
+ PJ_DECL_LIST_MEMBER(struct pjmedia_converter_factory);
+
+ /**
+ * Factory name.
+ */
+ const char *name;
+
+ /**
+ * Converter priority determines which converter instance to be used if
+ * more than one converters are able to perform the requested conversion.
+ * Converter implementor can use this value to order the preference based
+ * on attributes such as quality or performance. Higher number indicates
+ * higher priority. The pjmedia_converter_priority_guide enumeration shall
+ * be used as the base value to set the priority.
+ */
+ int priority;
+
+ /**
+ * Pointer to factory operation.
+ */
+ pjmedia_converter_factory_op *op;
+
+} pjmedia_converter_factory;
+
+/** Forward declaration for converter operation. */
+typedef struct pjmedia_converter_op pjmedia_converter_op;
+
+/**
+ * This structure describes a converter instance.
+ */
+typedef struct pjmedia_converter
+{
+ /**
+ * Pointer to converter operation.
+ */
+ pjmedia_converter_op *op;
+
+} pjmedia_converter;
+
+
+/**
+ * Converter factory operation.
+ */
+struct pjmedia_converter_factory_op
+{
+ /**
+ * This function creates a converter with the specified conversion format,
+ * if such format is supported.
+ *
+ * @param cf The converter factory.
+ * @param pool Pool to allocate memory from.
+ * @param prm Conversion parameter.
+ * @param p_cv Pointer to hold the created converter instance.
+ *
+ * @return PJ_SUCCESS if converter has been created successfully.
+ */
+ pj_status_t (*create_converter)(pjmedia_converter_factory *cf,
+ pj_pool_t *pool,
+ const pjmedia_conversion_param *prm,
+ pjmedia_converter **p_cv);
+
+ /**
+ * Destroy the factory.
+ *
+ * @param cf The converter factory.
+ */
+ void (*destroy_factory)(pjmedia_converter_factory *cf);
+};
+
+/**
+ * Converter operation.
+ */
+struct pjmedia_converter_op
+{
+ /**
+ * Convert the buffer in the source frame and save the result in the
+ * buffer of the destination frame, according to conversion format that
+ * was specified when the converter was created.
+ *
+ * Note that application should use #pjmedia_converter_convert() instead
+ * of calling this function directly.
+ *
+ * @param cv The converter instance.
+ * @param src_frame The source frame.
+ * @param dst_frame The destination frame.
+ *
+ * @return PJ_SUCCESS if conversion has been performed
+ * successfully.
+ */
+ pj_status_t (*convert)(pjmedia_converter *cv,
+ pjmedia_frame *src_frame,
+ pjmedia_frame *dst_frame);
+
+ /**
+ * Destroy the converter instance.
+ *
+ * Note that application should use #pjmedia_converter_destroy() instead
+ * of calling this function directly.
+ *
+ * @param cv The converter.
+ */
+ void (*destroy)(pjmedia_converter *cv);
+
+};
+
+
+/**
+ * Opaque data type for conversion manager. Typically, the conversion manager
+ * is a singleton instance, although application may instantiate more than one
+ * instances of this if required.
+ */
+typedef struct pjmedia_converter_mgr pjmedia_converter_mgr;
+
+
+/**
+ * Create a new conversion manager instance. This will also set the pointer
+ * to the singleton instance if the value is still NULL.
+ *
+ * @param pool Pool to allocate memory from.
+ * @param mgr Pointer to hold the created instance of the
+ * conversion manager.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_converter_mgr_create(pj_pool_t *pool,
+ pjmedia_converter_mgr **mgr);
+
+/**
+ * Get the singleton instance of the conversion manager.
+ *
+ * @return The instance.
+ */
+PJ_DECL(pjmedia_converter_mgr*) pjmedia_converter_mgr_instance(void);
+
+/**
+ * Manually assign a specific video manager instance as the singleton
+ * instance. Normally this is not needed if only one instance is ever
+ * going to be created, as the library automatically assign the singleton
+ * instance.
+ *
+ * @param mgr The instance to be used as the singleton instance.
+ * Application may specify NULL to clear the singleton
+ * singleton instance.
+ */
+PJ_DECL(void) pjmedia_converter_mgr_set_instance(pjmedia_converter_mgr *mgr);
+
+/**
+ * Destroy a converter manager. If the manager happens to be the singleton
+ * instance, the singleton instance will be set to NULL.
+ *
+ * @param mgr The converter manager. Specify NULL to use
+ * the singleton instance.
+ */
+PJ_DECL(void) pjmedia_converter_mgr_destroy(pjmedia_converter_mgr *mgr);
+
+/**
+ * Register a converter factory to the converter manager.
+ *
+ * @param mgr The converter manager. Specify NULL to use
+ * the singleton instance.
+ * @param f The converter factory to be registered.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t)
+pjmedia_converter_mgr_register_factory(pjmedia_converter_mgr *mgr,
+ pjmedia_converter_factory *f);
+
+/**
+ * Unregister a previously registered converter factory from the converter
+ * manager.
+ *
+ * @param mgr The converter manager. Specify NULL to use
+ * the singleton instance.
+ * @param f The converter factory to be unregistered.
+ * @param call_destroy If this is set to non-zero, the \a destroy_factory()
+ * callback of the factory will be called while
+ * unregistering the factory from the manager.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t)
+pjmedia_converter_mgr_unregister_factory(pjmedia_converter_mgr *mgr,
+ pjmedia_converter_factory *f,
+ pj_bool_t call_destroy);
+
+/**
+ * Create a converter instance to perform the specified format conversion
+ * as specified in \a param.
+ *
+ * @param mgr The converter manager. Specify NULL to use
+ * the singleton instance.
+ * @param pool Pool to allocate the memory from.
+ * @param param Conversion parameter.
+ * @param p_cv Pointer to hold the created converter.
+ *
+ * @return PJ_SUCCESS if a converter has been created successfully
+ * or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_converter_create(pjmedia_converter_mgr *mgr,
+ pj_pool_t *pool,
+ pjmedia_conversion_param *param,
+ pjmedia_converter **p_cv);
+
+/**
+ * Convert the buffer in the source frame and save the result in the
+ * buffer of the destination frame, according to conversion format that
+ * was specified when the converter was created.
+ *
+ * @param cv The converter instance.
+ * @param src_frame The source frame.
+ * @param dst_frame The destination frame.
+ *
+ * @return PJ_SUCCESS if conversion has been performed
+ * successfully.
+ */
+PJ_DECL(pj_status_t) pjmedia_converter_convert(pjmedia_converter *cv,
+ pjmedia_frame *src_frame,
+ pjmedia_frame *dst_frame);
+
+/**
+ * Destroy the converter.
+ *
+ * @param cv The converter instance.
+ */
+PJ_DECL(void) pjmedia_converter_destroy(pjmedia_converter *cv);
+
+
+PJ_END_DECL
+
+/**
+ * @}
+ */
+
+
+#endif /* __PJMEDIA_CONVERTER_H__ */
+
+
diff --git a/pjmedia/include/pjmedia/endpoint.h b/pjmedia/include/pjmedia/endpoint.h
index 725c975..6d12923 100644
--- a/pjmedia/include/pjmedia/endpoint.h
+++ b/pjmedia/include/pjmedia/endpoint.h
@@ -39,6 +39,7 @@
#include <pjmedia/codec.h>
#include <pjmedia/sdp.h>
+#include <pjmedia/transport.h>
PJ_BEGIN_DECL
diff --git a/pjmedia/include/pjmedia/errno.h b/pjmedia/include/pjmedia/errno.h
index 362936b..af6a7df 100644
--- a/pjmedia/include/pjmedia/errno.h
+++ b/pjmedia/include/pjmedia/errno.h
@@ -391,7 +391,11 @@
* Remote does not support RFC 2833
*/
#define PJMEDIA_RTP_EREMNORFC2833 (PJMEDIA_ERRNO_START+107) /* 220107 */
-
+/**
+ * @hideinitializer
+ * Invalid or bad format
+ */
+#define PJMEDIA_EBADFMT (PJMEDIA_ERRNO_START+108) /* 220108 */
/************************************************************
@@ -517,6 +521,11 @@
* Sound frame is too large for file buffer.
*/
#define PJMEDIA_EFRMFILETOOBIG (PJMEDIA_ERRNO_START+183) /* 220183 */
+/**
+ * @hideinitializer
+ * Unsupported AVI file.
+ */
+#define PJMEDIA_EAVIUNSUPP (PJMEDIA_ERRNO_START+191) /* 220191 */
/************************************************************
diff --git a/pjmedia/include/pjmedia/format.h b/pjmedia/include/pjmedia/format.h
new file mode 100644
index 0000000..2487340
--- /dev/null
+++ b/pjmedia/include/pjmedia/format.h
@@ -0,0 +1,706 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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_FORMAT_H__
+#define __PJMEDIA_FORMAT_H__
+
+/**
+ * @file pjmedia/format.h Media format
+ * @brief Media format
+ */
+#include <pjmedia/types.h>
+
+/**
+ * @defgroup PJMEDIA_FORMAT Media format
+ * @ingroup PJMEDIA_TYPES
+ * @brief Media format
+ * @{
+ */
+
+PJ_BEGIN_DECL
+
+/**
+ * Macro for packing format from a four character code, similar to FOURCC.
+ * This macro is used for building the constants in pjmedia_format_id
+ * enumeration.
+ */
+#define PJMEDIA_FORMAT_PACK(C1, C2, C3, C4) ( C4<<24 | C3<<16 | C2<<8 | C1 )
+
+/**
+ * This enumeration uniquely identify audio sample and/or video pixel formats.
+ * Some well known formats are listed here. The format ids are built by
+ * combining four character codes, similar to FOURCC. The format id is
+ * extensible, as application may define and use format ids not declared
+ * on this enumeration.
+ *
+ * This format id along with other information will fully describe the media
+ * in #pjmedia_format structure.
+ */
+typedef enum pjmedia_format_id
+{
+ /*
+ * Audio formats
+ */
+
+ /** 16bit signed integer linear PCM audio */
+ PJMEDIA_FORMAT_L16 = 0,
+
+ /** Alias for PJMEDIA_FORMAT_L16 */
+ PJMEDIA_FORMAT_PCM = PJMEDIA_FORMAT_L16,
+
+ /** G.711 ALAW */
+ PJMEDIA_FORMAT_PCMA = PJMEDIA_FORMAT_PACK('A', 'L', 'A', 'W'),
+
+ /** Alias for PJMEDIA_FORMAT_PCMA */
+ PJMEDIA_FORMAT_ALAW = PJMEDIA_FORMAT_PCMA,
+
+ /** G.711 ULAW */
+ PJMEDIA_FORMAT_PCMU = PJMEDIA_FORMAT_PACK('u', 'L', 'A', 'W'),
+
+ /** Aliaw for PJMEDIA_FORMAT_PCMU */
+ PJMEDIA_FORMAT_ULAW = PJMEDIA_FORMAT_PCMU,
+
+ /** AMR narrowband */
+ PJMEDIA_FORMAT_AMR = PJMEDIA_FORMAT_PACK(' ', 'A', 'M', 'R'),
+
+ /** ITU G.729 */
+ PJMEDIA_FORMAT_G729 = PJMEDIA_FORMAT_PACK('G', '7', '2', '9'),
+
+ /** Internet Low Bit-Rate Codec (ILBC) */
+ PJMEDIA_FORMAT_ILBC = PJMEDIA_FORMAT_PACK('I', 'L', 'B', 'C'),
+
+
+ /*
+ * Video formats.
+ */
+ /**
+ * 24bit RGB
+ */
+ PJMEDIA_FORMAT_RGB24 = PJMEDIA_FORMAT_PACK('R', 'G', 'B', '3'),
+
+ /**
+ * 32bit RGB with alpha channel
+ */
+ PJMEDIA_FORMAT_RGBA = PJMEDIA_FORMAT_PACK('R', 'G', 'B', 'A'),
+ PJMEDIA_FORMAT_BGRA = PJMEDIA_FORMAT_PACK('B', 'G', 'R', 'A'),
+
+ /**
+ * Alias for PJMEDIA_FORMAT_RGBA
+ */
+ PJMEDIA_FORMAT_RGB32 = PJMEDIA_FORMAT_RGBA,
+
+ /**
+ * Device Independent Bitmap, alias for 24 bit RGB
+ */
+ PJMEDIA_FORMAT_DIB = PJMEDIA_FORMAT_PACK('D', 'I', 'B', ' '),
+
+ /**
+ * This is a packed 4:4:4/32bpp format, where each pixel is encoded as
+ * four consecutive bytes, arranged in the following sequence: V0, U0,
+ * Y0, A0. Source:
+ * http://msdn.microsoft.com/en-us/library/dd206750%28v=VS.85%29.aspx#ayuv
+ */
+ PJMEDIA_FORMAT_AYUV = PJMEDIA_FORMAT_PACK('A', 'Y', 'U', 'V'),
+
+ /**
+ * This is packed 4:2:2/16bpp YUV format, the data can be treated as
+ * an array of unsigned char values, where the first byte contains
+ * the first Y sample, the second byte contains the first U (Cb) sample,
+ * the third byte contains the second Y sample, and the fourth byte
+ * contains the first V (Cr) sample, and so forth. Source:
+ * http://msdn.microsoft.com/en-us/library/dd206750%28v=VS.85%29.aspx#yuy2
+ */
+ PJMEDIA_FORMAT_YUY2 = PJMEDIA_FORMAT_PACK('Y', 'U', 'Y', '2'),
+
+ /**
+ * This format is the same as the YUY2 format except the byte order is
+ * reversed -- that is, the chroma and luma bytes are flipped. If the
+ * image is addressed as an array of two little-endian WORD values, the
+ * first WORD contains U in the LSBs and Y0 in the MSBs, and the second
+ * WORD contains V in the LSBs and Y1 in the MSBs. Source:
+ * http://msdn.microsoft.com/en-us/library/dd206750%28v=VS.85%29.aspx#uyvy
+ */
+ PJMEDIA_FORMAT_UYVY = PJMEDIA_FORMAT_PACK('U', 'Y', 'V', 'Y'),
+
+ /**
+ * This format is the same as the YUY2 and UYVY format except the byte
+ * order is reversed -- that is, the chroma and luma bytes are flipped.
+ * If the image is addressed as an array of two little-endian WORD values,
+ * the first WORD contains Y0 in the LSBs and V in the MSBs, and the second
+ * WORD contains Y1 in the LSBs and U in the MSBs.
+ */
+ PJMEDIA_FORMAT_YVYU = PJMEDIA_FORMAT_PACK('Y', 'V', 'Y', 'U'),
+
+ /**
+ * This is planar 4:2:0/12bpp YUV format, the data can be treated as
+ * three planes of color components, where the first plane contains
+ * only the Y samples, the second plane contains only the U (Cb) samples,
+ * and the third plane contains only the V (Cr) sample.
+ */
+ PJMEDIA_FORMAT_I420 = PJMEDIA_FORMAT_PACK('I', '4', '2', '0'),
+
+ /**
+ * IYUV is alias for I420.
+ */
+ PJMEDIA_FORMAT_IYUV = PJMEDIA_FORMAT_I420,
+
+ /**
+ * This is planar 4:2:2/16bpp YUV format.
+ */
+ PJMEDIA_FORMAT_YV12 = PJMEDIA_FORMAT_PACK('Y', 'V', '1', '2'),
+
+ /**
+ * The JPEG version of planar 4:2:0/12bpp YUV format.
+ */
+ PJMEDIA_FORMAT_I420JPEG = PJMEDIA_FORMAT_PACK('J', '4', '2', '0'),
+
+ /**
+ * The JPEG version of planar 4:2:2/16bpp YUV format.
+ */
+ PJMEDIA_FORMAT_I422JPEG = PJMEDIA_FORMAT_PACK('J', '4', '2', '2'),
+
+ /**
+ * Encoded video formats
+ */
+
+ PJMEDIA_FORMAT_H261 = PJMEDIA_FORMAT_PACK('H', '2', '6', '1'),
+ PJMEDIA_FORMAT_H263 = PJMEDIA_FORMAT_PACK('H', '2', '6', '3'),
+
+ PJMEDIA_FORMAT_MJPEG = PJMEDIA_FORMAT_PACK('M', 'J', 'P', 'G'),
+ PJMEDIA_FORMAT_MPEG1VIDEO = PJMEDIA_FORMAT_PACK('M', 'P', '1', 'V'),
+ PJMEDIA_FORMAT_MPEG2VIDEO = PJMEDIA_FORMAT_PACK('M', 'P', '2', 'V'),
+ PJMEDIA_FORMAT_MPEG4 = PJMEDIA_FORMAT_PACK('M', 'P', 'G', '4'),
+
+} pjmedia_format_id;
+
+/**
+ * This enumeration specifies what type of detail is included in a
+ * #pjmedia_format structure.
+ */
+typedef enum pjmedia_format_detail_type
+{
+ /** Format detail is not specified. */
+ PJMEDIA_FORMAT_DETAIL_NONE,
+
+ /** Audio format detail. */
+ PJMEDIA_FORMAT_DETAIL_AUDIO,
+
+ /** Video format detail. */
+ PJMEDIA_FORMAT_DETAIL_VIDEO,
+
+ /** Number of format detail type that has been defined. */
+ PJMEDIA_FORMAT_DETAIL_MAX
+
+} pjmedia_format_detail_type;
+
+/**
+ * This structure is put in \a detail field of #pjmedia_format to describe
+ * detail information about an audio media.
+ */
+typedef struct pjmedia_audio_format_detail
+{
+ unsigned clock_rate; /**< Audio clock rate in samples or Hz. */
+ unsigned channel_count; /**< Number of channels. */
+ unsigned frame_time_usec;/**< Frame interval, in microseconds. */
+ unsigned bits_per_sample;/**< Number of bits per sample. */
+ pj_uint32_t avg_bps; /**< Average bitrate */
+ pj_uint32_t max_bps; /**< Maximum bitrate */
+} pjmedia_audio_format_detail;
+
+/**
+ * This structure is put in \a detail field of #pjmedia_format to describe
+ * detail information about a video media.
+ *
+ * Additional information about a video format can also be retrieved by
+ * calling #pjmedia_get_video_format_info().
+ */
+typedef struct pjmedia_video_format_detail
+{
+ pjmedia_rect_size size; /**< Video size (width, height) */
+ pjmedia_ratio fps; /**< Number of frames per second. */
+ pj_uint32_t avg_bps;/**< Average bitrate. */
+ pj_uint32_t max_bps;/**< Maximum bitrate. */
+} pjmedia_video_format_detail;
+
+/**
+ * This macro declares the size of the detail section in #pjmedia_format
+ * to be reserved for user defined detail.
+ */
+#ifndef PJMEDIA_FORMAT_DETAIL_USER_SIZE
+# define PJMEDIA_FORMAT_DETAIL_USER_SIZE 1
+#endif
+
+/**
+ * This structure contains all the information needed to completely describe
+ * a media.
+ */
+typedef struct pjmedia_format
+{
+ /**
+ * The format id that specifies the audio sample or video pixel format.
+ * Some well known formats ids are declared in pjmedia_format_id
+ * enumeration.
+ *
+ * @see pjmedia_format_id
+ */
+ pj_uint32_t id;
+
+ /**
+ * The top-most type of the media, as an information.
+ */
+ pjmedia_type type;
+
+ /**
+ * The type of detail structure in the \a detail pointer.
+ */
+ pjmedia_format_detail_type detail_type;
+
+ /**
+ * Detail section to describe the media.
+ */
+ union
+ {
+ /**
+ * Detail section for audio format.
+ */
+ pjmedia_audio_format_detail aud;
+
+ /**
+ * Detail section for video format.
+ */
+ pjmedia_video_format_detail vid;
+
+ /**
+ * Reserved area for user-defined format detail.
+ */
+ char user[PJMEDIA_FORMAT_DETAIL_USER_SIZE];
+ } det;
+
+} pjmedia_format;
+
+/**
+ * This enumeration describes video color model. It mostly serves as
+ * information only.
+ */
+typedef enum pjmedia_color_model
+{
+ /** The color model is unknown or unspecified. */
+ PJMEDIA_COLOR_MODEL_NONE,
+
+ /** RGB color model. */
+ PJMEDIA_COLOR_MODEL_RGB,
+
+ /** YUV color model. */
+ PJMEDIA_COLOR_MODEL_YUV
+} pjmedia_color_model;
+
+/**
+ * This structure holds information to apply a specific video format
+ * against size and buffer information, and get additional information
+ * from it. To do that, application fills up the input fields of this
+ * structure, and give this structure to \a apply_fmt() function
+ * of #pjmedia_video_format_info structure.
+ */
+typedef struct pjmedia_video_apply_fmt_param
+{
+ /* input fields: */
+
+ /**
+ * [IN] The image size. This field is mandatory, and has to be set
+ * correctly prior to calling \a apply_fmt() function.
+ */
+ pjmedia_rect_size size;
+
+ /**
+ * [IN] Pointer to the buffer that holds the frame. The \a apply_fmt()
+ * function uses this pointer to calculate the pointer for each video
+ * planes of the media. This field is optional -- however, the
+ * \a apply_fmt() would still fill up the \a planes[] array with the
+ * correct pointer even though the buffer is set to NULL. This could be
+ * useful to calculate the size (in bytes) of each plane.
+ */
+ pj_uint8_t *buffer;
+
+ /* output fields: */
+
+ /**
+ * [OUT] The size (in bytes) required of the buffer to hold the video
+ * frame of the particular frame size (width, height).
+ */
+ pj_size_t framebytes;
+
+ /**
+ * [OUT] Array of strides value (in bytes) for each video plane.
+ */
+ int strides[PJMEDIA_MAX_VIDEO_PLANES];
+
+ /**
+ * [OUT] Array of pointers to each of the video planes. The values are
+ * calculated from the \a buffer field.
+ */
+ pj_uint8_t *planes[PJMEDIA_MAX_VIDEO_PLANES];
+
+ /**
+ * [OUT] Array of video plane sizes.
+ */
+ pj_size_t plane_bytes[PJMEDIA_MAX_VIDEO_PLANES];
+
+} pjmedia_video_apply_fmt_param;
+
+/**
+ * This structure holds information to describe a video format. Application
+ * can retrieve this structure by calling #pjmedia_get_video_format_info()
+ * funcion.
+ */
+typedef struct pjmedia_video_format_info
+{
+ /**
+ * The unique format ID of the media. Well known format ids are declared
+ * in pjmedia_format_id enumeration.
+ */
+ pj_uint32_t id;
+
+ /**
+ * Null terminated string containing short identification about the
+ * format.
+ */
+ char name[8];
+
+ /**
+ * Information about the color model of this video format.
+ */
+ pjmedia_color_model color_model;
+
+ /**
+ * Number of bits needed to store one pixel of this video format.
+ */
+ pj_uint8_t bpp;
+
+ /**
+ * Number of video planes that this format uses. Value 1 indicates
+ * packed format, while value greater than 1 indicates planar format.
+ */
+ pj_uint8_t plane_cnt;
+
+ /**
+ * Pointer to function to apply this format against size and buffer
+ * information in pjmedia_video_apply_fmt_param argument. Application
+ * uses this function to obtain various information such as the
+ * memory size of a frame buffer, strides value of the image, the
+ * location of the planes, and so on. See pjmedia_video_apply_fmt_param
+ * for additional information.
+ *
+ * @param vfi The video format info.
+ * @param vafp The parameters to investigate.
+ *
+ * @return PJ_SUCCESS if the function has calculated the
+ * information in \a vafp successfully.
+ */
+ pj_status_t (*apply_fmt)(const struct pjmedia_video_format_info *vfi,
+ pjmedia_video_apply_fmt_param *vafp);
+
+} pjmedia_video_format_info;
+
+
+/*****************************************************************************
+ * UTILITIES:
+ */
+
+/**
+ * General utility routine to calculate samples per frame value from clock
+ * rate, ptime (in usec), and channel count. Application should use this
+ * macro whenever possible due to possible overflow in the math calculation.
+ *
+ * @param clock_rate Clock rate.
+ * @param usec_ptime Frame interval, in microsecond.
+ * @param channel_count Number of channels.
+ *
+ * @return The samples per frame value.
+ */
+PJ_INLINE(unsigned) PJMEDIA_SPF(unsigned clock_rate, unsigned usec_ptime,
+ unsigned channel_count)
+{
+#if PJ_HAS_INT64
+ return ((unsigned)((pj_uint64_t)usec_ptime * \
+ clock_rate / channel_count / 1000000));
+#elif PJ_HAS_FLOATING_POINT
+ return ((unsigned)(usec_ptime * clock_rate / channel_count / 1000000.0));
+#else
+ return ((unsigned)(usec_ptime / 1000L * clock_rate / \
+ channel_count / 1000));
+#endif
+}
+
+/**
+ * Utility routine to calculate frame size (in bytes) from bitrate and frame
+ * interval values. Application should use this macro whenever possible due
+ * to possible overflow in the math calculation.
+ *
+ * @param bps The bitrate of the stream.
+ * @param usec_ptime Frame interval, in microsecond.
+ *
+ * @return Frame size in bytes.
+ */
+PJ_INLINE(unsigned) PJMEDIA_FSZ(unsigned bps, unsigned usec_ptime)
+{
+#if PJ_HAS_INT64
+ return ((unsigned)(bps * usec_ptime / PJ_UINT64(8000000)));
+#elif PJ_HAS_FLOATING_POINT
+ return ((unsigned)(bps * usec_ptime / 8000000.0));
+#else
+ return ((unsigned)(bps / 8L * usec_ptime / 1000000));
+#endif
+}
+
+
+/**
+ * Utility to retrieve samples_per_frame value from
+ * pjmedia_audio_format_detail.
+ *
+ * @param pafd Pointer to pjmedia_audio_format_detail
+ * @return Samples per frame
+ */
+PJ_INLINE(unsigned) PJMEDIA_AFD_SPF(const pjmedia_audio_format_detail *pafd)
+{
+ return PJMEDIA_SPF(pafd->clock_rate, pafd->frame_time_usec,
+ pafd->channel_count);
+}
+
+/**
+ * Utility to retrieve average frame size from pjmedia_audio_format_detail.
+ * The average frame size is derived from the average bitrate of the audio
+ * stream.
+ *
+ * @param afd Pointer to pjmedia_audio_format_detail
+ * @return Average frame size.
+ */
+PJ_INLINE(unsigned) PJMEDIA_AFD_AVG_FSZ(const pjmedia_audio_format_detail *afd)
+{
+ return PJMEDIA_FSZ(afd->avg_bps, afd->frame_time_usec);
+}
+
+/**
+ * Utility to retrieve maximum frame size from pjmedia_audio_format_detail.
+ * The maximum frame size is derived from the maximum bitrate of the audio
+ * stream.
+ *
+ * @param afd Pointer to pjmedia_audio_format_detail
+ * @return Average frame size.
+ */
+PJ_INLINE(unsigned) PJMEDIA_AFD_MAX_FSZ(const pjmedia_audio_format_detail *afd)
+{
+ return PJMEDIA_FSZ(afd->max_bps, afd->frame_time_usec);
+}
+
+
+/**
+ * Initialize the format as audio format with the specified parameters.
+ *
+ * @param fmt The format to be initialized.
+ * @param fmt_id Format ID. See #pjmedia_format_id
+ * @param clock_rate Audio clock rate.
+ * @param channel_count Number of channels.
+ * @param bits_per_sample Number of bits per sample.
+ * @param frame_time_usec Frame interval, in microsecond.
+ * @param avg_bps Average bitrate.
+ * @param max_bps Maximum bitrate.
+ */
+PJ_DECL(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);
+
+/**
+ * Initialize the format as video format with the specified parameters.
+ * A format manager should have been created, as this function will need
+ * to consult to a format manager in order to fill in detailed
+ * information about the format.
+ *
+ * @param fmt The format to be initialised.
+ * @param fmt_id Format ID. See #pjmedia_format_id
+ * @param width Image width.
+ * @param height Image heigth.
+ * @param fps_num FPS numerator.
+ * @param fps_denum FPS denumerator.
+ * @param avg_bps Average bitrate.
+ * @param max_bps Maximum bitrate.
+ */
+PJ_DECL(void) pjmedia_format_init_video(pjmedia_format *fmt,
+ pj_uint32_t fmt_id,
+ unsigned width,
+ unsigned height,
+ unsigned fps_num,
+ unsigned fps_denum);
+
+/**
+ * Copy format to another.
+ *
+ * @param dst The destination format.
+ * @param src The source format.
+ *
+ * @return Pointer to destination format.
+ */
+PJ_DECL(pjmedia_format*) pjmedia_format_copy(pjmedia_format *dst,
+ const pjmedia_format *src);
+
+/**
+ * Check if the format contains audio format, and retrieve the audio format
+ * detail in the format.
+ *
+ * @param fmt The format structure.
+ * @param assert_valid If this is set to non-zero, an assertion will be
+ * raised if the detail type is not audio or if the
+ * the detail is NULL.
+ *
+ * @return The instance of audio format detail in the format
+ * structure, or NULL if the format doesn't contain
+ * audio detail.
+ */
+PJ_DECL(pjmedia_audio_format_detail*)
+pjmedia_format_get_audio_format_detail(const pjmedia_format *fmt,
+ pj_bool_t assert_valid);
+
+/**
+ * Check if the format contains video format, and retrieve the video format
+ * detail in the format.
+ *
+ * @param fmt The format structure.
+ * @param assert_valid If this is set to non-zero, an assertion will be
+ * raised if the detail type is not video or if the
+ * the detail is NULL.
+ *
+ * @return The instance of video format detail in the format
+ * structure, or NULL if the format doesn't contain
+ * video detail.
+ */
+PJ_DECL(pjmedia_video_format_detail*)
+pjmedia_format_get_video_format_detail(const pjmedia_format *fmt,
+ pj_bool_t assert_valid);
+
+/*****************************************************************************
+ * FORMAT MANAGEMENT:
+ */
+
+/**
+ * Opaque data type for video format manager. The video format manager manages
+ * the repository of video formats that the framework recognises. Typically it
+ * is a singleton instance, although application may instantiate more than one
+ * instances of this if required.
+ */
+typedef struct pjmedia_video_format_mgr pjmedia_video_format_mgr;
+
+
+/**
+ * Create a new video format manager instance. This will also set the pointer
+ * to the singleton instance if the value is still NULL.
+ *
+ * @param pool The pool to allocate memory.
+ * @param max_fmt Maximum number of formats to accommodate.
+ * @param options Option flags. Must be zero for now.
+ * @param p_mgr Pointer to hold the created instance.
+ *
+ * @return PJ_SUCCESS on success, or the appripriate error value.
+ */
+PJ_DECL(pj_status_t)
+pjmedia_video_format_mgr_create(pj_pool_t *pool,
+ unsigned max_fmt,
+ unsigned options,
+ pjmedia_video_format_mgr **p_mgr);
+
+/**
+ * Get the singleton instance of the video format manager.
+ *
+ * @return The instance.
+ */
+PJ_DECL(pjmedia_video_format_mgr*) pjmedia_video_format_mgr_instance(void);
+
+/**
+ * Manually assign a specific video manager instance as the singleton
+ * instance. Normally this is not needed if only one instance is ever
+ * going to be created, as the library automatically assign the singleton
+ * instance.
+ *
+ * @param mgr The instance to be used as the singleton instance.
+ * Application may specify NULL to clear the singleton
+ * singleton instance.
+ */
+PJ_DECL(void)
+pjmedia_video_format_mgr_set_instance(pjmedia_video_format_mgr *mgr);
+
+/**
+ * Retrieve a video format info for the specified format id.
+ *
+ * @param mgr The video format manager. Specify NULL to use
+ * the singleton instance (however, a video format
+ * manager still must have been created prior to
+ * calling this function).
+ * @param id The format id which format info is to be
+ * retrieved.
+ *
+ * @return The video format info.
+ */
+PJ_DECL(const pjmedia_video_format_info*)
+pjmedia_get_video_format_info(pjmedia_video_format_mgr *mgr,
+ pj_uint32_t id);
+
+/**
+ * Register a new video format to the framework. By default, built-in
+ * formats will be registered automatically to the format manager when
+ * it is created (note: built-in formats are ones which format id is
+ * listed in pjmedia_format_id enumeration). This function allows
+ * application to use user defined format id by registering that format
+ * into the framework.
+ *
+ * @param mgr The video format manager. Specify NULL to use
+ * the singleton instance (however, a video format
+ * manager still must have been created prior to
+ * calling this function).
+ * @param vfi The video format info to be registered. This
+ * structure must remain valid until the format
+ * manager is destroyed.
+ *
+ * @return PJ_SUCCESS on success, or the appripriate error value.
+ */
+PJ_DECL(pj_status_t)
+pjmedia_register_video_format_info(pjmedia_video_format_mgr *mgr,
+ pjmedia_video_format_info *vfi);
+
+/**
+ * Destroy a video format manager. If the manager happens to be the singleton
+ * instance, the singleton instance will be set to NULL.
+ *
+ * @param mgr The video format manager. Specify NULL to use
+ * the singleton instance (however, a video format
+ * manager still must have been created prior to
+ * calling this function).
+ */
+PJ_DECL(void) pjmedia_video_format_mgr_destroy(pjmedia_video_format_mgr *mgr);
+
+PJ_END_DECL
+
+/**
+ * @}
+ */
+
+#endif /* __PJMEDIA_FORMAT_H__ */
+
diff --git a/pjmedia/include/pjmedia/frame.h b/pjmedia/include/pjmedia/frame.h
new file mode 100644
index 0000000..0df67e9
--- /dev/null
+++ b/pjmedia/include/pjmedia/frame.h
@@ -0,0 +1,316 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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_FRAME_H__
+#define __PJMEDIA_FRAME_H__
+
+/**
+ * @file pjmedia/frame.h Media frame
+ * @brief Frame
+ */
+#include <pjmedia/types.h>
+#include <pj/string.h>
+
+/**
+ * @defgroup PJMEDIA_FRAME Media frame
+ * @ingroup PJMEDIA_TYPES
+ * @brief Frame
+ * @{
+ */
+
+PJ_BEGIN_DECL
+
+
+/**
+ * Types of media frame.
+ */
+typedef enum pjmedia_frame_type
+{
+ PJMEDIA_FRAME_TYPE_NONE, /**< No frame. */
+ PJMEDIA_FRAME_TYPE_AUDIO, /**< Normal audio frame. */
+ PJMEDIA_FRAME_TYPE_EXTENDED, /**< Extended audio frame. */
+ PJMEDIA_FRAME_TYPE_VIDEO /**< Video frame. */
+
+} pjmedia_frame_type;
+
+
+/**
+ * This structure describes a media frame.
+ */
+typedef struct pjmedia_frame
+{
+ pjmedia_frame_type type; /**< Frame type. */
+ void *buf; /**< Pointer to buffer. */
+ pj_size_t size; /**< Frame size in bytes. */
+ pj_timestamp timestamp; /**< Frame timestamp. */
+ pj_uint32_t bit_info; /**< Bit info of the frame, sample case:
+ a frame may not exactly start and end
+ at the octet boundary, so this field
+ may be used for specifying start &
+ end bit offset. */
+} pjmedia_frame;
+
+
+/**
+ * The pjmedia_frame_ext is used to carry a more complex audio frames than
+ * the typical PCM audio frames, and it is signaled by setting the "type"
+ * field of a pjmedia_frame to PJMEDIA_FRAME_TYPE_EXTENDED. With this set,
+ * application may typecast pjmedia_frame to pjmedia_frame_ext.
+ *
+ * This structure may contain more than one audio frames, which subsequently
+ * will be called subframes in this structure. The subframes section
+ * immediately follows the end of this structure, and each subframe is
+ * represented by pjmedia_frame_ext_subframe structure. Every next
+ * subframe immediately follows the previous subframe, and all subframes
+ * are byte-aligned although its payload may not be byte-aligned.
+ */
+
+#pragma pack(1)
+typedef struct pjmedia_frame_ext {
+ pjmedia_frame base; /**< Base frame info */
+ pj_uint16_t samples_cnt; /**< Number of samples in this frame */
+ pj_uint16_t subframe_cnt; /**< Number of (sub)frames in this frame */
+
+ /* Zero or more (sub)frames follows immediately after this,
+ * each will be represented by pjmedia_frame_ext_subframe
+ */
+} pjmedia_frame_ext;
+#pragma pack()
+
+/**
+ * This structure represents the individual subframes in the
+ * pjmedia_frame_ext structure.
+ */
+#pragma pack(1)
+typedef struct pjmedia_frame_ext_subframe {
+ pj_uint16_t bitlen; /**< Number of bits in the data */
+ pj_uint8_t data[1]; /**< Start of encoded data */
+} pjmedia_frame_ext_subframe;
+
+#pragma pack()
+
+
+/**
+ * Append one subframe to #pjmedia_frame_ext.
+ *
+ * @param frm The #pjmedia_frame_ext.
+ * @param src Subframe data.
+ * @param bitlen Lenght of subframe, in bits.
+ * @param samples_cnt Number of audio samples in subframe.
+ */
+PJ_INLINE(void) pjmedia_frame_ext_append_subframe(pjmedia_frame_ext *frm,
+ const void *src,
+ unsigned bitlen,
+ unsigned samples_cnt)
+{
+ pjmedia_frame_ext_subframe *fsub;
+ pj_uint8_t *p;
+ unsigned i;
+
+ p = (pj_uint8_t*)frm + sizeof(pjmedia_frame_ext);
+ for (i = 0; i < frm->subframe_cnt; ++i) {
+ fsub = (pjmedia_frame_ext_subframe*) p;
+ p += sizeof(fsub->bitlen) + ((fsub->bitlen+7) >> 3);
+ }
+
+ fsub = (pjmedia_frame_ext_subframe*) p;
+ fsub->bitlen = (pj_uint16_t)bitlen;
+ if (bitlen)
+ pj_memcpy(fsub->data, src, (bitlen+7) >> 3);
+
+ frm->subframe_cnt++;
+ frm->samples_cnt = (pj_uint16_t)(frm->samples_cnt + samples_cnt);
+}
+
+/**
+ * Get a subframe from #pjmedia_frame_ext.
+ *
+ * @param frm The #pjmedia_frame_ext.
+ * @param n Subframe index, zero based.
+ *
+ * @return The n-th subframe, or NULL if n is out-of-range.
+ */
+PJ_INLINE(pjmedia_frame_ext_subframe*)
+pjmedia_frame_ext_get_subframe(const pjmedia_frame_ext *frm, unsigned n)
+{
+ pjmedia_frame_ext_subframe *sf = NULL;
+
+ if (n < frm->subframe_cnt) {
+ pj_uint8_t *p;
+ unsigned i;
+
+ p = (pj_uint8_t*)frm + sizeof(pjmedia_frame_ext);
+ for (i = 0; i < n; ++i) {
+ sf = (pjmedia_frame_ext_subframe*) p;
+ p += sizeof(sf->bitlen) + ((sf->bitlen+7) >> 3);
+ }
+
+ sf = (pjmedia_frame_ext_subframe*) p;
+ }
+
+ return sf;
+}
+
+/**
+ * Extract all frame payload to the specified buffer.
+ *
+ * @param frm The frame.
+ * @param dst Destination buffer.
+ * @param maxlen Maximum size to copy (i.e. the size of the
+ * destination buffer).
+ *
+ * @return Total size of payload copied.
+ */
+PJ_INLINE(unsigned)
+pjmedia_frame_ext_copy_payload(const pjmedia_frame_ext *frm,
+ void *dst,
+ unsigned maxlen)
+{
+ unsigned i, copied=0;
+ for (i=0; i<frm->subframe_cnt; ++i) {
+ pjmedia_frame_ext_subframe *sf;
+ unsigned sz;
+
+ sf = pjmedia_frame_ext_get_subframe(frm, i);
+ if (!sf)
+ continue;
+
+ sz = ((sf->bitlen + 7) >> 3);
+ if (sz + copied > maxlen)
+ break;
+
+ pj_memcpy(((pj_uint8_t*)dst) + copied, sf->data, sz);
+ copied += sz;
+ }
+ return copied;
+}
+
+
+/**
+ * Pop out first n subframes from #pjmedia_frame_ext.
+ *
+ * @param frm The #pjmedia_frame_ext.
+ * @param n Number of first subframes to be popped out.
+ *
+ * @return PJ_SUCCESS when successful.
+ */
+PJ_INLINE(pj_status_t)
+pjmedia_frame_ext_pop_subframes(pjmedia_frame_ext *frm, unsigned n)
+{
+ pjmedia_frame_ext_subframe *sf;
+ pj_uint8_t *move_src;
+ unsigned move_len;
+
+ if (frm->subframe_cnt <= n) {
+ frm->subframe_cnt = 0;
+ frm->samples_cnt = 0;
+ return PJ_SUCCESS;
+ }
+
+ move_src = (pj_uint8_t*)pjmedia_frame_ext_get_subframe(frm, n);
+ sf = pjmedia_frame_ext_get_subframe(frm, frm->subframe_cnt-1);
+ move_len = (pj_uint8_t*)sf - move_src + sizeof(sf->bitlen) +
+ ((sf->bitlen+7) >> 3);
+ pj_memmove((pj_uint8_t*)frm+sizeof(pjmedia_frame_ext),
+ move_src, move_len);
+
+ frm->samples_cnt = (pj_uint16_t)
+ (frm->samples_cnt - n*frm->samples_cnt/frm->subframe_cnt);
+ frm->subframe_cnt = (pj_uint16_t) (frm->subframe_cnt - n);
+
+ return PJ_SUCCESS;
+}
+
+
+/**
+ * This is a general purpose function set PCM samples to zero.
+ * Since this function is needed by many parts of the library,
+ * by putting this functionality in one place, it enables some.
+ * clever people to optimize this function.
+ *
+ * @param samples The 16bit PCM samples.
+ * @param count Number of samples.
+ */
+PJ_INLINE(void) pjmedia_zero_samples(pj_int16_t *samples, unsigned count)
+{
+#if 1
+ pj_bzero(samples, (count<<1));
+#elif 0
+ unsigned i;
+ for (i=0; i<count; ++i) samples[i] = 0;
+#else
+ unsigned i;
+ count >>= 1;
+ for (i=0; i<count; ++i) ((pj_int32_t*)samples)[i] = (pj_int32_t)0;
+#endif
+}
+
+
+/**
+ * This is a general purpose function to copy samples from/to buffers with
+ * equal size. Since this function is needed by many parts of the library,
+ * by putting this functionality in one place, it enables some.
+ * clever people to optimize this function.
+ */
+PJ_INLINE(void) pjmedia_copy_samples(pj_int16_t *dst, const pj_int16_t *src,
+ unsigned count)
+{
+#if 1
+ pj_memcpy(dst, src, (count<<1));
+#elif 0
+ unsigned i;
+ for (i=0; i<count; ++i) dst[i] = src[i];
+#else
+ unsigned i;
+ count >>= 1;
+ for (i=0; i<count; ++i)
+ ((pj_int32_t*)dst)[i] = ((pj_int32_t*)src)[i];
+#endif
+}
+
+
+/**
+ * This is a general purpose function to copy samples from/to buffers with
+ * equal size. Since this function is needed by many parts of the library,
+ * by putting this functionality in one place, it enables some.
+ * clever people to optimize this function.
+ */
+PJ_INLINE(void) pjmedia_move_samples(pj_int16_t *dst, const pj_int16_t *src,
+ unsigned count)
+{
+#if 1
+ pj_memmove(dst, src, (count<<1));
+#elif 0
+ unsigned i;
+ for (i=0; i<count; ++i) dst[i] = src[i];
+#else
+ unsigned i;
+ count >>= 1;
+ for (i=0; i<count; ++i)
+ ((pj_int32_t*)dst)[i] = ((pj_int32_t*)src)[i];
+#endif
+}
+
+PJ_END_DECL
+
+/**
+ * @}
+ */
+
+#endif /* __PJMEDIA_FRAME_H__ */
diff --git a/pjmedia/include/pjmedia/port.h b/pjmedia/include/pjmedia/port.h
index c460575..594f618 100644
--- a/pjmedia/include/pjmedia/port.h
+++ b/pjmedia/include/pjmedia/port.h
@@ -1,6 +1,6 @@
/* $Id$ */
/*
- * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2008-2010 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
@@ -24,7 +24,8 @@
* @file port.h
* @brief Port interface declaration
*/
-#include <pjmedia/types.h>
+#include <pjmedia/format.h>
+#include <pjmedia/frame.h>
#include <pj/assert.h>
#include <pj/os.h>
@@ -184,6 +185,13 @@
/**
+ * Create 32bit port signature from ASCII characters.
+ */
+#define PJMEDIA_PORT_SIGNATURE(a,b,c,d) \
+ (a<<24 | b<<16 | c<<8 | d)
+
+
+/**
* Port operation setting.
*/
typedef enum pjmedia_port_op
@@ -220,19 +228,133 @@
{
pj_str_t name; /**< Port name. */
pj_uint32_t signature; /**< Port signature. */
- pjmedia_type type; /**< Media type. */
- pj_bool_t has_info; /**< Has info? */
- pj_bool_t need_info; /**< Need info on connect? */
- unsigned pt; /**< Payload type (can be dynamic). */
- pjmedia_format format; /**< Format. */
- pj_str_t encoding_name; /**< Encoding name. */
- unsigned clock_rate; /**< Sampling rate. */
- unsigned channel_count; /**< Number of channels. */
- unsigned bits_per_sample; /**< Bits/sample */
- unsigned samples_per_frame; /**< No of samples per frame. */
- unsigned bytes_per_frame; /**< No of bytes per frame. */
+ pjmedia_dir dir; /**< Port direction. */
+ pjmedia_format fmt; /**< Format. */
} pjmedia_port_info;
+/**
+ * Utility to retrieve audio clock rate/sampling rate value from
+ * pjmedia_port_info.
+ *
+ * @param pia Pointer to port info containing audio format.
+ * @return Audio clock rate.
+ */
+PJ_INLINE(unsigned) PJMEDIA_PIA_SRATE(const pjmedia_port_info *pia)
+{
+ pj_assert(pia->fmt.type==PJMEDIA_TYPE_AUDIO &&
+ pia->fmt.detail_type==PJMEDIA_FORMAT_DETAIL_AUDIO);
+ return pia->fmt.det.aud.clock_rate;
+}
+
+/**
+ * Utility to retrieve audio channel count value from pjmedia_port_info.
+ *
+ * @param pia Pointer to port info containing audio format.
+ * @return Audio channel count.
+ */
+PJ_INLINE(unsigned) PJMEDIA_PIA_CCNT(const pjmedia_port_info *pia)
+{
+ pj_assert(pia->fmt.type==PJMEDIA_TYPE_AUDIO &&
+ pia->fmt.detail_type==PJMEDIA_FORMAT_DETAIL_AUDIO);
+ return pia->fmt.det.aud.channel_count;
+}
+
+/**
+ * Utility to retrieve audio bits per sample value from pjmedia_port_info.
+ *
+ * @param pia Pointer to port info containing audio format.
+ * @return Number of bits per sample.
+ */
+PJ_INLINE(unsigned) PJMEDIA_PIA_BITS(const pjmedia_port_info *pia)
+{
+ pj_assert(pia->fmt.type==PJMEDIA_TYPE_AUDIO &&
+ pia->fmt.detail_type==PJMEDIA_FORMAT_DETAIL_AUDIO);
+ return pia->fmt.det.aud.bits_per_sample;
+}
+
+/**
+ * Utility to retrieve audio frame interval (ptime) value from
+ * pjmedia_port_info.
+ *
+ * @param pia Pointer to port info containing audio format.
+ * @return Frame interval in msec.
+ */
+PJ_INLINE(unsigned) PJMEDIA_PIA_PTIME(const pjmedia_port_info *pia)
+{
+ pj_assert(pia->fmt.type==PJMEDIA_TYPE_AUDIO &&
+ pia->fmt.detail_type==PJMEDIA_FORMAT_DETAIL_AUDIO);
+ return pia->fmt.det.aud.frame_time_usec / 1000;
+}
+
+/**
+ * This is a utility routine to retrieve the audio samples_per_frame value
+ * from port info.
+ *
+ * @param pia Pointer to port info containing audio format.
+ * @return Samples per frame value.
+ */
+PJ_INLINE(unsigned) PJMEDIA_PIA_SPF(const pjmedia_port_info *pia)
+{
+ pj_assert(pia->fmt.type==PJMEDIA_TYPE_AUDIO &&
+ pia->fmt.detail_type==PJMEDIA_FORMAT_DETAIL_AUDIO);
+ return PJMEDIA_AFD_SPF(&pia->fmt.det.aud);
+}
+
+/**
+ * This is a utility routine to retrieve the average bitrate value
+ * from port info.
+ *
+ * @param pia Pointer to port info containing audio format.
+ * @return Bitrate, in bits per second.
+ */
+PJ_INLINE(unsigned) PJMEDIA_PIA_AVG_BPS(const pjmedia_port_info *pia)
+{
+ pj_assert(pia->fmt.type==PJMEDIA_TYPE_AUDIO &&
+ pia->fmt.detail_type==PJMEDIA_FORMAT_DETAIL_AUDIO);
+ return pia->fmt.det.aud.avg_bps;
+}
+
+/**
+ * This is a utility routine to retrieve the maximum bitrate value
+ * from port info.
+ *
+ * @param pia Pointer to port info containing audio format.
+ * @return Bitrate, in bits per second.
+ */
+PJ_INLINE(unsigned) PJMEDIA_PIA_MAX_BPS(const pjmedia_port_info *pia)
+{
+ pj_assert(pia->fmt.type==PJMEDIA_TYPE_AUDIO &&
+ pia->fmt.detail_type==PJMEDIA_FORMAT_DETAIL_AUDIO);
+ return pia->fmt.det.aud.max_bps;
+}
+
+/**
+ * This is a utility routine to retrieve the average audio frame size value
+ * from pjmedia_port_info.
+ *
+ * @param pia Pointer to port info containing audio format.
+ * @return Frame size in bytes.
+ */
+PJ_INLINE(unsigned) PJMEDIA_PIA_AVG_FSZ(const pjmedia_port_info *pia)
+{
+ pj_assert(pia->fmt.type==PJMEDIA_TYPE_AUDIO &&
+ pia->fmt.detail_type==PJMEDIA_FORMAT_DETAIL_AUDIO);
+ return PJMEDIA_AFD_AVG_FSZ(&pia->fmt.det.aud);
+}
+
+/**
+ * Utility to retrieve audio frame size from maximum bitrate from
+ * pjmedia_port_info.
+ *
+ * @param pia Pointer to port info containing audio format.
+ * @return Frame size in bytes.
+ */
+PJ_INLINE(unsigned) PJMEDIA_PIA_MAX_FSZ(const pjmedia_port_info *pia)
+{
+ pj_assert(pia->fmt.type==PJMEDIA_TYPE_AUDIO &&
+ pia->fmt.detail_type==PJMEDIA_FORMAT_DETAIL_AUDIO);
+ return PJMEDIA_AFD_MAX_FSZ(&pia->fmt.det.aud);
+}
/**
* Port interface.
@@ -254,7 +376,7 @@
* This should only be called by #pjmedia_port_put_frame().
*/
pj_status_t (*put_frame)(struct pjmedia_port *this_port,
- const pjmedia_frame *frame);
+ pjmedia_frame *frame);
/**
* Source interface.
@@ -276,6 +398,7 @@
* ports which deal with PCM audio.
*
* @param info The port info to be initialized.
+ * @param pool Pool to allocate memory from.
* @param name Port name.
* @param signature Port signature.
* @param clock_rate Port's clock rate.
@@ -293,6 +416,24 @@
unsigned bits_per_sample,
unsigned samples_per_frame);
+/**
+ * This is an auxiliary function to initialize port info for
+ * ports which deal with PCM audio.
+ *
+ * @param info The port info to be initialized.
+ * @param name Port name.
+ * @param signature Port signature.
+ * @param dir Port's direction.
+ * @param fmt Port's media format.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(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);
+
/**
* Get a frame from the port (and subsequent downstream ports).
@@ -314,7 +455,7 @@
* @return PJ_SUCCESS on success, or the appropriate error code.
*/
PJ_DECL(pj_status_t) pjmedia_port_put_frame( pjmedia_port *port,
- const pjmedia_frame *frame );
+ pjmedia_frame *frame );
/**
diff --git a/pjmedia/include/pjmedia/sdp.h b/pjmedia/include/pjmedia/sdp.h
index 456a047..3a4e7ae 100644
--- a/pjmedia/include/pjmedia/sdp.h
+++ b/pjmedia/include/pjmedia/sdp.h
@@ -25,7 +25,7 @@
* @brief SDP header file.
*/
#include <pjmedia/types.h>
-
+#include <pj/sock.h>
/**
* @defgroup PJMEDIA_SDP SDP Parsing and Data Structure
diff --git a/pjmedia/include/pjmedia/stream.h b/pjmedia/include/pjmedia/stream.h
index c3454ac..3c30810 100644
--- a/pjmedia/include/pjmedia/stream.h
+++ b/pjmedia/include/pjmedia/stream.h
@@ -32,6 +32,7 @@
#include <pjmedia/port.h>
#include <pjmedia/rtcp.h>
#include <pjmedia/transport.h>
+#include <pjmedia/vid_codec.h>
#include <pj/sock.h>
PJ_BEGIN_DECL
@@ -133,6 +134,9 @@
(see #PJMEDIA_STREAM_ENABLE_KA)
is enabled? */
#endif
+
+ pjmedia_vid_codec_info vid_codec_info;
+ pjmedia_vid_codec_param *vid_codec_param;
};
diff --git a/pjmedia/include/pjmedia/transport.h b/pjmedia/include/pjmedia/transport.h
index a6a6ff4..6ccb493 100644
--- a/pjmedia/include/pjmedia/transport.h
+++ b/pjmedia/include/pjmedia/transport.h
@@ -28,6 +28,7 @@
#include <pjmedia/types.h>
#include <pjmedia/errno.h>
+#include <pj/string.h>
/**
* @defgroup PJMEDIA_TRANSPORT Media Transport
@@ -257,6 +258,35 @@
/**
+ * Media socket info is used to describe the underlying sockets
+ * to be used as media transport.
+ */
+typedef struct pjmedia_sock_info
+{
+ /** The RTP socket handle */
+ pj_sock_t rtp_sock;
+
+ /** Address to be advertised as the local address for the RTP
+ * socket, which does not need to be equal as the bound
+ * address (for example, this address can be the address resolved
+ * with STUN).
+ */
+ pj_sockaddr rtp_addr_name;
+
+ /** The RTCP socket handle. */
+ pj_sock_t rtcp_sock;
+
+ /** Address to be advertised as the local address for the RTCP
+ * socket, which does not need to be equal as the bound
+ * address (for example, this address can be the address resolved
+ * with STUN).
+ */
+ pj_sockaddr rtcp_addr_name;
+
+} pjmedia_sock_info;
+
+
+/**
* This structure describes the operations for the stream transport.
*/
struct pjmedia_transport_op
diff --git a/pjmedia/include/pjmedia/types.h b/pjmedia/include/pjmedia/types.h
index c39a424..5fd3dae 100644
--- a/pjmedia/include/pjmedia/types.h
+++ b/pjmedia/include/pjmedia/types.h
@@ -1,6 +1,6 @@
/* $Id$ */
/*
- * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2008-2010 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
@@ -26,8 +26,9 @@
*/
#include <pjmedia/config.h>
-#include <pj/sock.h> /* pjmedia_sock_info */
-#include <pj/string.h> /* pj_memcpy(), pj_memset() */
+#include <pj/sock.h>
+#include <pj/types.h>
+
/**
* @defgroup PJMEDIA_PORT Media Ports Framework
@@ -52,22 +53,20 @@
*/
typedef enum pjmedia_type
{
- /** No type. */
- PJMEDIA_TYPE_NONE = 0,
+ /** Type is not specified. */
+ PJMEDIA_TYPE_NONE,
/** The media is audio */
- PJMEDIA_TYPE_AUDIO = 1,
+ PJMEDIA_TYPE_AUDIO,
/** The media is video. */
- PJMEDIA_TYPE_VIDEO = 2,
-
- /** Unknown media type, in this case the name will be specified in
- * encoding_name.
- */
- PJMEDIA_TYPE_UNKNOWN = 3,
+ PJMEDIA_TYPE_VIDEO,
/** The media is application. */
- PJMEDIA_TYPE_APPLICATION = 4
+ PJMEDIA_TYPE_APPLICATION,
+
+ /** The media type is unknown or unsupported. */
+ PJMEDIA_TYPE_UNKNOWN
} pjmedia_type;
@@ -100,428 +99,98 @@
/** None */
PJMEDIA_DIR_NONE = 0,
- /** Encoding (outgoing to network) stream */
+ /** Encoding (outgoing to network) stream, also known as capture */
PJMEDIA_DIR_ENCODING = 1,
- /** Decoding (incoming from network) stream. */
+ /** Same as encoding direction. */
+ PJMEDIA_DIR_CAPTURE = PJMEDIA_DIR_ENCODING,
+
+ /** Decoding (incoming from network) stream, also known as playback. */
PJMEDIA_DIR_DECODING = 2,
- /** Incoming and outgoing stream. */
- PJMEDIA_DIR_ENCODING_DECODING = 3
+ /** Same as decoding. */
+ PJMEDIA_DIR_PLAYBACK = PJMEDIA_DIR_DECODING,
+
+ /** Same as decoding. */
+ PJMEDIA_DIR_RENDER = PJMEDIA_DIR_DECODING,
+
+ /** Incoming and outgoing stream, same as PJMEDIA_DIR_CAPTURE_PLAYBACK */
+ PJMEDIA_DIR_ENCODING_DECODING = 3,
+
+ /** Same as ENCODING_DECODING */
+ PJMEDIA_DIR_CAPTURE_PLAYBACK = PJMEDIA_DIR_ENCODING_DECODING,
+
+ /** Same as ENCODING_DECODING */
+ PJMEDIA_DIR_CAPTURE_RENDER = PJMEDIA_DIR_ENCODING_DECODING
} pjmedia_dir;
-
-/* Alternate names for media direction: */
-
-/**
- * Direction is capturing audio frames.
- */
-#define PJMEDIA_DIR_CAPTURE PJMEDIA_DIR_ENCODING
-
-/**
- * Direction is playback of audio frames.
- */
-#define PJMEDIA_DIR_PLAYBACK PJMEDIA_DIR_DECODING
-
-/**
- * Direction is both capture and playback.
- */
-#define PJMEDIA_DIR_CAPTURE_PLAYBACK PJMEDIA_DIR_ENCODING_DECODING
-
-
-/**
- * Create 32bit port signature from ASCII characters.
- */
-#define PJMEDIA_PORT_SIGNATURE(a,b,c,d) \
- (a<<24 | b<<16 | c<<8 | d)
-
-
/**
* Opaque declaration of media endpoint.
*/
typedef struct pjmedia_endpt pjmedia_endpt;
-
/*
* Forward declaration for stream (needed by transport).
*/
typedef struct pjmedia_stream pjmedia_stream;
-
/**
- * Media socket info is used to describe the underlying sockets
- * to be used as media transport.
+ * Enumeration for picture coordinate base.
*/
-typedef struct pjmedia_sock_info
-{
- /** The RTP socket handle */
- pj_sock_t rtp_sock;
-
- /** Address to be advertised as the local address for the RTP
- * socket, which does not need to be equal as the bound
- * address (for example, this address can be the address resolved
- * with STUN).
- */
- pj_sockaddr rtp_addr_name;
-
- /** The RTCP socket handle. */
- pj_sock_t rtcp_sock;
-
- /** Address to be advertised as the local address for the RTCP
- * socket, which does not need to be equal as the bound
- * address (for example, this address can be the address resolved
- * with STUN).
- */
- pj_sockaddr rtcp_addr_name;
-
-} pjmedia_sock_info;
-
-
-/**
- * Macro for packing format.
- */
-#define PJMEDIA_FORMAT_PACK(C1, C2, C3, C4) ( C4<<24 | C3<<16 | C2<<8 | C1 )
-
-/**
- * This enumeration describes format ID.
- */
-typedef enum pjmedia_format_id
+typedef enum pjmedia_coord_base
{
/**
- * 16bit linear
+ * This specifies that the pixel [0, 0] location is at the left-top
+ * position.
*/
- PJMEDIA_FORMAT_L16 = 0,
-
- /**
- * Alias for PJMEDIA_FORMAT_L16
- */
- PJMEDIA_FORMAT_PCM = PJMEDIA_FORMAT_L16,
+ PJMEDIA_COORD_BASE_LEFT_TOP,
/**
- * G.711 ALAW
+ * This specifies that the pixel [0, 0] location is at the left-bottom
+ * position.
*/
- PJMEDIA_FORMAT_PCMA = PJMEDIA_FORMAT_PACK('A', 'L', 'A', 'W'),
+ PJMEDIA_COORD_BASE_LEFT_BOTTOM
- /**
- * Alias for PJMEDIA_FORMAT_PCMA
- */
- PJMEDIA_FORMAT_ALAW = PJMEDIA_FORMAT_PCMA,
-
- /**
- * G.711 ULAW
- */
- PJMEDIA_FORMAT_PCMU = PJMEDIA_FORMAT_PACK('u', 'L', 'A', 'W'),
-
- /**
- * Aliaw for PJMEDIA_FORMAT_PCMU
- */
- PJMEDIA_FORMAT_ULAW = PJMEDIA_FORMAT_PCMU,
-
- /**
- * AMR narrowband
- */
- PJMEDIA_FORMAT_AMR = PJMEDIA_FORMAT_PACK(' ', 'A', 'M', 'R'),
-
- /**
- * ITU G.729
- */
- PJMEDIA_FORMAT_G729 = PJMEDIA_FORMAT_PACK('G', '7', '2', '9'),
-
- /**
- * Internet Low Bit-Rate Codec (ILBC)
- */
- PJMEDIA_FORMAT_ILBC = PJMEDIA_FORMAT_PACK('I', 'L', 'B', 'C')
-
-} pjmedia_format_id;
-
+} pjmedia_coord_base;
/**
- * Media format information.
+ * This structure is used to represent rational numbers.
*/
-typedef struct pjmedia_format
+typedef struct pjmedia_ratio
{
- /** Format ID */
- pjmedia_format_id id;
-
- /** Bitrate. */
- pj_uint32_t bitrate;
-
- /** Flag to indicate whether VAD is enabled */
- pj_bool_t vad;
-
-} pjmedia_format;
-
-
+ int num; /** < Numerator. */
+ int denum; /** < Denumerator. */
+} pjmedia_ratio;
/**
- * This is a general purpose function set PCM samples to zero.
- * Since this function is needed by many parts of the library,
- * by putting this functionality in one place, it enables some.
- * clever people to optimize this function.
- *
- * @param samples The 16bit PCM samples.
- * @param count Number of samples.
+ * This structure represent a coordinate.
*/
-PJ_INLINE(void) pjmedia_zero_samples(pj_int16_t *samples, unsigned count)
+typedef struct pjmedia_coord
{
-#if 1
- pj_bzero(samples, (count<<1));
-#elif 0
- unsigned i;
- for (i=0; i<count; ++i) samples[i] = 0;
-#else
- unsigned i;
- count >>= 1;
- for (i=0; i<count; ++i) ((pj_int32_t*)samples)[i] = (pj_int32_t)0;
-#endif
-}
-
+ int x; /**< X position of the coordinate */
+ int y; /**< Y position of the coordinate */
+} pjmedia_coord;
/**
- * This is a general purpose function to copy samples from/to buffers with
- * equal size. Since this function is needed by many parts of the library,
- * by putting this functionality in one place, it enables some.
- * clever people to optimize this function.
+ * This structure represents rectangle size.
*/
-PJ_INLINE(void) pjmedia_copy_samples(pj_int16_t *dst, const pj_int16_t *src,
- unsigned count)
+typedef struct pjmedia_rect_size
{
-#if 1
- pj_memcpy(dst, src, (count<<1));
-#elif 0
- unsigned i;
- for (i=0; i<count; ++i) dst[i] = src[i];
-#else
- unsigned i;
- count >>= 1;
- for (i=0; i<count; ++i)
- ((pj_int32_t*)dst)[i] = ((pj_int32_t*)src)[i];
-#endif
-}
-
+ unsigned w; /**< The width. */
+ unsigned h; /**< The height. */
+} pjmedia_rect_size;
/**
- * This is a general purpose function to copy samples from/to buffers with
- * equal size. Since this function is needed by many parts of the library,
- * by putting this functionality in one place, it enables some.
- * clever people to optimize this function.
+ * This structure describes a rectangle.
*/
-PJ_INLINE(void) pjmedia_move_samples(pj_int16_t *dst, const pj_int16_t *src,
- unsigned count)
+typedef struct pjmedia_rect
{
-#if 1
- pj_memmove(dst, src, (count<<1));
-#elif 0
- unsigned i;
- for (i=0; i<count; ++i) dst[i] = src[i];
-#else
- unsigned i;
- count >>= 1;
- for (i=0; i<count; ++i)
- ((pj_int32_t*)dst)[i] = ((pj_int32_t*)src)[i];
-#endif
-}
+ pjmedia_coord coord; /**< The position. */
+ pjmedia_rect_size size; /**< The size. */
+} pjmedia_rect;
-/**
- * Types of media frame.
- */
-typedef enum pjmedia_frame_type
-{
- PJMEDIA_FRAME_TYPE_NONE, /**< No frame. */
- PJMEDIA_FRAME_TYPE_AUDIO, /**< Normal audio frame. */
- PJMEDIA_FRAME_TYPE_EXTENDED /**< Extended audio frame. */
-
-} pjmedia_frame_type;
-
-
-/**
- * This structure describes a media frame.
- */
-typedef struct pjmedia_frame
-{
- pjmedia_frame_type type; /**< Frame type. */
- void *buf; /**< Pointer to buffer. */
- pj_size_t size; /**< Frame size in bytes. */
- pj_timestamp timestamp; /**< Frame timestamp. */
- pj_uint32_t bit_info; /**< Bit info of the frame, sample case:
- a frame may not exactly start and end
- at the octet boundary, so this field
- may be used for specifying start &
- end bit offset. */
-} pjmedia_frame;
-
-
-/**
- * The pjmedia_frame_ext is used to carry a more complex audio frames than
- * the typical PCM audio frames, and it is signaled by setting the "type"
- * field of a pjmedia_frame to PJMEDIA_FRAME_TYPE_EXTENDED. With this set,
- * application may typecast pjmedia_frame to pjmedia_frame_ext.
- *
- * This structure may contain more than one audio frames, which subsequently
- * will be called subframes in this structure. The subframes section
- * immediately follows the end of this structure, and each subframe is
- * represented by pjmedia_frame_ext_subframe structure. Every next
- * subframe immediately follows the previous subframe, and all subframes
- * are byte-aligned although its payload may not be byte-aligned.
- */
-
-#pragma pack(1)
-typedef struct pjmedia_frame_ext {
- pjmedia_frame base; /**< Base frame info */
- pj_uint16_t samples_cnt; /**< Number of samples in this frame */
- pj_uint16_t subframe_cnt; /**< Number of (sub)frames in this frame */
-
- /* Zero or more (sub)frames follows immediately after this,
- * each will be represented by pjmedia_frame_ext_subframe
- */
-} pjmedia_frame_ext;
-#pragma pack()
-
-/**
- * This structure represents the individual subframes in the
- * pjmedia_frame_ext structure.
- */
-#pragma pack(1)
-typedef struct pjmedia_frame_ext_subframe {
- pj_uint16_t bitlen; /**< Number of bits in the data */
- pj_uint8_t data[1]; /**< Start of encoded data */
-} pjmedia_frame_ext_subframe;
-
-#pragma pack()
-
-
-/**
- * Append one subframe to #pjmedia_frame_ext.
- *
- * @param frm The #pjmedia_frame_ext.
- * @param src Subframe data.
- * @param bitlen Lenght of subframe, in bits.
- * @param samples_cnt Number of audio samples in subframe.
- */
-PJ_INLINE(void) pjmedia_frame_ext_append_subframe(pjmedia_frame_ext *frm,
- const void *src,
- unsigned bitlen,
- unsigned samples_cnt)
-{
- pjmedia_frame_ext_subframe *fsub;
- pj_uint8_t *p;
- unsigned i;
-
- p = (pj_uint8_t*)frm + sizeof(pjmedia_frame_ext);
- for (i = 0; i < frm->subframe_cnt; ++i) {
- fsub = (pjmedia_frame_ext_subframe*) p;
- p += sizeof(fsub->bitlen) + ((fsub->bitlen+7) >> 3);
- }
-
- fsub = (pjmedia_frame_ext_subframe*) p;
- fsub->bitlen = (pj_uint16_t)bitlen;
- if (bitlen)
- pj_memcpy(fsub->data, src, (bitlen+7) >> 3);
-
- frm->subframe_cnt++;
- frm->samples_cnt = (pj_uint16_t)(frm->samples_cnt + samples_cnt);
-}
-
-/**
- * Get a subframe from #pjmedia_frame_ext.
- *
- * @param frm The #pjmedia_frame_ext.
- * @param n Subframe index, zero based.
- *
- * @return The n-th subframe, or NULL if n is out-of-range.
- */
-PJ_INLINE(pjmedia_frame_ext_subframe*)
-pjmedia_frame_ext_get_subframe(const pjmedia_frame_ext *frm, unsigned n)
-{
- pjmedia_frame_ext_subframe *sf = NULL;
-
- if (n < frm->subframe_cnt) {
- pj_uint8_t *p;
- unsigned i;
-
- p = (pj_uint8_t*)frm + sizeof(pjmedia_frame_ext);
- for (i = 0; i < n; ++i) {
- sf = (pjmedia_frame_ext_subframe*) p;
- p += sizeof(sf->bitlen) + ((sf->bitlen+7) >> 3);
- }
-
- sf = (pjmedia_frame_ext_subframe*) p;
- }
-
- return sf;
-}
-
-/**
- * Extract all frame payload to the specified buffer.
- *
- * @param frm The frame.
- * @param dst Destination buffer.
- * @param maxlen Maximum size to copy (i.e. the size of the
- * destination buffer).
- *
- * @return Total size of payload copied.
- */
-PJ_INLINE(unsigned)
-pjmedia_frame_ext_copy_payload(const pjmedia_frame_ext *frm,
- void *dst,
- unsigned maxlen)
-{
- unsigned i, copied=0;
- for (i=0; i<frm->subframe_cnt; ++i) {
- pjmedia_frame_ext_subframe *sf;
- unsigned sz;
-
- sf = pjmedia_frame_ext_get_subframe(frm, i);
- if (!sf)
- continue;
-
- sz = ((sf->bitlen + 7) >> 3);
- if (sz + copied > maxlen)
- break;
-
- pj_memcpy(((pj_uint8_t*)dst) + copied, sf->data, sz);
- copied += sz;
- }
- return copied;
-}
-
-
-/**
- * Pop out first n subframes from #pjmedia_frame_ext.
- *
- * @param frm The #pjmedia_frame_ext.
- * @param n Number of first subframes to be popped out.
- *
- * @return PJ_SUCCESS when successful.
- */
-PJ_INLINE(pj_status_t)
-pjmedia_frame_ext_pop_subframes(pjmedia_frame_ext *frm, unsigned n)
-{
- pjmedia_frame_ext_subframe *sf;
- pj_uint8_t *move_src;
- unsigned move_len;
-
- if (frm->subframe_cnt <= n) {
- frm->subframe_cnt = 0;
- frm->samples_cnt = 0;
- return PJ_SUCCESS;
- }
-
- move_src = (pj_uint8_t*)pjmedia_frame_ext_get_subframe(frm, n);
- sf = pjmedia_frame_ext_get_subframe(frm, frm->subframe_cnt-1);
- move_len = (pj_uint8_t*)sf - move_src + sizeof(sf->bitlen) +
- ((sf->bitlen+7) >> 3);
- pj_memmove((pj_uint8_t*)frm+sizeof(pjmedia_frame_ext),
- move_src, move_len);
-
- frm->samples_cnt = (pj_uint16_t)
- (frm->samples_cnt - n*frm->samples_cnt/frm->subframe_cnt);
- frm->subframe_cnt = (pj_uint16_t) (frm->subframe_cnt - n);
-
- return PJ_SUCCESS;
-}
/**
diff --git a/pjmedia/include/pjmedia/vid_codec.h b/pjmedia/include/pjmedia/vid_codec.h
new file mode 100644
index 0000000..d5321b5
--- /dev/null
+++ b/pjmedia/include/pjmedia/vid_codec.h
@@ -0,0 +1,673 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 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_VID_CODEC_H__
+#define __PJMEDIA_VID_CODEC_H__
+
+
+/**
+ * @file codec.h
+ * @brief Codec framework.
+ */
+
+#include <pjmedia/codec.h>
+#include <pjmedia/format.h>
+#include <pjmedia/types.h>
+#include <pj/list.h>
+#include <pj/pool.h>
+
+PJ_BEGIN_DECL
+
+#define PJMEDIA_VID_CODEC_MAX_DEC_FMT_CNT 8
+#define PJMEDIA_VID_CODEC_MAX_FPS_CNT 16
+
+/**
+ * Identification used to search for codec factory that supports specific
+ * codec specification.
+ */
+typedef struct pjmedia_vid_codec_info
+{
+ pjmedia_format_id fmt_id; /**< Encoded format ID */
+ pj_str_t encoding_name; /**< Encoding name */
+ pjmedia_dir dir; /**< Direction */
+ unsigned pt; /**< Payload type (may be 0 for
+ dynamic payload type) */
+ unsigned clock_rate; /**< (?) Clock rate */
+ unsigned dec_fmt_id_cnt;
+ pjmedia_format_id dec_fmt_id[PJMEDIA_VID_CODEC_MAX_DEC_FMT_CNT];
+ /**< Supported encoding source
+ format IDs */
+ unsigned fps_cnt; /**< zero if support any fps */
+ pjmedia_ratio fps[PJMEDIA_VID_CODEC_MAX_FPS_CNT];
+ /**< (?) supported FPSes,
+ ffmpeg has this */
+} pjmedia_vid_codec_info;
+
+/**
+ * Detailed codec attributes used in configuring a codec and in querying
+ * the capability of codec factories. Default attributes of any codecs could
+ * be queried using #pjmedia_vid_codec_mgr_get_default_param() and modified
+ * using #pjmedia_vid_codec_mgr_set_default_param().
+ *
+ * Please note that codec parameter also contains SDP specific setting,
+ * #dec_fmtp and #enc_fmtp, which may need to be set appropriately based on
+ * the effective setting. See each codec documentation for more detail.
+ */
+typedef struct pjmedia_vid_codec_param
+{
+ pjmedia_dir dir; /**< Direction */
+ pjmedia_format enc_fmt; /**< Encoded format */
+ pjmedia_format dec_fmt; /**< Decoded format */
+
+ pjmedia_codec_fmtp enc_fmtp; /**< Encoder fmtp params */
+ pjmedia_codec_fmtp dec_fmtp; /**< Decoder fmtp params */
+
+ unsigned enc_mtu; /**< MTU or max payload size setting*/
+ unsigned pt; /**< Payload type. */
+} pjmedia_vid_codec_param;
+
+
+
+/*
+ * Forward declaration for pjmedia_vid_codec.
+ */
+typedef struct pjmedia_vid_codec pjmedia_vid_codec;
+
+
+/**
+ * This structure describes codec operations. Each codec MUST implement
+ * all of these functions.
+ */
+typedef struct pjmedia_vid_codec_op
+{
+ /**
+ * Initialize codec using the specified attribute.
+ *
+ * @param codec The codec instance.
+ * @param pool Pool to use when the codec needs to allocate
+ * some memory.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+ pj_status_t (*init)(pjmedia_vid_codec *codec,
+ pj_pool_t *pool );
+
+ /**
+ * Open the codec and initialize with the specified parameter.
+ * Upon successful initialization, the codec may modify the parameter
+ * and fills in the unspecified values (such as enc_ptime, when
+ * encoder ptime is different than decoder ptime).
+ *
+ * @param codec The codec instance.
+ * @param param Codec initialization parameter.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+ pj_status_t (*open)(pjmedia_vid_codec *codec,
+ pjmedia_vid_codec_param *param );
+
+ /**
+ * Close and shutdown codec, releasing all resources allocated by
+ * this codec, if any.
+ *
+ * @param codec The codec instance.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+ pj_status_t (*close)(pjmedia_vid_codec *codec);
+
+ /**
+ * Modify the codec parameter after the codec is open.
+ * Note that not all codec parameters can be modified during run-time.
+ * When the parameter cannot be changed, this function will return
+ * non-PJ_SUCCESS, and the original parameters will not be changed.
+ *
+ * Application can expect changing trivial codec settings such as
+ * changing VAD setting to succeed.
+ *
+ * @param codec The codec instance.
+ * @param param The new codec parameter.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+ pj_status_t (*modify)(pjmedia_vid_codec *codec,
+ const pjmedia_vid_codec_param *param );
+
+ /**
+ * Instruct the codec to inspect the specified payload/packet and
+ * split the packet into individual base frames. Each output frames will
+ * have ptime that is equal to basic frame ptime (i.e. the value of
+ * info.frm_ptime in #pjmedia_vid_codec_param).
+ *
+ * @param codec The codec instance
+ * @param pkt The input packet.
+ * @param pkt_size Size of the packet.
+ * @param timestamp The timestamp of the first sample in the packet.
+ * @param frame_cnt On input, specifies the maximum number of frames
+ * in the array. On output, the codec must fill
+ * with number of frames detected in the packet.
+ * @param frames On output, specifies the frames that have been
+ * detected in the packet.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+ pj_status_t (*packetize) (pjmedia_vid_codec *codec,
+ pj_uint8_t *buf,
+ pj_size_t buf_len,
+ unsigned *pos,
+ const pj_uint8_t **payload,
+ pj_size_t *payload_len);
+
+ /**
+ * Instruct the codec to inspect the specified payload/packet and
+ * split the packet into individual base frames. Each output frames will
+ * have ptime that is equal to basic frame ptime (i.e. the value of
+ * info.frm_ptime in #pjmedia_vid_codec_param).
+ *
+ * @param codec The codec instance
+ * @param pkt The input packet.
+ * @param pkt_size Size of the packet.
+ * @param timestamp The timestamp of the first sample in the packet.
+ * @param frame_cnt On input, specifies the maximum number of frames
+ * in the array. On output, the codec must fill
+ * with number of frames detected in the packet.
+ * @param frames On output, specifies the frames that have been
+ * detected in the packet.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+ pj_status_t (*unpacketize)(pjmedia_vid_codec *codec,
+ const pj_uint8_t *payload,
+ pj_size_t payload_len,
+ pj_uint8_t *buf,
+ pj_size_t *buf_len);
+
+ /**
+ * Instruct the codec to encode the specified input frame. The input
+ * PCM samples MUST have ptime that is multiplication of base frame
+ * ptime (i.e. the value of info.frm_ptime in #pjmedia_vid_codec_param).
+ *
+ * @param codec The codec instance.
+ * @param input The input frame.
+ * @param out_size The length of buffer in the output frame.
+ * @param output The output frame.
+ *
+ * @return PJ_SUCCESS on success;
+ */
+ pj_status_t (*encode)(pjmedia_vid_codec *codec,
+ const pjmedia_frame *input,
+ unsigned out_size,
+ pjmedia_frame *output);
+
+ /**
+ * Instruct the codec to decode the specified input frame. The input
+ * frame MUST have ptime that is exactly equal to base frame
+ * ptime (i.e. the value of info.frm_ptime in #pjmedia_vid_codec_param).
+ * Application can achieve this by parsing the packet into base
+ * frames before decoding each frame.
+ *
+ * @param codec The codec instance.
+ * @param input The input frame.
+ * @param out_size The length of buffer in the output frame.
+ * @param output The output frame.
+ *
+ * @return PJ_SUCCESS on success;
+ */
+ pj_status_t (*decode)(pjmedia_vid_codec *codec,
+ const pjmedia_frame *input,
+ unsigned out_size,
+ pjmedia_frame *output);
+
+ /**
+ * Instruct the codec to recover a missing frame.
+ *
+ * @param codec The codec instance.
+ * @param out_size The length of buffer in the output frame.
+ * @param output The output frame where generated signal
+ * will be placed.
+ *
+ * @return PJ_SUCCESS on success;
+ */
+ pj_status_t (*recover)(pjmedia_vid_codec *codec,
+ unsigned out_size,
+ pjmedia_frame *output);
+} pjmedia_vid_codec_op;
+
+
+
+/*
+ * Forward declaration for pjmedia_vid_codec_factory.
+ */
+typedef struct pjmedia_vid_codec_factory pjmedia_vid_codec_factory;
+
+
+/**
+ * This structure describes a codec instance.
+ */
+struct pjmedia_vid_codec
+{
+ /** Entries to put this codec instance in codec factory's list. */
+ PJ_DECL_LIST_MEMBER(struct pjmedia_vid_codec);
+
+ /** Codec's private data. */
+ void *codec_data;
+
+ /** Codec factory where this codec was allocated. */
+ pjmedia_vid_codec_factory *factory;
+
+ /** Operations to codec. */
+ pjmedia_vid_codec_op *op;
+};
+
+
+
+/**
+ * This structure describes operations that must be supported by codec
+ * factories.
+ */
+typedef struct pjmedia_vid_codec_factory_op
+{
+ /**
+ * Check whether the factory can create codec with the specified
+ * codec info.
+ *
+ * @param factory The codec factory.
+ * @param info The codec info.
+ *
+ * @return PJ_SUCCESS if this factory is able to create an
+ * instance of codec with the specified info.
+ */
+ pj_status_t (*test_alloc)(pjmedia_vid_codec_factory *factory,
+ const pjmedia_vid_codec_info *info );
+
+ /**
+ * Create default attributes for the specified codec ID. This function
+ * can be called by application to get the capability of the codec.
+ *
+ * @param factory The codec factory.
+ * @param info The codec info.
+ * @param attr The attribute to be initialized.
+ *
+ * @return PJ_SUCCESS if success.
+ */
+ pj_status_t (*default_attr)(pjmedia_vid_codec_factory *factory,
+ const pjmedia_vid_codec_info *info,
+ pjmedia_vid_codec_param *attr );
+
+ /**
+ * Enumerate supported codecs that can be created using this factory.
+ *
+ * @param factory The codec factory.
+ * @param count On input, specifies the number of elements in
+ * the array. On output, the value will be set to
+ * the number of elements that have been initialized
+ * by this function.
+ * @param info The codec info array, which contents will be
+ * initialized upon return.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+ pj_status_t (*enum_info)(pjmedia_vid_codec_factory *factory,
+ unsigned *count,
+ pjmedia_vid_codec_info codecs[]);
+
+ /**
+ * Create one instance of the codec with the specified codec info.
+ *
+ * @param factory The codec factory.
+ * @param info The codec info.
+ * @param p_codec Pointer to receive the codec instance.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+ pj_status_t (*alloc_codec)(pjmedia_vid_codec_factory *factory,
+ const pjmedia_vid_codec_info *info,
+ pjmedia_vid_codec **p_codec);
+
+ /**
+ * This function is called by codec manager to return a particular
+ * instance of codec back to the codec factory.
+ *
+ * @param factory The codec factory.
+ * @param codec The codec instance to be returned.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+ pj_status_t (*dealloc_codec)(pjmedia_vid_codec_factory *factory,
+ pjmedia_vid_codec *codec );
+
+} pjmedia_vid_codec_factory_op;
+
+
+
+/**
+ * Codec factory describes a module that is able to create codec with specific
+ * capabilities. These capabilities can be queried by codec manager to create
+ * instances of codec.
+ */
+struct pjmedia_vid_codec_factory
+{
+ /** Entries to put this structure in the codec manager list. */
+ PJ_DECL_LIST_MEMBER(struct pjmedia_vid_codec_factory);
+
+ /** The factory's private data. */
+ void *factory_data;
+
+ /** Operations to the factory. */
+ pjmedia_vid_codec_factory_op *op;
+
+};
+
+
+/**
+ * Declare maximum codecs
+ */
+#define PJMEDIA_VID_CODEC_MGR_MAX_CODECS 32
+
+
+/**
+ * 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_param *def_param; /**< Default codecs
+ parameters. */
+} pjmedia_vid_codec_desc;
+
+
+/**
+ * The declaration for codec manager. Application doesn't normally need
+ * to see this declaration, but nevertheless this declaration is needed
+ * by media endpoint to instantiate the codec manager.
+ */
+typedef struct pjmedia_vid_codec_mgr
+{
+ /** 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];
+
+} pjmedia_vid_codec_mgr;
+
+
+
+/**
+ * Initialize codec manager. Normally this function is called by pjmedia
+ * endpoint's initialization code.
+ *
+ * @param mgr Codec manager instance.
+ * @param pf Pool factory instance.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_codec_mgr_create(pj_pool_t *pool,
+ pjmedia_vid_codec_mgr **mgr);
+
+
+/**
+ * Destroy codec manager. Normally this function is called by pjmedia
+ * endpoint's deinitialization code.
+ *
+ * @param mgr Codec manager instance.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_codec_mgr_destroy(pjmedia_vid_codec_mgr *mgr);
+
+PJ_DECL(pjmedia_vid_codec_mgr*) pjmedia_vid_codec_mgr_instance(void);
+PJ_DECL(void) pjmedia_vid_codec_mgr_set_instance(pjmedia_vid_codec_mgr* mgr);
+
+
+/**
+ * Register codec factory to codec manager. This will also register
+ * all supported codecs in the factory to the codec manager.
+ *
+ * @param mgr The codec manager instance. Application can get the
+ * instance by calling #pjmedia_endpt_get_codec_mgr().
+ * @param factory The codec factory to be registered.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t)
+pjmedia_vid_codec_mgr_register_factory( pjmedia_vid_codec_mgr *mgr,
+ pjmedia_vid_codec_factory *factory);
+
+/**
+ * Unregister codec factory from the codec manager. This will also
+ * remove all the codecs registered by the codec factory from the
+ * codec manager's list of supported codecs.
+ *
+ * @param mgr The codec manager instance. Application can get the
+ * instance by calling #pjmedia_endpt_get_codec_mgr().
+ * @param factory The codec factory to be unregistered.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t)
+pjmedia_vid_codec_mgr_unregister_factory( pjmedia_vid_codec_mgr *mgr,
+ pjmedia_vid_codec_factory *factory);
+
+/**
+ * Enumerate all supported codecs that have been registered to the
+ * codec manager by codec factories.
+ *
+ * @param mgr The codec manager instance. Application can get the
+ * instance by calling #pjmedia_endpt_get_codec_mgr().
+ * @param count On input, specifies the number of elements in
+ * the array. On output, the value will be set to
+ * the number of elements that have been initialized
+ * by this function.
+ * @param info The codec info array, which contents will be
+ * initialized upon return.
+ * @param prio Optional pointer to receive array of codec priorities.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_codec_mgr_enum_codecs(
+ pjmedia_vid_codec_mgr *mgr,
+ unsigned *count,
+ pjmedia_vid_codec_info info[],
+ unsigned *prio);
+
+/**
+ * Get codec info for the specified static payload type.
+ *
+ * @param mgr The codec manager instance. Application can get the
+ * instance by calling #pjmedia_endpt_get_codec_mgr().
+ * @param pt Static payload type/number.
+ * @param inf Pointer to receive codec info.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t)
+pjmedia_vid_codec_mgr_get_codec_info( pjmedia_vid_codec_mgr *mgr,
+ unsigned pt,
+ const pjmedia_vid_codec_info **inf);
+
+/**
+ * Convert codec info struct into a unique codec identifier.
+ * A codec identifier looks something like "L16/44100/2".
+ *
+ * @param info The codec info
+ * @param id Buffer to put the codec info string.
+ * @param max_len The length of the buffer.
+ *
+ * @return The null terminated codec info string, or NULL if
+ * the buffer is not long enough.
+ */
+PJ_DECL(char*) pjmedia_vid_codec_info_to_id(
+ const pjmedia_vid_codec_info *info,
+ char *id, unsigned max_len );
+
+
+/**
+ * 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.
+ *
+ * @param mgr The codec manager instance. Application can get the
+ * instance by calling #pjmedia_endpt_get_codec_mgr().
+ * @param codec_id The full codec ID or codec ID prefix. If an empty
+ * string is given, it will match all codecs.
+ * @param count Maximum number of codecs to find. On return, it
+ * contains the actual number of codecs found.
+ * @param p_info Array of pointer to codec info to be filled. This
+ * argument may be NULL, which in this case, only
+ * codec count will be returned.
+ * @param prio Optional array of codec priorities.
+ *
+ * @return PJ_SUCCESS if at least one codec info is found.
+ */
+PJ_DECL(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[]);
+
+
+/**
+ * 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.
+ *
+ * @param mgr The codec manager instance. Application can get the
+ * instance by calling #pjmedia_endpt_get_codec_mgr().
+ * @param codec_id The full codec ID or codec ID prefix. If an empty
+ * string is given, it will match all codecs.
+ * @param prio Priority to be set. The priority can have any value
+ * between 1 to 255. When the priority is set to zero,
+ * the codec will be disabled.
+ *
+ * @return PJ_SUCCESS if at least one codec info is found.
+ */
+PJ_DECL(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);
+
+
+/**
+ * Get default codec param for the specified codec info.
+ *
+ * @param mgr The codec manager instance. Application can get the
+ * instance by calling #pjmedia_endpt_get_codec_mgr().
+ * @param info The codec info, which default parameter's is being
+ * queried.
+ * @param param On return, will be filled with the default codec
+ * parameter.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(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 );
+
+
+/**
+ * Set default codec param for the specified codec info.
+ *
+ * @param mgr The codec manager instance. Application can get the
+ * instance by calling #pjmedia_endpt_get_codec_mgr().
+ * @param info The codec info, which default parameter's is being
+ * updated.
+ * @param param The new default codec parameter. Set to NULL to reset
+ * codec parameter to library default settings.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t)
+pjmedia_vid_codec_mgr_set_default_param(pjmedia_vid_codec_mgr *mgr,
+ pj_pool_t *pool,
+ const pjmedia_vid_codec_info *info,
+ const pjmedia_vid_codec_param *param);
+
+
+/**
+ * Request the codec manager to allocate one instance of codec with the
+ * specified codec info. The codec will enumerate all codec factories
+ * until it finds factory that is able to create the specified codec.
+ *
+ * @param mgr The codec manager instance. Application can get the
+ * instance by calling #pjmedia_endpt_get_codec_mgr().
+ * @param info The information about the codec to be created.
+ * @param p_codec Pointer to receive the codec instance.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(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);
+
+/**
+ * Deallocate the specified codec instance. The codec manager will return
+ * the instance of the codec back to its factory.
+ *
+ * @param mgr The codec manager instance. Application can get the
+ * instance by calling #pjmedia_endpt_get_codec_mgr().
+ * @param codec The codec instance.
+ *
+ * @return PJ_SUCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_codec_mgr_dealloc_codec(
+ pjmedia_vid_codec_mgr *mgr,
+ pjmedia_vid_codec *codec);
+
+
+
+
+
+/**
+ * @}
+ */
+
+/**
+ * @defgroup PJMEDIA_CODEC_CODECS Supported codecs
+ * @ingroup PJMEDIA_CODEC
+ * @brief Documentation about individual codec supported by PJMEDIA
+ * @{
+ * Please see the APIs provided by the individual codecs below.
+ */
+/**
+ * @}
+ */
+
+
+
+
+PJ_END_DECL
+
+
+#endif /* __PJMEDIA_VID_CODEC_H__ */
diff --git a/pjmedia/include/pjmedia/videoport.h b/pjmedia/include/pjmedia/videoport.h
new file mode 100644
index 0000000..0cd0b3e
--- /dev/null
+++ b/pjmedia/include/pjmedia/videoport.h
@@ -0,0 +1,206 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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
+ */
+#ifndef __PJMEDIA_VIDPORT_H__
+#define __PJMEDIA_VIDPORT_H__
+
+/**
+ * @file pjmedia/videoport.h Video media port
+ * @brief Video media port
+ */
+
+#include <pjmedia-videodev/videodev.h>
+#include <pjmedia/port.h>
+
+/**
+ * @defgroup PJMEDIA_VIDEO_PORT Video media port
+ * @ingroup PJMEDIA_PORT_CLOCK
+ * @brief Video media port
+ * @{
+ */
+
+PJ_BEGIN_DECL
+
+/**
+ * This structure describes the parameters to create a video port
+ */
+typedef struct pjmedia_vid_port_param
+{
+ /**
+ * Video stream parameter.
+ */
+ pjmedia_vid_param vidparam;
+
+ /**
+ * Specify whether the video port should use active or passive interface.
+ * If active interface is selected, the video port will perform as
+ * a media clock, automatically calls pjmedia_port_get_frame() and
+ * pjmedia_port_put_frame() of its slave port (depending on the direction
+ * that is specified when opening the video stream). If passive interface
+ * is selected, application can retrieve the media port of this video
+ * port by calling pjmedia_vid_port_get_passive_port(), and subsequently
+ * calls pjmedia_port_put_frame() or pjmedia_port_get_frame() to that
+ * media port.
+ */
+ pj_bool_t active;
+
+} pjmedia_vid_port_param;
+
+/**
+ * Opaque data type for video port.
+ */
+typedef struct pjmedia_vid_port pjmedia_vid_port;
+
+/**
+ * Initialize the parameter with the default values. Note that this typically
+ * would only fill the structure to zeroes.
+ *
+ * @param prm The parameter.
+ */
+PJ_DECL(void) pjmedia_vid_port_param_default(pjmedia_vid_port_param *prm);
+
+/**
+ * Create a video port with the specified parameter.
+ *
+ * @param pool Pool to allocate memory from.
+ * @param prm The video port parameter.
+ * @param p_vp Pointer to receive the result.
+ *
+ * @return PJ_SUCCESS if video port has been created
+ * successfully, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_port_create(pj_pool_t *pool,
+ const pjmedia_vid_port_param *prm,
+ pjmedia_vid_port **p_vp);
+
+/**
+ * Set the callbacks of the video port's underlying video stream.
+ *
+ * @param vid_port The video port.
+ * @param cb Pointer to structure containing video stream
+ * callbacks.
+ * @param user_data Arbitrary user data, which will be given back in the
+ * callbacks.
+ */
+PJ_DECL(void) pjmedia_vid_port_set_cb(pjmedia_vid_port *vid_port,
+ const pjmedia_vid_cb *cb,
+ void *user_data);
+
+/**
+ * Return the underlying video stream of the video port.
+ *
+ * @param vid_port The video port.
+ *
+ * @return The video stream.
+ */
+PJ_DECL(pjmedia_vid_stream*)
+pjmedia_vid_port_get_stream(pjmedia_vid_port *vid_port);
+
+/**
+ * Return the (passive) media port of the video port. This operation
+ * is only valid for video ports created with passive interface selected.
+ * Retrieving the media port for active video ports may raise an
+ * assertion.
+ *
+ * @param vid_port The video port.
+ *
+ * @return The media port instance, or NULL.
+ */
+PJ_DECL(pjmedia_port*)
+pjmedia_vid_port_get_passive_port(pjmedia_vid_port *vid_port);
+
+/**
+ * Connect the video port to a downstream (slave) media port. This operation
+ * is only valid for video ports created with active interface selected.
+ * Connecting a passive video port may raise an assertion.
+ *
+ * @param vid_port The video port.
+ * @param port A downstream media port to be connected to
+ * this video port.
+ * @param destroy Specify if the downstream media port should also be
+ * destroyed by this video port when the video port
+ * is destroyed.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_port_connect(pjmedia_vid_port *vid_port,
+ pjmedia_port *port,
+ pj_bool_t destroy);
+
+/**
+ * Connect the video port from its downstream (slave) media port, if any.
+ * This operation is only valid for video ports created with active interface
+ * selected, and assertion may be triggered if this is invoked on a passive
+ * video port.
+ *
+ * @param vid_port The video port.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_port_disconnect(pjmedia_vid_port *vid_port);
+
+/**
+ * Retrieve the media port currently connected as downstream media port of the
+ * specified video port. This operation is only valid for video ports created
+ * with active interface selected, and assertion may be triggered if this is
+ * invoked on a passive video port.
+ *
+ * @param vid_port The video port.
+ *
+ * @return Media port currently connected to the video port,
+ * if any.
+ */
+PJ_DECL(pjmedia_port*)
+pjmedia_vid_port_get_connected_port(pjmedia_vid_port *vid_port);
+
+/**
+ * Start the video port.
+ *
+ * @param vid_port The video port.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_port_start(pjmedia_vid_port *vid_port);
+
+/**
+ * Stop the video port.
+ *
+ * @param vid_port The video port.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_port_stop(pjmedia_vid_port *vid_port);
+
+/**
+ * Destroy the video port, along with its video stream. If the video port is
+ * an active one, this may also destroy the downstream media port, if the
+ * destroy flag is set when the media port is connected.
+ *
+ * @param vid_port The video port.
+ */
+PJ_DECL(void) pjmedia_vid_port_destroy(pjmedia_vid_port *vid_port);
+
+
+PJ_END_DECL
+
+/**
+ * @}
+ */
+
+#endif /* __PJMEDIA_VIDPORT_H__ */
+
diff --git a/pjmedia/include/pjmedia_videodev.h b/pjmedia/include/pjmedia_videodev.h
new file mode 100644
index 0000000..cf22e61
--- /dev/null
+++ b/pjmedia/include/pjmedia_videodev.h
@@ -0,0 +1,30 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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
+ */
+#ifndef __PJMEDIA_VIDEODEV_H__
+#define __PJMEDIA_VIDEODEV_H__
+
+/**
+ * @file pjmedia_videodev.h
+ * @brief PJMEDIA main header file.
+ */
+
+#include <pjmedia-videodev/videodev.h>
+#include <pjmedia-videodev/videodev_imp.h>
+
+#endif /* __PJMEDIA_VIDEODEV_H__ */
diff --git a/pjmedia/src/pjmedia-audiodev/audiodev.c b/pjmedia/src/pjmedia-audiodev/audiodev.c
index 2a5a8e6..fa381c9 100644
--- a/pjmedia/src/pjmedia-audiodev/audiodev.c
+++ b/pjmedia/src/pjmedia-audiodev/audiodev.c
@@ -190,9 +190,11 @@
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;
diff --git a/pjmedia/src/pjmedia-audiodev/wmme_dev.c b/pjmedia/src/pjmedia-audiodev/wmme_dev.c
index 4a577c7..c69c5c8 100644
--- a/pjmedia/src/pjmedia-audiodev/wmme_dev.c
+++ b/pjmedia/src/pjmedia-audiodev/wmme_dev.c
@@ -250,12 +250,12 @@
/* Extended formats */
wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
wdi->info.ext_fmt_cnt = 2;
- wdi->info.ext_fmt[0].id = PJMEDIA_FORMAT_PCMU;
- wdi->info.ext_fmt[0].bitrate = 64000;
- wdi->info.ext_fmt[0].vad = 0;
- wdi->info.ext_fmt[1].id = PJMEDIA_FORMAT_PCMA;
- wdi->info.ext_fmt[1].bitrate = 64000;
- wdi->info.ext_fmt[1].vad = 0;
+ 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 */
diff --git a/pjmedia/src/pjmedia-codec/ffmpeg_codecs.c b/pjmedia/src/pjmedia-codec/ffmpeg_codecs.c
new file mode 100644
index 0000000..35128e5
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/ffmpeg_codecs.c
@@ -0,0 +1,1054 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2010 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_codecs.h>
+#include <pjmedia-codec/h263_packetizer.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 <pj/string.h>
+#include <pj/os.h>
+
+/*
+ * Only build this file if PJMEDIA_HAS_FFMPEG_CODEC != 0
+ */
+#if defined(PJMEDIA_HAS_FFMPEG_CODEC) && PJMEDIA_HAS_FFMPEG_CODEC != 0
+
+#define THIS_FILE "ffmpeg_codecs.c"
+
+#include "../pjmedia/ffmpeg_util.h"
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+
+
+#define PJMEDIA_FORMAT_FFMPEG_UNKNOWN PJMEDIA_FORMAT_PACK('f','f','0','0');
+
+
+/* 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_packetize ( pjmedia_vid_codec *codec,
+ pj_uint8_t *buf,
+ pj_size_t buf_len,
+ unsigned *pos,
+ const pj_uint8_t **payload,
+ pj_size_t *payload_len);
+static pj_status_t ffmpeg_unpacketize(pjmedia_vid_codec *codec,
+ const pj_uint8_t *payload,
+ pj_size_t payload_len,
+ pj_uint8_t *buf,
+ pj_size_t *buf_len);
+static pj_status_t ffmpeg_codec_encode( pjmedia_vid_codec *codec,
+ const pjmedia_frame *input,
+ unsigned output_buf_len,
+ pjmedia_frame *output);
+static pj_status_t ffmpeg_codec_decode( pjmedia_vid_codec *codec,
+ const pjmedia_frame *input,
+ unsigned output_buf_len,
+ pjmedia_frame *output);
+static pj_status_t ffmpeg_codec_recover( pjmedia_vid_codec *codec,
+ unsigned output_buf_len,
+ 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_packetize,
+ &ffmpeg_unpacketize,
+ &ffmpeg_codec_encode,
+ &ffmpeg_codec_decode,
+ NULL //&ffmpeg_codec_recover
+};
+
+/* 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
+};
+
+
+typedef struct ffmpeg_codec_info {
+ PJ_DECL_LIST_MEMBER(struct ffmpeg_codec_info);
+ pjmedia_vid_codec_info info;
+ AVCodec *enc;
+ AVCodec *dec;
+} ffmpeg_codec_info;
+
+
+/* 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_codec_info codecs;
+} ffmpeg_factory;
+
+
+/* FFMPEG codecs private data. */
+typedef struct ffmpeg_private {
+ AVCodec *enc;
+ AVCodec *dec;
+ AVCodecContext *enc_ctx;
+ AVCodecContext *dec_ctx;
+ AVCodecParserContext *dec_parser_ctx;
+
+ /*
+ pjmedia_frame *pack_frms;
+ unsigned pack_frm_cnt;
+ unsigned pack_frm_max_cnt;
+ */
+
+ pjmedia_vid_codec_param param; /**< Codec param. */
+ pj_pool_t *pool; /**< Pool for each instance. */
+ pj_timestamp last_tx; /**< Timestamp of last transmit.*/
+ const pjmedia_video_format_info *vfi;
+ pjmedia_video_apply_fmt_param vafp;
+} ffmpeg_private;
+
+
+/*
+ * Initialize and register FFMPEG codec factory to pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_ffmpeg_init(pjmedia_vid_codec_mgr *mgr,
+ pj_pool_factory *pf)
+{
+ pj_pool_t *pool;
+ AVCodec *c;
+ enum CodecID last_codec_id = CODEC_ID_NONE;
+ pj_status_t status;
+
+ 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;
+ pj_list_init(&ffmpeg_factory.codecs);
+
+ 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;
+
+ avcodec_init();
+ avcodec_register_all();
+
+ /* Enum FFMPEG codecs */
+ for (c=av_codec_next(NULL); c; c=av_codec_next(c))
+ {
+ ffmpeg_codec_info *ci;
+
+ if (c->type != CODEC_TYPE_VIDEO)
+ continue;
+
+ /* Video encoder and decoder are usually implemented in separate
+ * AVCodec instances.
+ */
+
+ if (c->id == last_codec_id) {
+ /* This codec usually be the decoder, and not as much info as in
+ * encoder can be fetched here.
+ */
+ pj_assert(!pj_list_empty(&ffmpeg_factory.codecs));
+ ci = ffmpeg_factory.codecs.prev;
+ pj_assert(ci->info.dir != PJMEDIA_DIR_ENCODING_DECODING);
+ pj_assert(!ci->dec || !ci->enc);
+ } else {
+ pjmedia_format_id enc_fmt_id;
+ pjmedia_format_id raw_fmt[PJMEDIA_VID_CODEC_MAX_DEC_FMT_CNT];
+ unsigned raw_fmt_cnt = 0;
+ unsigned raw_fmt_cnt_should_be = 0;
+
+ /* Get encoded format id */
+ status = CodecID_to_pjmedia_format_id(c->id, &enc_fmt_id);
+ if (status != PJ_SUCCESS) {
+ //PJ_LOG(5, (THIS_FILE, "Unrecognized ffmpeg codec id %d, "
+ // "codec [%s/%s] ignored",
+ // c->id, c->name, c->long_name));
+ //enc_fmt_id = PJMEDIA_FORMAT_FFMPEG_UNKNOWN;
+
+ /* Skip unrecognized encoding format ID */
+ continue;
+ }
+
+ /* Get raw/decoded format ids */
+ if (c->pix_fmts) {
+ 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;
+ }
+ } else {
+ /* Unknown raw format, ignore this codec? */
+ 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));
+ }
+ 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));
+ /* Comment this to see all ffmpeg codecs */
+ continue;
+ }
+
+ ci = PJ_POOL_ZALLOC_T(pool, ffmpeg_codec_info);
+ ci->info.fmt_id = enc_fmt_id;
+ pj_cstr(&ci->info.encoding_name, c->name);
+ ci->info.clock_rate = 90000;
+ ci->info.dec_fmt_id_cnt = raw_fmt_cnt;
+ pj_memcpy(ci->info.dec_fmt_id, raw_fmt,
+ sizeof(raw_fmt[0])*raw_fmt_cnt);
+
+ switch (enc_fmt_id) {
+ case PJMEDIA_FORMAT_H263:
+ ci->info.pt = PJMEDIA_RTP_PT_H263;
+ break;
+ case PJMEDIA_FORMAT_H261:
+ ci->info.pt = PJMEDIA_RTP_PT_H261;
+ break;
+ default:
+ break;
+ }
+
+ if (c->supported_framerates) {
+ const AVRational *fr = c->supported_framerates;
+ while ((fr->num != 0 || fr->den != 0) &&
+ ci->info.fps_cnt < PJMEDIA_VID_CODEC_MAX_FPS_CNT)
+ {
+ ci->info.fps[ci->info.fps_cnt].num = fr->num;
+ ci->info.fps[ci->info.fps_cnt].denum = fr->den;
+ ++ci->info.fps_cnt;
+ ++fr;
+ }
+ }
+
+ pj_list_push_back(&ffmpeg_factory.codecs, ci);
+ }
+
+ if (c->encode) {
+ ci->info.dir |= PJMEDIA_DIR_ENCODING;
+ ci->enc = c;
+ }
+ if (c->decode) {
+ ci->info.dir |= PJMEDIA_DIR_DECODING;
+ ci->dec = c;
+ }
+
+ last_codec_id = c->id;
+ }
+
+ /* 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_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;
+
+ return status;
+}
+
+
+static ffmpeg_codec_info* find_codec(const pjmedia_vid_codec_info *info)
+{
+ ffmpeg_codec_info *ci = ffmpeg_factory.codecs.next;
+
+ pj_mutex_lock(ffmpeg_factory.mutex);
+
+ while (ci != &ffmpeg_factory.codecs) {
+ if ((ci->info.fmt_id == info->fmt_id) &&
+ ((ci->info.dir & info->dir) == info->dir) &&
+ pj_stricmp(&ci->info.encoding_name, &info->encoding_name)==0)
+ {
+ pj_mutex_unlock(ffmpeg_factory.mutex);
+ return ci;
+ }
+ ci = ci->next;
+ }
+
+ pj_mutex_unlock(ffmpeg_factory.mutex);
+
+ return NULL;
+}
+
+
+/*
+ * 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 )
+{
+ ffmpeg_codec_info *ci;
+
+ PJ_ASSERT_RETURN(factory==&ffmpeg_factory.base, PJ_EINVAL);
+ PJ_ASSERT_RETURN(info, PJ_EINVAL);
+
+ ci = find_codec(info);
+ if (!ci) {
+ 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 )
+{
+ ffmpeg_codec_info *ci;
+
+ PJ_ASSERT_RETURN(factory==&ffmpeg_factory.base, PJ_EINVAL);
+ PJ_ASSERT_RETURN(info && attr, PJ_EINVAL);
+
+ ci = find_codec(info);
+ if (!ci) {
+ return PJMEDIA_CODEC_EUNSUP;
+ }
+
+ pj_bzero(attr, sizeof(pjmedia_vid_codec_param));
+ attr->dir = ci->info.dir;
+ attr->pt = info->pt;
+ pjmedia_format_init_video(&attr->enc_fmt, ci->info.fmt_id,
+ 352, 288, 25, 1);
+ pjmedia_format_init_video(&attr->dec_fmt, ci->info.dec_fmt_id[0],
+ 352, 288, 25, 1);
+
+ 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[])
+{
+ ffmpeg_codec_info *ci;
+ unsigned max;
+
+ PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL);
+ PJ_ASSERT_RETURN(factory == &ffmpeg_factory.base, PJ_EINVAL);
+
+ max = *count;
+ *count = 0;
+ ci = ffmpeg_factory.codecs.next;
+
+ pj_mutex_lock(ffmpeg_factory.mutex);
+ while (*count < max && ci != &ffmpeg_factory.codecs) {
+ pj_memcpy(&codecs[*count], &ci->info, sizeof(pjmedia_vid_codec_info));
+ *count = *count + 1;
+ ci = ci->next;
+ }
+ pj_mutex_unlock(ffmpeg_factory.mutex);
+
+ 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;
+ ffmpeg_codec_info *ci;
+ 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);
+
+ ci = find_codec(info);
+ if (!ci) {
+ 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 = ci->enc;
+ ff->dec = ci->dec;
+
+ *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_VERSION_MAJOR >= 52 && LIBAVCODEC_VERSION_MINOR >= 72
+ char errbuf[512];
+ if (av_strerror(err, errbuf, sizeof(errbuf)) >= 0)
+ PJ_LOG(1, (THIS_FILE, "ffmpeg err %d: %s", err, errbuf));
+#else
+ PJ_LOG(1, (THIS_FILE, "ffmpeg err %d", err));
+#endif
+
+}
+
+/*
+static enum PixelFormat ffdec_nego_format(struct AVCodecContext *s,
+ const enum PixelFormat * fmt)
+{
+ enum PixelFormat pix_fmt;
+
+ PJ_UNUSED_ARG(s);
+ PJ_UNUSED_ARG(fmt);
+
+ pjmedia_format_id_to_PixelFormat(PJMEDIA_FORMAT_BGRA, &pix_fmt);
+ return pix_fmt;
+}
+
+static void enc_got_payload(struct AVCodecContext *avctx,
+ void *data, int size, int mb_nb);
+*/
+
+
+static pj_status_t open_ffmpeg_codec(ffmpeg_private *ff,
+ pj_mutex_t *ff_mutex)
+{
+ enum PixelFormat pix_fmt;
+ pj_status_t status;
+
+ status = pjmedia_format_id_to_PixelFormat(ff->param.dec_fmt.id,
+ &pix_fmt);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ while (((ff->param.dir & PJMEDIA_DIR_ENCODING) && ff->enc_ctx == NULL) ||
+ ((ff->param.dir & PJMEDIA_DIR_DECODING) && ff->dec_ctx == NULL))
+ {
+ pjmedia_dir dir;
+ AVCodecContext *ctx = NULL;
+ AVCodec *codec = NULL;
+ int err;
+
+ /* Set which direction to open */
+ if (ff->param.dir==PJMEDIA_DIR_ENCODING_DECODING && ff->enc!=ff->dec) {
+ dir = ff->enc_ctx? PJMEDIA_DIR_DECODING : PJMEDIA_DIR_ENCODING;
+ } else {
+ dir = ff->param.dir;
+ }
+
+ ctx = avcodec_alloc_context();
+
+ /* Common attributes */
+ ctx->pix_fmt = pix_fmt;
+ ctx->width = ff->param.enc_fmt.det.vid.size.w;
+ ctx->height = ff->param.enc_fmt.det.vid.size.h;
+ ctx->workaround_bugs = FF_BUG_AUTODETECT;
+ ctx->opaque = ff;
+
+ if (dir & PJMEDIA_DIR_ENCODING) {
+ codec = ff->enc;
+
+ /* Encoding only attributes */
+ ctx->time_base.num = ff->param.enc_fmt.det.vid.fps.denum;
+ ctx->time_base.den = ff->param.enc_fmt.det.vid.fps.num;
+ if (ff->param.enc_fmt.det.vid.avg_bps)
+ ctx->bit_rate = ff->param.enc_fmt.det.vid.avg_bps;
+ if (ff->param.enc_fmt.det.vid.max_bps)
+ ctx->rc_max_rate = ff->param.enc_fmt.det.vid.max_bps;
+#if 0
+ if (ff->param.enc_mtu) {
+ //ctx->rtp_payload_size = ff->param.enc_mtu;
+ //ctx->rtp_callback = &enc_got_payload;
+
+ /* Allocate frame array for RTP payload packing */
+ if (ff->param.enc_fmt.det.vid.max_bps)
+ ff->pack_frm_max_cnt = ff->param.enc_fmt.det.vid.max_bps /
+ ff->param.enc_mtu + 1;
+ else
+ ff->pack_frm_max_cnt = 32;
+
+ ff->pack_frms = (pjmedia_frame*)
+ pj_pool_calloc(ff->pool, ff->pack_frm_max_cnt,
+ sizeof(ff->pack_frms[0]));
+ }
+#endif
+
+ /* For encoder, should be better to be strict to the standards */
+ ctx->strict_std_compliance = FF_COMPLIANCE_STRICT;
+ }
+ if (dir & PJMEDIA_DIR_DECODING) {
+ codec = ff->dec;
+
+ /* Decoding only attributes */
+ ctx->coded_width = ctx->width;
+ ctx->coded_height = ctx->height;
+
+ /* For decoder, be more flexible */
+ if (ff->param.dir!=PJMEDIA_DIR_ENCODING_DECODING || ff->enc!=ff->dec)
+ ctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
+
+ //ctx->get_format = &ffdec_nego_format;
+ }
+
+ /* avcodec_open() should be protected */
+ pj_mutex_lock(ff_mutex);
+ err = avcodec_open(ctx, codec);
+ pj_mutex_unlock(ff_mutex);
+ if (err < 0) {
+ print_ffmpeg_err(err);
+ return PJ_EUNKNOWN;
+ }
+
+ if (dir & PJMEDIA_DIR_ENCODING)
+ ff->enc_ctx = ctx;
+ if (dir & PJMEDIA_DIR_DECODING)
+ ff->dec_ctx = ctx;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * 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;
+
+ ff->vfi = pjmedia_get_video_format_info(NULL, attr->dec_fmt.id);
+ if (!ff->vfi) {
+ status = PJ_EINVAL;
+ goto on_error;
+ }
+
+ pj_bzero(&ff->vafp, sizeof(ff->vafp));
+ ff->vafp.size = attr->dec_fmt.det.vid.size;
+ ff->vafp.buffer = 0;
+ status = (*ff->vfi->apply_fmt)(ff->vfi, &ff->vafp);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ pj_memcpy(&ff->param, attr, sizeof(*attr));
+
+ ff_mutex = ((struct ffmpeg_factory*)codec->factory)->mutex;
+ status = open_ffmpeg_codec(ff, ff_mutex);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ 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);
+ }
+ if (ff->dec_parser_ctx) {
+ av_parser_close(ff->dec_parser_ctx);
+
+ }
+ ff->enc_ctx = NULL;
+ ff->dec_ctx = NULL;
+ ff->dec_parser_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_packetize ( pjmedia_vid_codec *codec,
+ pj_uint8_t *buf,
+ pj_size_t buf_len,
+ unsigned *pos,
+ const pj_uint8_t **payload,
+ pj_size_t *payload_len)
+{
+ ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+
+ switch (ff->param.enc_fmt.id) {
+ case PJMEDIA_FORMAT_H263:
+ return pjmedia_h263_packetize(buf, buf_len, pos,
+ ff->param.enc_mtu, payload,
+ payload_len);
+ break;
+ default:
+ 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 *buf,
+ pj_size_t *buf_len)
+{
+ ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+
+ switch (ff->param.enc_fmt.id) {
+ case PJMEDIA_FORMAT_H263:
+ return pjmedia_h263_unpacketize(payload, payload_len,
+ buf, buf_len);
+ break;
+ default:
+ return PJ_ENOTSUP;
+ }
+}
+
+#if 0
+/*
+ * Pack encoded frame to RTP payload frames.
+ */
+static pj_status_t ffmpeg_codec_pack ( pjmedia_vid_codec *codec,
+ const pjmedia_frame *enc_frame,
+ unsigned *frame_cnt,
+ pjmedia_frame frames[])
+{
+ ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+ unsigned i;
+
+ /* Can only work when encoding MTU is set */
+ PJ_ASSERT_RETURN(ff->param.enc_mtu, PJ_EINVALIDOP);
+
+ /* Validate available payload number */
+ PJ_ASSERT_RETURN(ff->pack_frm_cnt <= *frame_cnt, PJ_EINVALIDOP);
+
+ /* Validate encoded bitstream */
+ PJ_ASSERT_RETURN(ff->pack_frm_cnt==0 ||
+ ff->pack_frms[0].buf == enc_frame->buf,
+ PJ_EINVAL);
+
+ /* Return the payloads */
+ *frame_cnt = ff->pack_frm_cnt;
+ for (i = 0; i < *frame_cnt; ++i)
+ frames[i] = ff->pack_frms[i];
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get frames in the packet.
+ */
+static pj_status_t ffmpeg_codec_parse( pjmedia_vid_codec *codec,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_timestamp *ts,
+ pjmedia_frame *frame)
+{
+ ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+ pj_uint8_t *buf = frame->buf;
+ int buf_size = frame->size;
+ int processed;
+
+
+ if (!ff->dec_parser_ctx) {
+ ff->dec_parser_ctx = av_parser_init(ff->dec->id);
+ if (!ff->dec_parser_ctx)
+ return PJ_ENOTSUP;
+ }
+
+#if LIBAVCODEC_VERSION_MAJOR >= 52 && LIBAVCODEC_VERSION_MINOR >= 72
+ processed = av_parser_parse2(ff->dec_parser_ctx, ff->dec_ctx,
+ &buf, &buf_size, (uint8_t*)pkt, pkt_size,
+ ts->u64, ts->u64, AV_NOPTS_VALUE);
+#else
+ processed = av_parser_parse (ff->dec_parser_ctx, ff->dec_ctx,
+ &buf, &buf_size, (uint8_t*)pkt, pkt_size,
+ ts->u64, ts->u64);
+#endif
+
+ if (buf_size) {
+ frame->timestamp = *ts;
+ frame->size = buf_size;
+ return PJ_SUCCESS;
+ }
+
+ return PJ_EPENDING;
+}
+
+static void enc_got_payload(struct AVCodecContext *avctx,
+ void *data, int size, int mb_nb)
+{
+ ffmpeg_private *ff = (ffmpeg_private*) avctx->opaque;
+ pjmedia_frame *payload;
+
+ pj_assert(ff->pack_frm_cnt < ff->pack_frm_max_cnt);
+ payload = &ff->pack_frms[ff->pack_frm_cnt++];
+ payload->buf = data;
+ payload->size = size;
+ payload->bit_info = mb_nb;
+}
+
+#endif
+
+
+/*
+ * Encode frames.
+ */
+static pj_status_t ffmpeg_codec_encode( pjmedia_vid_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+ pj_uint8_t *p = (pj_uint8_t*)input->buf;
+ AVFrame avframe;
+ pj_uint8_t *out_buf = (pj_uint8_t*)output->buf;
+ int out_buf_len = output_buf_len;
+ int err;
+ unsigned i;
+
+ /* Check if encoder has been opened */
+ PJ_ASSERT_RETURN(ff->enc_ctx, PJ_EINVALIDOP);
+
+ /*
+ ff->pack_frm_cnt = 0;
+ */
+ avcodec_get_frame_defaults(&avframe);
+
+ for (i = 0; i < ff->vfi->plane_cnt; ++i) {
+ avframe.data[i] = p;
+ avframe.linesize[i] = ff->vafp.strides[i];
+ p += ff->vafp.plane_bytes[i];
+ }
+
+#ifdef _MSC_VER
+ /* Align stack for MSVC environment to avoid 'random' crash, as advised in
+ * http://ffmpeg.arrozcru.org/forum/viewtopic.php?f=1&t=549
+ */
+# define VHALIGNCALL16(x) \
+ {\
+ _asm { mov ebx, esp }\
+ _asm { and esp, 0xfffffff0 }\
+ _asm { sub esp, 12 }\
+ _asm { push ebx }\
+ x;\
+ _asm { pop ebx }\
+ _asm { mov esp, ebx }\
+ }
+#else
+# define VHALIGNCALL16(x)
+#endif
+
+ VHALIGNCALL16(err = avcodec_encode_video(ff->enc_ctx, out_buf,
+ out_buf_len, &avframe));
+
+ if (err < 0) {
+ print_ffmpeg_err(err);
+ return PJ_EUNKNOWN;
+ } else {
+ output->size = err;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Decode frame.
+ */
+static pj_status_t ffmpeg_codec_decode( pjmedia_vid_codec *codec,
+ const struct pjmedia_frame *input,
+ unsigned output_buf_len,
+ struct 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);
+
+ PJ_UNUSED_ARG(output_buf_len);
+
+ /* 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);
+
+#if LIBAVCODEC_VERSION_MAJOR >= 52 && LIBAVCODEC_VERSION_MINOR >= 72
+ avpacket.flags = AV_PKT_FLAG_KEY;
+#else
+ avpacket.flags = 0;
+#endif
+
+#if LIBAVCODEC_VERSION_MAJOR >= 52 && LIBAVCODEC_VERSION_MINOR >= 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) {
+ print_ffmpeg_err(err);
+ return PJ_EUNKNOWN;
+ } else if (got_picture) {
+ pjmedia_video_apply_fmt_param *vafp = (pjmedia_video_apply_fmt_param*)
+ &ff->vafp;
+ pj_uint8_t *q = (pj_uint8_t*)output->buf;
+ unsigned i;
+
+ /* Get the decoded data */
+ for (i = 0; i < ff->vfi->plane_cnt; ++i) {
+ pj_uint8_t *p = avframe.data[i];
+
+ /* The decoded data may contain padding */
+ if (avframe.linesize[i]==vafp->strides[i]) {
+ /* No padding, copy the whole plane */
+ pj_memcpy(q, p, vafp->plane_bytes[i]);
+ q += vafp->plane_bytes[i];
+ } else {
+ /* 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];
+ }
+ }
+ }
+ output->size = vafp->framebytes;
+ } else {
+ return PJ_EUNKNOWN;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Recover lost frame.
+ */
+static pj_status_t ffmpeg_codec_recover( pjmedia_vid_codec *codec,
+ unsigned output_buf_len,
+ struct pjmedia_frame *output)
+{
+ ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+
+ PJ_UNUSED_ARG(output_buf_len);
+ PJ_UNUSED_ARG(output);
+ PJ_UNUSED_ARG(ff);
+
+ return PJ_SUCCESS;
+}
+
+#ifdef _MSC_VER
+# pragma comment( lib, "avcodec.lib")
+#endif
+
+#endif /* PJMEDIA_HAS_FFMPEG_CODEC */
+
diff --git a/pjmedia/src/pjmedia-videodev/colorbar_dev.c b/pjmedia/src/pjmedia-videodev/colorbar_dev.c
new file mode 100644
index 0000000..93b61a5
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/colorbar_dev.c
@@ -0,0 +1,604 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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 PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC
+
+#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_I420JPEG },
+ { PJMEDIA_FORMAT_I422JPEG },
+};
+
+/* Video stream. */
+struct cbar_stream
+{
+ pjmedia_vid_stream base; /**< Base stream */
+ pjmedia_vid_param param; /**< Settings */
+ pj_pool_t *pool; /**< Memory pool. */
+
+ pjmedia_vid_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];
+};
+
+
+/* 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 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_param *param);
+static pj_status_t cbar_factory_create_stream(pjmedia_vid_dev_factory *f,
+ const pjmedia_vid_param *param,
+ const pjmedia_vid_cb *cb,
+ void *user_data,
+ pjmedia_vid_stream **p_vid_strm);
+
+static pj_status_t cbar_stream_get_param(pjmedia_vid_stream *strm,
+ pjmedia_vid_param *param);
+static pj_status_t cbar_stream_get_cap(pjmedia_vid_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ void *value);
+static pj_status_t cbar_stream_set_cap(pjmedia_vid_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ const void *value);
+static pj_status_t cbar_stream_get_frame(pjmedia_vid_stream *strm,
+ pjmedia_frame *frame);
+static pj_status_t cbar_stream_start(pjmedia_vid_stream *strm);
+static pj_status_t cbar_stream_stop(pjmedia_vid_stream *strm);
+static pj_status_t cbar_stream_destroy(pjmedia_vid_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
+};
+
+static pjmedia_vid_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));
+ strncpy(ddi->info.name, "Colorbar generator",
+ sizeof(ddi->info.name));
+ ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0';
+ 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]);
+ ddi->info.fmt = (pjmedia_format*)
+ pj_pool_calloc(cf->pool, ddi->info.fmt_cnt,
+ sizeof(pjmedia_format));
+ 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 devices:",
+ cf->dev_count));
+
+ 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: 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_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;
+ param->frame_rate.num = DEFAULT_FPS;
+ param->frame_rate.denum = 1;
+ pj_memcpy(¶m->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
+ 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,
+ const pjmedia_vid_param *param,
+ const pjmedia_vid_cb *cb,
+ void *user_data,
+ pjmedia_vid_stream **p_vid_strm)
+{
+ struct cbar_factory *cf = (struct cbar_factory*)f;
+ pj_pool_t *pool;
+ struct cbar_stream *strm;
+ 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,
+ PJ_EINVAL);
+
+ pj_bzero(&vafp, sizeof(vafp));
+
+ 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));
+
+ 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,
+ ¶m->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_stream *s,
+ pjmedia_vid_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_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_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_stream *strm,
+ pjmedia_frame *frame)
+{
+ struct cbar_stream *stream = (struct cbar_stream*)strm;
+
+ return spectrum_run(stream, frame->buf, frame->size);
+}
+
+/* API: Start stream. */
+static pj_status_t cbar_stream_start(pjmedia_vid_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_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_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..f4b95ac
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/dshow_dev.c
@@ -0,0 +1,903 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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 PJMEDIA_VIDEO_DEV_HAS_DSHOW
+
+#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")
+
+#define THIS_FILE "dshow_dev.c"
+#define DEFAULT_WIDTH 640
+#define DEFAULT_HEIGHT 480
+#define DEFAULT_FPS 25
+
+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_SetBufferSize(SourceFilter *src, long size);
+
+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} ,
+};
+
+/* 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_factory *pf;
+
+ unsigned dev_count;
+ struct dshow_dev_info *dev_info;
+};
+
+/* Video stream. */
+struct dshow_stream
+{
+ pjmedia_vid_stream base; /**< Base stream */
+ pjmedia_vid_param param; /**< Settings */
+ pj_pool_t *pool; /**< Memory pool. */
+
+ pjmedia_vid_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;
+
+ struct dshow_graph
+ {
+ IFilterGraph *filter_graph;
+ IMediaFilter *media_filter;
+ SourceFilter *csource_filter;
+ IBaseFilter *source_filter;
+ IBaseFilter *rend_filter;
+ AM_MEDIA_TYPE *mediatype;
+ } dgraph[2];
+};
+
+
+/* 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 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_param *param);
+static pj_status_t dshow_factory_create_stream(pjmedia_vid_dev_factory *f,
+ const pjmedia_vid_param *param,
+ const pjmedia_vid_cb *cb,
+ void *user_data,
+ pjmedia_vid_stream **p_vid_strm);
+
+static pj_status_t dshow_stream_get_param(pjmedia_vid_stream *strm,
+ pjmedia_vid_param *param);
+static pj_status_t dshow_stream_get_cap(pjmedia_vid_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ void *value);
+static pj_status_t dshow_stream_set_cap(pjmedia_vid_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ const void *value);
+static pj_status_t dshow_stream_start(pjmedia_vid_stream *strm);
+static pj_status_t dshow_stream_put_frame(pjmedia_vid_stream *strm,
+ const pjmedia_frame *frame);
+static pj_status_t dshow_stream_stop(pjmedia_vid_stream *strm);
+static pj_status_t dshow_stream_destroy(pjmedia_vid_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
+};
+
+static pjmedia_vid_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)
+{
+ struct dshow_factory *df = (struct dshow_factory*)f;
+ struct dshow_dev_info *ddi;
+ int dev_count = 0;
+ unsigned c, i;
+ ICreateDevEnum *dev_enum = NULL;
+ IEnumMoniker *enum_cat = NULL;
+ IMoniker *moniker = NULL;
+ HRESULT hr;
+ ULONG fetched;
+
+ df->dev_count = 0;
+
+ CoInitializeEx(NULL, COINIT_MULTITHREADED);
+
+ 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->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;
+
+ 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;
+ // TODO: Query the default width, height, fps, and
+ // supported formats
+ }
+ VariantClear(&var_name);
+
+ IPropertyBag_Release(prop_bag);
+ }
+ IMoniker_Release(moniker);
+ }
+
+ IEnumMoniker_Release(enum_cat);
+ ICreateDevEnum_Release(dev_enum);
+ }
+
+ ddi = &df->dev_info[df->dev_count++];
+ pj_bzero(ddi, sizeof(*ddi));
+ strncpy(ddi->info.name, "Video Mixing Renderer",
+ sizeof(ddi->info.name));
+ ddi->info.name[sizeof(ddi->info.name)-1] = '\0';
+ 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;
+
+ for (c = 0; c < df->dev_count; c++) {
+ ddi = &df->dev_info[c];
+ ddi->info.fmt_cnt = sizeof(dshow_fmts)/sizeof(dshow_fmts[0]);
+ ddi->info.fmt = (pjmedia_format*)
+ pj_pool_calloc(df->pool, ddi->info.fmt_cnt,
+ sizeof(pjmedia_format));
+
+ for (i = 0; i < ddi->info.fmt_cnt; i++) {
+ pjmedia_format *fmt = &ddi->info.fmt[i];
+
+ if (ddi->info.dir == PJMEDIA_DIR_RENDER && i > 0)
+ break;
+ pjmedia_format_init_video(fmt, dshow_fmts[i].pjmedia_format,
+ DEFAULT_WIDTH, DEFAULT_HEIGHT,
+ DEFAULT_FPS, 1);
+ }
+ }
+
+// TODO:
+// ddi->info.caps = PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
+
+ PJ_LOG(4, (THIS_FILE, "DShow initialized, found %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: 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;
+ pj_pool_release(pool);
+
+ CoUninitialize();
+
+ 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_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_RENDER) {
+ param->dir = PJMEDIA_DIR_CAPTURE_RENDER;
+ param->cap_id = index;
+ param->rend_id = index;
+ } else 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 = 9000;
+ param->frame_rate.num = 14;
+ param->frame_rate.denum = 1;
+ param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
+
+ pjmedia_format_copy(¶m->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;
+ unsigned char *buffer;
+ pjmedia_frame frame;
+
+ 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"));
+ }
+
+ IMediaSample_GetPointer(pMediaSample, &buffer);
+
+ frame.type = PJMEDIA_TYPE_VIDEO;
+ IMediaSample_GetPointer(pMediaSample, (BYTE **)&frame.buf);
+ frame.size = IMediaSample_GetActualDataLength(pMediaSample);
+ frame.bit_info = 0;
+ 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_stream *strm,
+ const pjmedia_frame *frame)
+{
+ struct dshow_stream *stream = (struct dshow_stream*)strm;
+ unsigned i;
+
+ if (stream->quit_flag) {
+ stream->rend_thread_exited = PJ_TRUE;
+ return PJ_SUCCESS;
+ }
+
+ for (i = 0; i < 2; i++) {
+ if (stream->dgraph[i].csource_filter) {
+ HRESULT hr = SourceFilter_Deliver(stream->dgraph[i].csource_filter,
+ frame->buf, frame->size);
+
+ if (FAILED(hr)) {
+ return hr;
+ }
+ break;
+ }
+ }
+
+ 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,
+ 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) {
+ IBindCtx *pbc;
+
+ 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 *)&graph->source_filter);
+ IMoniker_Release(moniker);
+ }
+ IBindCtx_Release(pbc);
+ }
+ 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);
+
+ IBaseFilter_EnumPins(graph->source_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;
+ const GUID *dshow_format;
+
+ dshow_format = get_dshow_format_info(strm->param.fmt.id)->
+ dshow_format;
+
+ 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++) {
+ RPC_STATUS rpcstatus;
+
+ hr = IAMStreamConfig_GetStreamCaps(streamcaps, i,
+ &mediatype,
+ (BYTE *)&vscc);
+ if (FAILED (hr))
+ continue;
+
+ if (UuidCompare(&mediatype->subtype,
+ (UUID *)dshow_format,
+ &rpcstatus) == 0 &&
+ rpcstatus == RPC_S_OK)
+ {
+ srcpin = pPin;
+ graph->mediatype = mediatype;
+ break;
+ }
+ }
+ IAMStreamConfig_Release(streamcaps);
+ }
+ } else {
+ srcpin = pPin;
+ mediatype = graph->mediatype = &mtype;
+
+ memset (mediatype, 0, sizeof(AM_MEDIA_TYPE));
+ mediatype->majortype = MEDIATYPE_Video;
+ mediatype->subtype = *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)
+ break;
+ IPin_Release(pPin);
+ }
+ IEnumPins_Release(pEnum);
+ }
+
+ if (!srcpin || !sinkpin || !mediatype) {
+ hr = S_FALSE;
+ goto on_error;
+ }
+ video_info = (VIDEOINFOHEADER *) mediatype->pbFormat;
+ video_info->bmiHeader.biWidth = vfd->size.w;
+ video_info->bmiHeader.biHeight = vfd->size.h;
+ if (vfd->fps.num != 0)
+ video_info->AvgTimePerFrame = (LONGLONG) (10000000 *
+ vfd->fps.denum /
+ (double)vfd->fps.num);
+ video_info->bmiHeader.biSizeImage = DIBSIZE(video_info->bmiHeader);
+ mediatype->lSampleSize = DIBSIZE(video_info->bmiHeader);
+ if (graph->csource_filter)
+ SourceFilter_SetBufferSize(graph->csource_filter,
+ mediatype->lSampleSize);
+
+ 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);
+
+on_error:
+ if (srcpin)
+ IPin_Release(srcpin);
+ if (sinkpin)
+ IPin_Release(sinkpin);
+ if (vi)
+ CoTaskMemFree(vi);
+ if (FAILED(hr))
+ return hr;
+
+ return PJ_SUCCESS;
+}
+
+/* API: create stream */
+static pj_status_t dshow_factory_create_stream(pjmedia_vid_dev_factory *f,
+ const pjmedia_vid_param *param,
+ const pjmedia_vid_cb *cb,
+ void *user_data,
+ pjmedia_vid_stream **p_vid_strm)
+{
+ struct dshow_factory *df = (struct dshow_factory*)f;
+ pj_pool_t *pool;
+ struct dshow_stream *strm;
+ unsigned ngraph = 0;
+ pj_status_t status;
+
+ 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;
+
+ /* Create capture stream here */
+ if (param->dir & PJMEDIA_DIR_CAPTURE) {
+ status = create_filter_graph(PJMEDIA_DIR_CAPTURE, param->cap_id,
+ df, strm, &strm->dgraph[ngraph++]);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ /* Create render stream here */
+ if (param->dir & PJMEDIA_DIR_RENDER) {
+ status = create_filter_graph(PJMEDIA_DIR_RENDER, param->rend_id,
+ df, strm, &strm->dgraph[ngraph++]);
+ 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,
+ ¶m->window);
+ }
+
+ /* Done */
+ strm->base.op = &stream_op;
+ *p_vid_strm = &strm->base;
+
+ return PJ_SUCCESS;
+
+on_error:
+ dshow_stream_destroy((pjmedia_vid_stream *)strm);
+ return PJ_EUNKNOWN;
+}
+
+/* API: Get stream info. */
+static pj_status_t dshow_stream_get_param(pjmedia_vid_stream *s,
+ pjmedia_vid_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_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_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_stream *strm)
+{
+ struct dshow_stream *stream = (struct dshow_stream*)strm;
+ unsigned i;
+
+ stream->quit_flag = PJ_FALSE;
+ stream->cap_thread_exited = PJ_FALSE;
+ stream->rend_thread_exited = PJ_FALSE;
+
+ for (i = 0; i < 2; i++) {
+ HRESULT hr;
+
+ if (!stream->dgraph[i].media_filter)
+ continue;
+ hr = IMediaFilter_Run(stream->dgraph[i].media_filter, 0);
+ if (FAILED(hr)) {
+ return hr;
+ }
+ }
+
+ PJ_LOG(4, (THIS_FILE, "Starting dshow video stream"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t dshow_stream_stop(pjmedia_vid_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);
+
+ for (i = 0; i < 2; i++) {
+ if (!stream->dgraph[i].media_filter)
+ continue;
+ IMediaFilter_Stop(stream->dgraph[i].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_stream *strm)
+{
+ struct dshow_stream *stream = (struct dshow_stream*)strm;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+ dshow_stream_stop(strm);
+
+ for (i = 0; i < 2; i++) {
+ if (stream->dgraph[i].source_filter) {
+ IBaseFilter_Release(stream->dgraph[i].source_filter);
+ stream->dgraph[i].source_filter = NULL;
+ }
+ if (stream->dgraph[i].rend_filter) {
+ IBaseFilter_Release(stream->dgraph[i].rend_filter);
+ stream->dgraph[i].rend_filter = NULL;
+ }
+ if (stream->dgraph[i].media_filter) {
+ IMediaFilter_Release(stream->dgraph[i].media_filter);
+ stream->dgraph[i].media_filter = NULL;
+ }
+ if (stream->dgraph[i].filter_graph) {
+ IFilterGraph_Release(stream->dgraph[i].filter_graph);
+ stream->dgraph[i].filter_graph = NULL;
+ }
+ }
+
+ 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..ad9d070
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/dshowclasses.cpp
@@ -0,0 +1,209 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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
+ */
+#undef _DEBUG
+#undef DEBUG
+
+#include <streams.h>
+
+#pragma comment(lib, "Strmbase.lib")
+
+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);
+
+ 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;
+ 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->GetPointer(&dst_buf);
+ memcpy(dst_buf, buf, size);
+ 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_SetBufferSize(SourceFilter *src,
+ long size)
+{
+ ((OutputPin *)src->GetPin(0))->bufSize = size;
+}
diff --git a/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c b/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c
new file mode 100644
index 0000000..abedbf8
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c
@@ -0,0 +1,494 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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 PJMEDIA_VIDEO_DEV_HAS_FFMPEG
+
+#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;
+ unsigned dev_count;
+ ffmpeg_dev_info dev_info[MAX_DEV_CNT];
+} ffmpeg_factory;
+
+
+typedef struct ffmpeg_stream
+{
+ pjmedia_vid_stream base;
+ ffmpeg_factory *factory;
+ pj_pool_t *pool;
+ pjmedia_vid_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 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_param *param);
+static pj_status_t ffmpeg_factory_create_stream(pjmedia_vid_dev_factory *f,
+ const pjmedia_vid_param *param,
+ const pjmedia_vid_cb *cb,
+ void *user_data,
+ pjmedia_vid_stream **p_vid_strm);
+
+static pj_status_t ffmpeg_stream_get_param(pjmedia_vid_stream *strm,
+ pjmedia_vid_param *param);
+static pj_status_t ffmpeg_stream_get_cap(pjmedia_vid_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ void *value);
+static pj_status_t ffmpeg_stream_set_cap(pjmedia_vid_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ const void *value);
+static pj_status_t ffmpeg_stream_start(pjmedia_vid_stream *strm);
+static pj_status_t ffmpeg_stream_get_frame(pjmedia_vid_stream *s,
+ pjmedia_frame *frame);
+static pj_status_t ffmpeg_stream_stop(pjmedia_vid_stream *strm);
+static pj_status_t ffmpeg_stream_destroy(pjmedia_vid_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
+};
+
+static pjmedia_vid_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_param *param)
+{
+ AVFormatParameters fp;
+ pjmedia_video_format_detail *fmt_detail;
+ 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);
+
+ fmt_detail = (pjmedia_video_format_detail*)param->fmt.detail;
+
+ /* Init ffmpeg format context */
+ *ctx = avformat_alloc_context();
+
+ /* Init ffmpeg format param */
+ pj_bzero(&fp, sizeof(fp));
+ fp.prealloced_context = 1;
+ fp.width = fmt_detail->size.w;
+ fp.height = fmt_detail->size.h;
+ fp.pix_fmt = PIX_FMT_BGR24;
+ fp.time_base.num = param->frame_rate.denum;
+ fp.time_base.den = param->frame_rate.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)
+{
+ 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);
+
+ /* TODO: this should enumerate devices, now it enumerates host APIs */
+ ff->dev_count = 0;
+ 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;
+ info->base.fmt = (pjmedia_format*)
+ pj_pool_calloc(ff->pool, info->base.fmt_cnt,
+ sizeof(pjmedia_format));
+ 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: destroy factory */
+static pj_status_t ffmpeg_factory_destroy(pjmedia_vid_dev_factory *f)
+{
+ ffmpeg_factory *ff = (ffmpeg_factory*)f;
+
+ ff->dev_count = 0;
+
+ 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_param *param)
+{
+ ffmpeg_factory *ff = (ffmpeg_factory*)f;
+ ffmpeg_dev_info *info;
+ pjmedia_video_format_detail *fmt_detail;
+
+ PJ_ASSERT_RETURN(index < ff->dev_count, PJMEDIA_EVID_INVDEV);
+ 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;
+ pj_memcpy(¶m->fmt, &info->base.fmt[0], sizeof(param->fmt));
+ param->clock_rate = 90000;
+ pjmedia_format_init_video(pool, ¶m->fmt, 320, 240, 25, 1,
+ 0, 0);
+ fmt_detail = (pjmedia_video_format_detail*)param->fmt.detail;
+ param->frame_rate.num = fmt_detail->fps.num;
+ param->frame_rate.denum = fmt_detail->fps.denum;
+
+ return PJ_SUCCESS;
+}
+
+
+
+/* API: create stream */
+static pj_status_t ffmpeg_factory_create_stream(pjmedia_vid_dev_factory *f,
+ const pjmedia_vid_param *param,
+ const pjmedia_vid_cb *cb,
+ void *user_data,
+ pjmedia_vid_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 &&
+ param->fmt.detail, 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_stream *s,
+ pjmedia_vid_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_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_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_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_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_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_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/sdl_dev.c b/pjmedia/src/pjmedia-videodev/sdl_dev.c
new file mode 100644
index 0000000..4b9b2d8
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/sdl_dev.c
@@ -0,0 +1,708 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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 <pjmedia-videodev/videodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+
+#if PJMEDIA_VIDEO_DEV_HAS_SDL
+
+#include <SDL.h>
+
+#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, 0, 0xFF000000, 0xFF0000, 0xFF00, 0xFF} ,
+ {PJMEDIA_FORMAT_RGB24, 0, 0xFF0000, 0xFF00, 0xFF, 0} ,
+ {PJMEDIA_FORMAT_BGRA, 0, 0xFF00, 0xFF0000, 0xFF000000, 0xFF} ,
+#else
+ {PJMEDIA_FORMAT_RGBA, 0, 0xFF, 0xFF00, 0xFF0000, 0xFF000000} ,
+ {PJMEDIA_FORMAT_RGB24, 0, 0xFF, 0xFF00, 0xFF0000, 0} ,
+ {PJMEDIA_FORMAT_BGRA, 0, 0xFF0000, 0xFF00, 0xFF, 0xFF000000} ,
+#endif
+
+ {PJMEDIA_FORMAT_DIB , 0, 0xFF0000, 0xFF00, 0xFF, 0} ,
+
+ {PJMEDIA_FORMAT_YUY2, SDL_YUY2_OVERLAY, 0, 0, 0, 0} ,
+ {PJMEDIA_FORMAT_UYVY, SDL_UYVY_OVERLAY, 0, 0, 0, 0} ,
+ {PJMEDIA_FORMAT_YVYU, SDL_YVYU_OVERLAY, 0, 0, 0, 0} ,
+ {PJMEDIA_FORMAT_I420, SDL_IYUV_OVERLAY, 0, 0, 0, 0} ,
+ {PJMEDIA_FORMAT_YV12, SDL_YV12_OVERLAY, 0, 0, 0, 0} ,
+ {PJMEDIA_FORMAT_I420JPEG, SDL_IYUV_OVERLAY, 0, 0, 0, 0} ,
+ {PJMEDIA_FORMAT_I422JPEG, SDL_YV12_OVERLAY, 0, 0, 0, 0} ,
+};
+
+/* sdl_ device info */
+struct sdl_dev_info
+{
+ pjmedia_vid_dev_info info;
+};
+
+/* 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;
+};
+
+/* Video stream. */
+struct sdl_stream
+{
+ pjmedia_vid_stream base; /**< Base stream */
+ pjmedia_vid_param param; /**< Settings */
+ pj_pool_t *pool; /**< Memory pool. */
+
+ pjmedia_vid_cb vid_cb; /**< Stream callback. */
+ void *user_data; /**< Application data. */
+
+ pj_thread_t *sdl_thread; /**< SDL thread. */
+ pj_bool_t is_quitting;
+ pj_bool_t is_running;
+ pj_bool_t render_exited;
+ pj_status_t status;
+
+ SDL_Rect rect; /**< Display rectangle. */
+ SDL_Surface *screen; /**< Display screen. */
+ SDL_Surface *surf; /**< RGB surface. */
+ SDL_Overlay *overlay; /**< YUV overlay. */
+
+ /* For frame conversion */
+ pjmedia_converter *conv;
+ pjmedia_conversion_param conv_param;
+ pjmedia_frame conv_buf;
+
+ 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 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_param *param);
+static pj_status_t sdl_factory_create_stream(pjmedia_vid_dev_factory *f,
+ const pjmedia_vid_param *param,
+ const pjmedia_vid_cb *cb,
+ void *user_data,
+ pjmedia_vid_stream **p_vid_strm);
+
+static pj_status_t sdl_stream_get_param(pjmedia_vid_stream *strm,
+ pjmedia_vid_param *param);
+static pj_status_t sdl_stream_get_cap(pjmedia_vid_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ void *value);
+static pj_status_t sdl_stream_set_cap(pjmedia_vid_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ const void *value);
+static pj_status_t sdl_stream_put_frame(pjmedia_vid_stream *strm,
+ const pjmedia_frame *frame);
+static pj_status_t sdl_stream_start(pjmedia_vid_stream *strm);
+static pj_status_t sdl_stream_stop(pjmedia_vid_stream *strm);
+static pj_status_t sdl_stream_destroy(pjmedia_vid_stream *strm);
+
+/* 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
+};
+
+static pjmedia_vid_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;
+}
+
+
+/* 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;
+
+ sf->dev_count = 1;
+ 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';
+ 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.fmt_cnt = PJ_ARRAY_SIZE(sdl_fmts);
+ ddi->info.fmt = (pjmedia_format*)
+ pj_pool_calloc(sf->pool, ddi->info.fmt_cnt,
+ sizeof(pjmedia_format));
+ for (i = 0; i < ddi->info.fmt_cnt; i++) {
+ pjmedia_format *fmt = &ddi->info.fmt[i];
+ pjmedia_format_init_video(fmt, sdl_fmts[i].fmt_id,
+ DEFAULT_WIDTH, DEFAULT_HEIGHT,
+ DEFAULT_FPS, 1);
+ }
+
+ PJ_LOG(4, (THIS_FILE, "SDL initialized"));
+
+ 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;
+
+ sf->pool = NULL;
+ pj_pool_release(pool);
+
+ 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_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));
+ if (di->info.dir & PJMEDIA_DIR_CAPTURE_RENDER) {
+ param->dir = PJMEDIA_DIR_CAPTURE_RENDER;
+ param->cap_id = index;
+ param->rend_id = index;
+ } else 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->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
+ param->fmt.type = PJMEDIA_TYPE_VIDEO;
+ param->clock_rate = DEFAULT_CLOCK_RATE;
+ param->frame_rate.num = DEFAULT_FPS;
+ param->frame_rate.denum = 1;
+ pjmedia_format_init_video(¶m->fmt, sdl_fmts[0].fmt_id,
+ DEFAULT_WIDTH, DEFAULT_HEIGHT,
+ DEFAULT_FPS, 1);
+
+ 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 int create_sdl_thread(void * data)
+{
+ struct sdl_stream *strm = (struct sdl_stream*)data;
+ sdl_fmt_info *sdl_info = get_sdl_format_info(strm->param.fmt.id);
+ const pjmedia_video_format_info *vfi;
+ pjmedia_video_format_detail *vfd;
+
+ vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(),
+ strm->param.fmt.id);
+ if (!vfi || !sdl_info) {
+ strm->status = PJMEDIA_EVID_BADFORMAT;
+ return strm->status;
+ }
+
+ strm->vafp.size = strm->param.fmt.det.vid.size;
+ strm->vafp.buffer = NULL;
+ if (vfi->apply_fmt(vfi, &strm->vafp) != PJ_SUCCESS) {
+ strm->status = PJMEDIA_EVID_BADFORMAT;
+ return strm->status;
+ }
+
+ /* Initialize the SDL library */
+ if (SDL_Init(SDL_INIT_VIDEO)) {
+ PJ_LOG(4, (THIS_FILE, "Cannot initialize SDL"));
+ strm->status = PJMEDIA_EVID_INIT;
+ return strm->status;
+ }
+
+ vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE);
+ strm->rect.x = strm->rect.y = 0;
+ strm->rect.w = (Uint16)vfd->size.w;
+ strm->rect.h = (Uint16)vfd->size.h;
+
+ /* Initialize the display, requesting a software surface */
+ strm->screen = SDL_SetVideoMode(strm->rect.w, strm->rect.h,
+ 0, SDL_RESIZABLE | SDL_SWSURFACE);
+ if (strm->screen == NULL) {
+ strm->status = PJMEDIA_EVID_SYSERR;
+ return strm->status;
+ }
+ SDL_WM_SetCaption("pjmedia-SDL video", NULL);
+
+ if (vfi->color_model == PJMEDIA_COLOR_MODEL_RGB) {
+ strm->surf = SDL_CreateRGBSurface(SDL_SWSURFACE,
+ strm->rect.w, strm->rect.h,
+ vfi->bpp,
+ sdl_info->Rmask,
+ sdl_info->Gmask,
+ sdl_info->Bmask,
+ sdl_info->Amask);
+ if (strm->surf == NULL) {
+ strm->status = PJMEDIA_EVID_SYSERR;
+ return strm->status;
+ }
+ } else if (vfi->color_model == PJMEDIA_COLOR_MODEL_YUV) {
+ strm->overlay = SDL_CreateYUVOverlay(strm->rect.w, strm->rect.h,
+ sdl_info->sdl_format,
+ strm->screen);
+ if (strm->overlay == NULL) {
+ strm->status = PJMEDIA_EVID_SYSERR;
+ return strm->status;
+ }
+ }
+
+ while(!strm->is_quitting) {
+ SDL_Event sevent;
+ pjmedia_vid_event pevent;
+
+ while (SDL_WaitEvent(&sevent)) {
+ pj_bzero(&pevent, sizeof(pevent));
+
+ switch(sevent.type) {
+ case SDL_USEREVENT:
+ return 0;
+ case SDL_MOUSEBUTTONDOWN:
+ pevent.event_type = PJMEDIA_EVENT_MOUSEBUTTONDOWN;
+ if (strm->vid_cb.on_event_cb)
+ if ((*strm->vid_cb.on_event_cb)(&strm->base,
+ strm->user_data,
+ &pevent) != PJ_SUCCESS)
+ {
+ /* Application wants us to ignore this event */
+ break;
+ }
+ if (strm->is_running)
+ pjmedia_vid_stream_stop(&strm->base);
+ else
+ pjmedia_vid_stream_start(&strm->base);
+ break;
+ case SDL_VIDEORESIZE:
+ pevent.event_type = PJMEDIA_EVENT_WINDOW_RESIZE;
+ if (strm->vid_cb.on_event_cb)
+ if ((*strm->vid_cb.on_event_cb)(&strm->base,
+ strm->user_data,
+ &pevent) != PJ_SUCCESS)
+ {
+ /* Application wants us to ignore this event */
+ break;
+ }
+ /* TODO: move this to OUTPUT_RESIZE cap
+ strm->screen = SDL_SetVideoMode(sevent.resize.w,
+ sevent.resize.h,
+ 0, SDL_RESIZABLE |
+ SDL_SWSURFACE);
+ */
+ break;
+ case SDL_QUIT:
+ pevent.event_type = PJMEDIA_EVENT_WINDOW_CLOSE;
+ /**
+ * To process PJMEDIA_EVENT_WINDOW_CLOSE event,
+ * application should do this in the on_event_cb callback:
+ * 1. stop further calls to #pjmedia_vid_stream_put_frame()
+ * 2. return PJ_SUCCESS
+ * Upon returning from the callback, SDL will destroy its
+ * own stream.
+ *
+ * Returning non-PJ_SUCCESS will cause SDL to ignore
+ * the event
+ */
+ if (strm->vid_cb.on_event_cb) {
+ strm->is_quitting = PJ_TRUE;
+ if ((*strm->vid_cb.on_event_cb)(&strm->base,
+ strm->user_data,
+ &pevent) != PJ_SUCCESS)
+ {
+ /* Application wants us to ignore this event */
+ strm->is_quitting = PJ_FALSE;
+ break;
+ }
+
+ /* Destroy the stream */
+ sdl_stream_destroy(&strm->base);
+ return 0;
+ }
+
+ /**
+ * Default event-handler when there is no user-specified
+ * callback: close the renderer window. We cannot destroy
+ * the stream here since there is no callback to notify
+ * the application.
+ */
+ sdl_stream_stop(&strm->base);
+ SDL_Quit();
+ strm->screen = NULL;
+ return 0;
+ default:
+ break;
+ }
+ }
+
+ }
+
+ return 0;
+}
+
+/* API: create stream */
+static pj_status_t sdl_factory_create_stream(pjmedia_vid_dev_factory *f,
+ const pjmedia_vid_param *param,
+ const pjmedia_vid_cb *cb,
+ void *user_data,
+ pjmedia_vid_stream **p_vid_strm)
+{
+ struct sdl_factory *sf = (struct sdl_factory*)f;
+ pj_pool_t *pool;
+ struct sdl_stream *strm;
+ pj_status_t status;
+
+ /* 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;
+ pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
+ strm->user_data = user_data;
+
+ /* Create capture stream here */
+ if (param->dir & PJMEDIA_DIR_CAPTURE) {
+ }
+
+ /* Create render stream here */
+ if (param->dir & PJMEDIA_DIR_RENDER) {
+ strm->status = PJ_SUCCESS;
+ status = pj_thread_create(pool, "sdl_thread", create_sdl_thread,
+ strm, 0, 0, &strm->sdl_thread);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+ while (strm->status == PJ_SUCCESS && !strm->surf && !strm->overlay)
+ pj_thread_sleep(10);
+ if ((status = strm->status) != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ pjmedia_format_copy(&strm->conv_param.src, ¶m->fmt);
+ pjmedia_format_copy(&strm->conv_param.dst, ¶m->fmt);
+
+ status = pjmedia_converter_create(NULL, pool, &strm->conv_param,
+ &strm->conv);
+ }
+
+ /* Apply the remaining settings */
+ if (param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) {
+ sdl_stream_set_cap(&strm->base,
+ PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW,
+ ¶m->window);
+ }
+
+ /* 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_stream *s,
+ pjmedia_vid_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->fmt.info_size) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
+ }
+*/
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t sdl_stream_get_cap(pjmedia_vid_stream *s,
+ pjmedia_vid_dev_cap cap,
+ void *pval)
+{
+ struct sdl_stream *strm = (struct sdl_stream*)s;
+
+ PJ_UNUSED_ARG(strm);
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW)
+ {
+ return PJ_SUCCESS;
+ } else {
+ return PJMEDIA_EVID_INVCAP;
+ }
+}
+
+/* API: set capability */
+static pj_status_t sdl_stream_set_cap(pjmedia_vid_stream *s,
+ pjmedia_vid_dev_cap cap,
+ const void *pval)
+{
+ struct sdl_stream *strm = (struct sdl_stream*)s;
+
+ PJ_UNUSED_ARG(strm);
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW)
+ {
+ return PJ_SUCCESS;
+ }
+
+ return PJMEDIA_EVID_INVCAP;
+}
+
+/* API: Put frame from stream */
+static pj_status_t sdl_stream_put_frame(pjmedia_vid_stream *strm,
+ const pjmedia_frame *frame)
+{
+ struct sdl_stream *stream = (struct sdl_stream*)strm;
+
+ if (!stream->is_running) {
+ stream->render_exited = PJ_TRUE;
+ return PJ_SUCCESS;
+ }
+
+ if (stream->surf) {
+ if (SDL_MUSTLOCK(stream->surf)) {
+ if (SDL_LockSurface(stream->surf) < 0) {
+ PJ_LOG(3, (THIS_FILE, "Unable to lock SDL surface"));
+ return PJMEDIA_EVID_NOTREADY;
+ }
+ }
+
+ pj_memcpy(stream->surf->pixels, frame->buf, frame->size);
+
+ if (SDL_MUSTLOCK(stream->surf)) {
+ SDL_UnlockSurface(stream->surf);
+ }
+ SDL_BlitSurface(stream->surf, NULL, stream->screen, NULL);
+ SDL_UpdateRect(stream->screen, 0, 0, 0, 0);
+ } else if (stream->overlay) {
+ int i, sz, offset;
+
+ if (SDL_LockYUVOverlay(stream->overlay) < 0) {
+ PJ_LOG(3, (THIS_FILE, "Unable to lock SDL overlay"));
+ return PJMEDIA_EVID_NOTREADY;
+ }
+
+ for (i = 0, offset = 0; i < stream->overlay->planes; i++) {
+ sz = stream->vafp.plane_bytes[i];
+ pj_memcpy(stream->overlay->pixels[i],
+ (char *)frame->buf + offset, sz);
+ offset += sz;
+ }
+
+ SDL_UnlockYUVOverlay(stream->overlay);
+ SDL_DisplayYUVOverlay(stream->overlay, &stream->rect);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: Start stream. */
+static pj_status_t sdl_stream_start(pjmedia_vid_stream *strm)
+{
+ struct sdl_stream *stream = (struct sdl_stream*)strm;
+
+ PJ_LOG(4, (THIS_FILE, "Starting sdl video stream"));
+
+ stream->is_running = PJ_TRUE;
+ stream->render_exited = PJ_FALSE;
+
+ return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t sdl_stream_stop(pjmedia_vid_stream *strm)
+{
+ struct sdl_stream *stream = (struct sdl_stream*)strm;
+ unsigned i;
+
+ PJ_LOG(4, (THIS_FILE, "Stopping sdl video stream"));
+
+ /* Wait for renderer put_frame() to finish */
+ stream->is_running = PJ_FALSE;
+ for (i=0; !stream->render_exited && i<100; ++i)
+ pj_thread_sleep(10);
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t sdl_stream_destroy(pjmedia_vid_stream *strm)
+{
+ struct sdl_stream *stream = (struct sdl_stream*)strm;
+ SDL_Event sevent;
+
+ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+ if (!stream->is_quitting) {
+ sevent.type = SDL_USEREVENT;
+ SDL_PushEvent(&sevent);
+ pj_thread_join(stream->sdl_thread);
+ }
+
+ sdl_stream_stop(strm);
+
+ if (stream->surf) {
+ SDL_FreeSurface(stream->surf);
+ stream->surf = NULL;
+ }
+
+ if (stream->overlay) {
+ SDL_FreeYUVOverlay(stream->overlay);
+ stream->overlay = NULL;
+ }
+
+ SDL_Quit();
+ stream->screen = NULL;
+
+ pj_pool_release(stream->pool);
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_VIDEO_DEV_HAS_SDL */
diff --git a/pjmedia/src/pjmedia-videodev/v4l2_dev.c b/pjmedia/src/pjmedia-videodev/v4l2_dev.c
new file mode 100644
index 0000000..88ef611
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/v4l2_dev.c
@@ -0,0 +1,805 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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_factory *pf;
+
+ unsigned dev_count;
+ vid4lin_dev_info *dev_info;
+} vid4lin_factory;
+
+/* Video stream. */
+typedef struct vid4lin_stream
+{
+ pjmedia_vid_stream base; /**< Base stream */
+ pjmedia_vid_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_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 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_param *param);
+static pj_status_t vid4lin_factory_create_stream(pjmedia_vid_dev_factory *f,
+ const pjmedia_vid_param *prm,
+ const pjmedia_vid_cb *cb,
+ void *user_data,
+ pjmedia_vid_stream **p_strm);
+
+static pj_status_t vid4lin_stream_get_param(pjmedia_vid_stream *strm,
+ pjmedia_vid_param *param);
+static pj_status_t vid4lin_stream_get_cap(pjmedia_vid_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ void *value);
+static pj_status_t vid4lin_stream_set_cap(pjmedia_vid_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ const void *value);
+static pj_status_t vid4lin_stream_get_frame(pjmedia_vid_stream *strm,
+ pjmedia_frame *frame);
+static pj_status_t vid4lin_stream_start(pjmedia_vid_stream *strm);
+static pj_status_t vid4lin_stream_stop(pjmedia_vid_stream *strm);
+static pj_status_t vid4lin_stream_destroy(pjmedia_vid_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
+};
+
+static pjmedia_vid_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;
+
+ pj_bzero(vdi, sizeof(vdi));
+ old_count = f->dev_count;
+ f->dev_count = 0;
+
+ for (i=0; i<V4L2_MAX_DEVS && f->dev_count < V4L2_MAX_DEVS; ++i) {
+ int fd;
+ vid4lin_dev_info *pdi;
+ pj_pool_t *pool = f->pool;
+ 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;
+ pdi->info.fmt = (pjmedia_format*)
+ pj_pool_calloc(pool, sizeof(pjmedia_format), 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->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)
+{
+ 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 initialized with %d devices",
+ cf->dev_count));
+
+ return PJ_SUCCESS;
+}
+
+/* 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->pool) {
+ cf->pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ 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_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;
+ param->frame_rate.num = DEFAULT_FPS;
+ param->frame_rate.denum = 1;
+ pjmedia_format_copy(¶m->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_param *param,
+ pj_uint32_t pix_fmt)
+{
+ const pjmedia_video_format_detail *vfd;
+ struct v4l2_format v4l2_fmt;
+ pj_status_t status;
+
+ vfd = pjmedia_format_get_video_format_detail(¶m->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))
+ {
+ status = PJMEDIA_EVID_BADSIZE;
+ return status;
+ }
+
+ 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,
+ const pjmedia_vid_param *param,
+ const pjmedia_vid_cb *cb,
+ void *user_data,
+ pjmedia_vid_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;
+ 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,
+ 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];
+
+ /* 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;
+
+ PJ_LOG(4,(THIS_FILE, "Opening video4linux2 device %s: format=%s..",
+ stream->name, fmt_info->name));
+
+ stream->fd = v4l2_open(vdi->dev_name, O_RDWR | O_NONBLOCK, 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_stream *s,
+ pjmedia_vid_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_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_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->size = buf.bytesused;
+ frame->timestamp.u64 = 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_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_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_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_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..57a55de
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/videodev.c
@@ -0,0 +1,728 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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>
+
+#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", "Renderer window"),
+};
+
+
+/*
+ * 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
+
+#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 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==32) {
+ *p_desc = "??";
+ return "??";
+ }
+
+ *p_desc = cap_infos[i].info;
+ return cap_infos[i].name;
+}
+
+static pj_status_t get_cap_pointer(const pjmedia_vid_param *param,
+ pjmedia_vid_dev_cap cap,
+ void **ptr,
+ unsigned *size)
+{
+#define FIELD_INFO(name) *ptr = (void*)¶m->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;
+ default:
+ return PJMEDIA_EVID_INVCAP;
+ }
+
+#undef FIELD_INFO
+
+ return PJ_SUCCESS;
+}
+
+/* API: set cap value to param */
+PJ_DEF(pj_status_t) pjmedia_vid_param_set_cap( pjmedia_vid_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_param_get_cap( const pjmedia_vid_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)
+{
+ struct driver *drv = &vid_subsys.drv[drv_idx];
+ pjmedia_vid_dev_factory *f;
+ unsigned i, dev_cnt;
+ pj_status_t status;
+
+ /* 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;
+ }
+
+ /* 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 subsystem. */
+PJ_DEF(pj_status_t) pjmedia_vid_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_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);
+ if (status != PJ_SUCCESS) {
+ deinit_driver(i);
+ continue;
+ }
+ }
+
+ return vid_subsys.dev_cnt ? PJ_SUCCESS : status;
+}
+
+/* API: register an video device factory to the video subsystem. */
+PJ_DEF(pj_status_t)
+pjmedia_vid_register_factory(pjmedia_vid_dev_factory_create_func_ptr adf)
+{
+ pj_status_t status;
+
+ if (vid_subsys.init_count == 0)
+ return PJMEDIA_EVID_INIT;
+
+ vid_subsys.drv[vid_subsys.drv_cnt].create = adf;
+ status = init_driver(vid_subsys.drv_cnt);
+ if (status == PJ_SUCCESS) {
+ vid_subsys.drv_cnt++;
+ } else {
+ deinit_driver(vid_subsys.drv_cnt);
+ }
+
+ return status;
+}
+
+/* API: unregister an video device factory from the video subsystem. */
+PJ_DEF(pj_status_t)
+pjmedia_vid_unregister_factory(pjmedia_vid_dev_factory_create_func_ptr adf)
+{
+ 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 (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 subsystem. */
+PJ_DEF(pj_pool_factory*) pjmedia_vid_subsys_get_pool_factory(void)
+{
+ return vid_subsys.pf;
+}
+
+/* API: Shutdown the video subsystem. */
+PJ_DEF(pj_status_t) pjmedia_vid_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;
+
+ for (i=0; i<vid_subsys.drv_cnt; ++i) {
+ deinit_driver(i);
+ }
+
+ vid_subsys.pf = NULL;
+ 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: 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 && id!=PJMEDIA_VID_INVALID_DEV, PJ_EINVAL);
+ PJ_ASSERT_RETURN(vid_subsys.pf, PJMEDIA_EVID_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_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_param *param)
+{
+ pjmedia_vid_dev_factory *f;
+ unsigned index;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(param && id!=PJMEDIA_VID_INVALID_DEV, PJ_EINVAL);
+ PJ_ASSERT_RETURN(vid_subsys.pf, PJMEDIA_EVID_INIT);
+
+ 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, ¶m->cap_id);
+ make_global_index(f->sys.drv_idx, ¶m->rend_id);
+
+ return PJ_SUCCESS;
+}
+
+/* API: Open video stream object using the specified parameters. */
+PJ_DEF(pj_status_t) pjmedia_vid_stream_create(const pjmedia_vid_param *prm,
+ const pjmedia_vid_cb *cb,
+ void *user_data,
+ pjmedia_vid_stream **p_vid_strm)
+{
+ pjmedia_vid_dev_factory *cap_f=NULL, *rend_f=NULL, *f=NULL;
+ pjmedia_vid_param param;
+ 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);
+
+ /* Must make copy of param because we're changing device ID */
+ pj_memcpy(¶m, prm, sizeof(param));
+
+ /* Normalize cap_id */
+ if (param.dir & PJMEDIA_DIR_CAPTURE) {
+ unsigned index;
+
+ if (param.cap_id < 0)
+ param.cap_id = PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
+
+ status = lookup_dev(param.cap_id, &cap_f, &index);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ param.cap_id = index;
+ f = cap_f;
+ }
+
+ /* Normalize rend_id */
+ if (param.dir & PJMEDIA_DIR_RENDER) {
+ unsigned index;
+
+ if (param.rend_id < 0)
+ param.rend_id = PJMEDIA_VID_DEFAULT_RENDER_DEV;
+
+ status = lookup_dev(param.rend_id, &rend_f, &index);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ param.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((param.dir != PJMEDIA_DIR_CAPTURE_RENDER) ||
+ (cap_f == rend_f),
+ PJMEDIA_EVID_INVDEV);
+
+ /* Create the stream */
+ status = f->op->create_stream(f, ¶m, 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_stream_get_param(pjmedia_vid_stream *strm,
+ pjmedia_vid_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, ¶m->cap_id);
+ make_global_index(strm->sys.drv_idx, ¶m->rend_id);
+
+ return PJ_SUCCESS;
+}
+
+/* API: Get the value of a specific capability of the video stream. */
+PJ_DEF(pj_status_t) pjmedia_vid_stream_get_cap(pjmedia_vid_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_stream_set_cap(pjmedia_vid_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_stream_start(pjmedia_vid_stream *strm)
+{
+ return strm->op->start(strm);
+}
+
+PJ_DEF(pj_status_t) pjmedia_vid_stream_get_frame(pjmedia_vid_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_stream_put_frame(pjmedia_vid_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_stream_stop(pjmedia_vid_stream *strm)
+{
+ return strm->op->stop(strm);
+}
+
+/* API: Destroy the stream. */
+PJ_DEF(pj_status_t) pjmedia_vid_stream_destroy(pjmedia_vid_stream *strm)
+{
+ return strm->op->destroy(strm);
+}
diff --git a/pjmedia/src/pjmedia/avi_player.c b/pjmedia/src/pjmedia/avi_player.c
new file mode 100644
index 0000000..8289a3b
--- /dev/null
+++ b/pjmedia/src/pjmedia/avi_player.c
@@ -0,0 +1,710 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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>
+
+
+#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_PORT_SIGNATURE('A', 'V', 'I', 'P')
+
+#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;
+ pj_int32_t *data32 = (pj_int32_t *)data;
+ pj_int16_t *data16 = (pj_int16_t *)data;
+ count /= (bits == 32? 4 : 2);
+ for (i=0; i<count; ++i) {
+ if (bits == 32)
+ data32[i] = pj_swap32(data32[i]);
+ else
+ 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
+
+struct pjmedia_avi_streams
+{
+ pj_uint8_t num_streams;
+ pjmedia_port **streams;
+};
+
+struct avi_reader_port
+{
+ pjmedia_port base;
+ pj_uint8_t stream_id;
+ unsigned options;
+ 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_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;
+ pj_uint8_t 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++) {
+ /* 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))
+ {
+ /* Check supported video formats here */
+ if (avi_hdr.strl_hdr[i].flags & AVISF_VIDEO_PALCHANGES ||
+ (avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_MJPEG &&
+ avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_UYVY &&
+ avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_YUY2 &&
+ avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_IYUV &&
+ avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_I420 &&
+ avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_DIB &&
+ avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_RGB24 &&
+ avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_RGB32))
+ {
+ 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;
+ }
+ }
+
+ if (nstr == 0) {
+ fport[0]->stream_id = i;
+ nstr++;
+ continue;
+ }
+
+ /* Create fport instance. */
+ fport[nstr] = create_avi_port(pool);
+ if (!fport[nstr]) {
+ status = PJ_ENOMEM;
+ goto on_error;
+ }
+
+ fport[nstr]->stream_id = i;
+
+ /* 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;
+ }
+
+ 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);
+ pjmedia_format_init_video(&fport[i]->base.info.fmt,
+ strl_hdr->codec,
+ strf_hdr->biWidth,
+ strf_hdr->biHeight,
+ strl_hdr->rate,
+ strl_hdr->scale);
+
+ } 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;
+ pjmedia_format_init_audio(&fport[i]->base.info.fmt,
+ strl_hdr->codec,
+ strf_hdr->sample_rate,
+ strf_hdr->nchannels,
+ strf_hdr->bits_per_sample,
+ 20000,
+ strf_hdr->bytes_per_sec,
+ strf_hdr->bytes_per_sec);
+ }
+
+ 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(pj_uint8_t)
+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,
+ pj_uint8_t 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,
+ pj_uint8_t start_idx,
+ pjmedia_type media_type)
+{
+ pj_uint8_t 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;
+ pj_uint8_t 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);
+ frame->timestamp.u64 = 0;
+ 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);
+
+ 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;
+}
diff --git a/pjmedia/src/pjmedia/bidirectional.c b/pjmedia/src/pjmedia/bidirectional.c
index 286ec1c..05be97e 100644
--- a/pjmedia/src/pjmedia/bidirectional.c
+++ b/pjmedia/src/pjmedia/bidirectional.c
@@ -33,7 +33,7 @@
static pj_status_t put_frame(pjmedia_port *this_port,
- const pjmedia_frame *frame)
+ pjmedia_frame *frame)
{
struct bidir_port *p = (struct bidir_port*)this_port;
return pjmedia_port_put_frame(p->put_port, frame);
@@ -54,14 +54,16 @@
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,
- get_port->info.clock_rate,
- get_port->info.channel_count,
- get_port->info.bits_per_sample,
- get_port->info.samples_per_frame);
+ gafd->clock_rate,
+ gafd->channel_count,
+ gafd->bits_per_sample,
+ PJMEDIA_AFD_SPF(gafd));
port->get_port = get_port;
port->put_port = put_port;
diff --git a/pjmedia/src/pjmedia/clock_thread.c b/pjmedia/src/pjmedia/clock_thread.c
index 9da38e3..cbab95f 100644
--- a/pjmedia/src/pjmedia/clock_thread.c
+++ b/pjmedia/src/pjmedia/clock_thread.c
@@ -50,6 +50,7 @@
static int clock_thread(void *arg);
#define MAX_JUMP_MSEC 500
+#define USEC_IN_SEC (pj_uint64_t)1000000
/*
* Create media clock.
@@ -63,25 +64,38 @@
void *user_data,
pjmedia_clock **p_clock)
{
+ return pjmedia_clock_create2(pool,
+ (unsigned)(samples_per_frame * USEC_IN_SEC /
+ channel_count / clock_rate),
+ clock_rate, options, cb, user_data, p_clock);
+}
+
+PJ_DEF(pj_status_t) pjmedia_clock_create2(pj_pool_t *pool,
+ unsigned usec_interval,
+ unsigned clock_rate,
+ unsigned options,
+ pjmedia_clock_callback *cb,
+ void *user_data,
+ pjmedia_clock **p_clock)
+{
pjmedia_clock *clock;
pj_status_t status;
- PJ_ASSERT_RETURN(pool && clock_rate && samples_per_frame && p_clock,
+ PJ_ASSERT_RETURN(pool && usec_interval && clock_rate && p_clock,
PJ_EINVAL);
clock = PJ_POOL_ALLOC_T(pool, pjmedia_clock);
-
status = pj_get_timestamp_freq(&clock->freq);
if (status != PJ_SUCCESS)
return status;
- clock->interval.u64 = samples_per_frame * clock->freq.u64 /
- channel_count / clock_rate;
+ clock->interval.u64 = 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 = samples_per_frame / channel_count;
+ clock->timestamp_inc = (unsigned)(usec_interval * clock_rate /
+ USEC_IN_SEC);
clock->options = options;
clock->cb = cb;
clock->user_data = user_data;
@@ -110,6 +124,7 @@
}
+
/*
* Start the clock.
*/
diff --git a/pjmedia/src/pjmedia/conf_switch.c b/pjmedia/src/pjmedia/conf_switch.c
index d2b874a..a325c47 100644
--- a/pjmedia/src/pjmedia/conf_switch.c
+++ b/pjmedia/src/pjmedia/conf_switch.c
@@ -23,10 +23,10 @@
#include <pjmedia/port.h>
#include <pjmedia/silencedet.h>
#include <pjmedia/sound_port.h>
-#include <pjmedia/stream.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>
@@ -80,6 +80,7 @@
/* Shortcut for port info. */
pjmedia_port_info *info;
+ unsigned samples_per_frame;
/* Calculated signal levels: */
unsigned tx_level; /**< Last tx level to this port. */
@@ -123,7 +124,7 @@
/* Prototypes */
static pj_status_t put_frame(pjmedia_port *this_port,
- const pjmedia_frame *frame);
+ 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);
@@ -166,6 +167,7 @@
/* Save some port's infos, for convenience. */
conf_port->port = port;
conf_port->info = &port->info;
+ conf_port->samples_per_frame= PJMEDIA_PINFO_SAMPLES_PER_FRAME(&port->info);
/* Init pjmedia_frame structure in the TX buffer. */
f = (pjmedia_frame*)conf_port->tx_buf;
@@ -504,6 +506,7 @@
{
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 */
@@ -523,31 +526,32 @@
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->format.id != dst_port->info->format.id ||
- src_port->info->format.bitrate != dst_port->info->format.bitrate)
+ 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_port->info->clock_rate != dst_port->info->clock_rate) {
+ if (src_afd->clock_rate != dst_afd->clock_rate) {
pj_mutex_unlock(conf->mutex);
return PJMEDIA_ENCCLOCKRATE;
}
/* Channel count must match. */
- if (src_port->info->channel_count != dst_port->info->channel_count) {
+ if (src_afd->channel_count != dst_afd->channel_count) {
pj_mutex_unlock(conf->mutex);
return PJMEDIA_ENCCLOCKRATE;
}
/* Source and sink ptime must be equal or a multiplication factor. */
- if ((src_port->info->samples_per_frame %
- dst_port->info->samples_per_frame != 0) &&
- (dst_port->info->samples_per_frame %
- src_port->info->samples_per_frame != 0))
+ 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;
@@ -829,6 +833,7 @@
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);
@@ -843,6 +848,8 @@
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;
@@ -852,11 +859,11 @@
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->info->clock_rate;
- info->channel_count = conf_port->info->channel_count;
- info->samples_per_frame = conf_port->info->samples_per_frame;
- info->bits_per_sample = conf_port->info->bits_per_sample;
- info->format = conf_port->port->info.format;
+ 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;
@@ -960,7 +967,7 @@
}
/* Level adjustment is applicable only for ports that work with raw PCM. */
- PJ_ASSERT_RETURN(conf_port->info->format.id == PJMEDIA_FORMAT_L16,
+ PJ_ASSERT_RETURN(conf_port->info->fmt.id == PJMEDIA_FORMAT_L16,
PJ_EIGNORED);
/* Set normalized adjustment level. */
@@ -1002,7 +1009,7 @@
}
/* Level adjustment is applicable only for ports that work with raw PCM. */
- PJ_ASSERT_RETURN(conf_port->info->format.id == PJMEDIA_FORMAT_L16,
+ PJ_ASSERT_RETURN(conf_port->info->fmt.id == PJMEDIA_FORMAT_L16,
PJ_EIGNORED);
/* Set normalized adjustment level. */
@@ -1046,7 +1053,7 @@
* i.e: samples count in TX buffer equal to listener's
* samples per frame.
*/
- if (f_dst->samples_cnt >= cport_dst->info->samples_per_frame)
+ if (f_dst->samples_cnt >= cport_dst->samples_per_frame)
{
if (cport_dst->slot) {
pjmedia_port_put_frame(cport_dst->port,
@@ -1058,8 +1065,8 @@
}
/* Update TX timestamp. */
- pj_add_timestamp32(&cport_dst->ts_tx,
- cport_dst->info->samples_per_frame);
+ pj_add_timestamp32(&cport_dst->ts_tx,
+ cport_dst->samples_per_frame);
}
}
@@ -1075,7 +1082,7 @@
/* Copy frame to listener's TX buffer. */
nsamples_to_copy = f_end - f_start;
- nsamples_req = cport_dst->info->samples_per_frame -
+ nsamples_req = cport_dst->samples_per_frame -
(frm_dst->size>>1);
if (nsamples_to_copy > nsamples_req)
nsamples_to_copy = nsamples_req;
@@ -1112,7 +1119,7 @@
* i.e: samples count in TX buffer equal to listener's
* samples per frame.
*/
- if ((frm_dst->size >> 1) == cport_dst->info->samples_per_frame)
+ if ((frm_dst->size >> 1) == cport_dst->samples_per_frame)
{
if (cport_dst->slot) {
pjmedia_port_put_frame(cport_dst->port, frm_dst);
@@ -1123,7 +1130,7 @@
/* Update TX timestamp. */
pj_add_timestamp32(&cport_dst->ts_tx,
- cport_dst->info->samples_per_frame);
+ cport_dst->samples_per_frame);
}
}
@@ -1131,18 +1138,18 @@
/* Check port format. */
if (cport_dst->port &&
- cport_dst->port->info.format.id == PJMEDIA_FORMAT_L16)
+ 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->info->samples_per_frame -
+ cport_dst->samples_per_frame -
(frm_dst->size>>1));
frm_dst->type = PJMEDIA_FRAME_TYPE_AUDIO;
- frm_dst->size = cport_dst->info->samples_per_frame << 1;
+ frm_dst->size = cport_dst->samples_per_frame << 1;
if (cport_dst->slot) {
pjmedia_port_put_frame(cport_dst->port, frm_dst);
@@ -1152,7 +1159,7 @@
/* Update TX timestamp. */
pj_add_timestamp32(&cport_dst->ts_tx,
- cport_dst->info->samples_per_frame);
+ cport_dst->samples_per_frame);
}
} else {
pjmedia_frame_ext *f_dst = (pjmedia_frame_ext*)frm_dst;
@@ -1160,7 +1167,7 @@
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->info->samples_per_frame - f_dst->samples_cnt));
+ (cport_dst->samples_per_frame - f_dst->samples_cnt));
if (cport_dst->slot) {
pjmedia_port_put_frame(cport_dst->port, frm_dst);
@@ -1171,7 +1178,7 @@
/* Update TX timestamp. */
pj_add_timestamp32(&cport_dst->ts_tx,
- cport_dst->info->samples_per_frame);
+ cport_dst->samples_per_frame);
}
}
@@ -1185,7 +1192,7 @@
pjmedia_port_put_frame(cport_dst->port, frm_dst);
/* Update TX timestamp. */
- pj_add_timestamp32(&cport_dst->ts_tx, cport_dst->info->samples_per_frame);
+ pj_add_timestamp32(&cport_dst->ts_tx, cport_dst->samples_per_frame);
}
}
@@ -1211,6 +1218,7 @@
*/
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)
@@ -1219,9 +1227,11 @@
/* Var "ci" is to count how many ports have been visited so far. */
++ci;
+ master_samples_per_frame = PJMEDIA_PINFO_SAMPLES_PER_FRAME(
+ &conf->master_port->info);
+
/* Update clock of the port. */
- pj_add_timestamp32(&cport->ts_clock,
- conf->master_port->info.samples_per_frame);
+ 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.
@@ -1230,8 +1240,7 @@
cport->listener_cnt == 0)
{
cport->rx_level = 0;
- pj_add_timestamp32(&cport->ts_rx,
- conf->master_port->info.samples_per_frame);
+ pj_add_timestamp32(&cport->ts_rx, master_samples_per_frame);
continue;
}
@@ -1245,10 +1254,10 @@
unsigned j;
pj_int32_t level = 0;
- pj_add_timestamp32(&cport->ts_rx, cport->info->samples_per_frame);
+ pj_add_timestamp32(&cport->ts_rx, cport->samples_per_frame);
f->buf = &conf->buf[sizeof(pjmedia_frame)];
- f->size = cport->info->samples_per_frame<<1;
+ f->size = cport->samples_per_frame<<1;
/* Get frame from port. */
status = pjmedia_port_get_frame(cport->port, f);
@@ -1302,7 +1311,7 @@
/* Skip if this listener doesn't want to receive audio */
if (listener->tx_setting == PJMEDIA_PORT_DISABLE) {
pj_add_timestamp32(&listener->ts_tx,
- listener->info->samples_per_frame);
+ listener->samples_per_frame);
listener->tx_level = 0;
continue;
}
@@ -1361,7 +1370,7 @@
tmp_f.size = 0;
pjmedia_port_put_frame(cport->port, &tmp_f);
- pj_add_timestamp32(&cport->ts_tx, cport->info->samples_per_frame);
+ pj_add_timestamp32(&cport->ts_tx, cport->samples_per_frame);
}
}
}
@@ -1380,7 +1389,7 @@
pjmedia_frame_ext_subframe *sf;
unsigned samples_per_subframe;
- if (f_src_->samples_cnt < this_cport->info->samples_per_frame) {
+ 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;
@@ -1393,7 +1402,7 @@
samples_per_subframe = f_src_->samples_cnt / f_src_->subframe_cnt;
- while (f_dst->samples_cnt < this_cport->info->samples_per_frame) {
+ 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,
@@ -1404,7 +1413,7 @@
pjmedia_frame_ext_pop_subframes(f_src_, i);
} else if (f_src->type == PJMEDIA_FRAME_TYPE_AUDIO) {
- if ((f_src->size>>1) < this_cport->info->samples_per_frame) {
+ if ((f_src->size>>1) < this_cport->samples_per_frame) {
frame->type = PJMEDIA_FRAME_TYPE_NONE;
frame->size = 0;
break;
@@ -1412,15 +1421,15 @@
pjmedia_copy_samples((pj_int16_t*)frame->buf,
(pj_int16_t*)f_src->buf,
- this_cport->info->samples_per_frame);
- frame->size = this_cport->info->samples_per_frame << 1;
+ 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->info->samples_per_frame,
+ this_cport->samples_per_frame,
f_src->size >> 1);
} else { /* PJMEDIA_FRAME_TYPE_NONE */
pjmedia_frame_ext *f_src_ = (pjmedia_frame_ext*)f_src;
@@ -1442,7 +1451,7 @@
* Recorder callback.
*/
static pj_status_t put_frame(pjmedia_port *this_port,
- const pjmedia_frame *f)
+ pjmedia_frame *f)
{
pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata;
struct conf_port *cport;
@@ -1460,7 +1469,7 @@
return PJ_SUCCESS;
}
- pj_add_timestamp32(&cport->ts_rx, cport->info->samples_per_frame);
+ pj_add_timestamp32(&cport->ts_rx, cport->samples_per_frame);
/* Skip if this port is muted/disabled. */
if (cport->rx_setting == PJMEDIA_PORT_DISABLE) {
@@ -1526,7 +1535,7 @@
/* Skip if this listener doesn't want to receive audio */
if (listener->tx_setting == PJMEDIA_PORT_DISABLE) {
pj_add_timestamp32(&listener->ts_tx,
- listener->info->samples_per_frame);
+ listener->samples_per_frame);
listener->tx_level = 0;
continue;
}
@@ -1534,7 +1543,7 @@
/* Skip loopback for now. */
if (listener == cport) {
pj_add_timestamp32(&listener->ts_tx,
- listener->info->samples_per_frame);
+ listener->samples_per_frame);
listener->tx_level = 0;
continue;
}
diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c
index fd6477c..4fb31c2 100644
--- a/pjmedia/src/pjmedia/conference.c
+++ b/pjmedia/src/pjmedia/conference.c
@@ -26,7 +26,6 @@
#include <pjmedia/silencedet.h>
#include <pjmedia/sound_port.h>
#include <pjmedia/stereo.h>
-#include <pjmedia/stream.h>
#include <pj/array.h>
#include <pj/assert.h>
#include <pj/log.h>
@@ -241,7 +240,7 @@
/* Prototypes */
static pj_status_t put_frame(pjmedia_port *this_port,
- const pjmedia_frame *frame);
+ 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,
@@ -285,10 +284,13 @@
/* 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 = port->info.clock_rate;
- conf_port->samples_per_frame = port->info.samples_per_frame;
- conf_port->channel_count = port->info.channel_count;
+ 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;
@@ -750,8 +752,9 @@
* - same between port & conference bridge.
* - monochannel on port or conference bridge.
*/
- if (strm_port->info.channel_count != conf->channel_count &&
- (strm_port->info.channel_count != 1 && conf->channel_count != 1))
+ 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;
@@ -2050,7 +2053,7 @@
* Recorder (or passive port) callback.
*/
static pj_status_t put_frame(pjmedia_port *this_port,
- const pjmedia_frame *frame)
+ pjmedia_frame *frame)
{
pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata;
struct conf_port *port = conf->ports[this_port->port_data.ldata];
diff --git a/pjmedia/src/pjmedia/converter.c b/pjmedia/src/pjmedia/converter.c
new file mode 100644
index 0000000..9e7280f
--- /dev/null
+++ b/pjmedia/src/pjmedia/converter.c
@@ -0,0 +1,162 @@
+/* $Id$ */
+/*
+ * 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>
+
+
+struct pjmedia_converter_mgr
+{
+ pjmedia_converter_factory factory_list;
+};
+
+static pjmedia_converter_mgr *converter_manager_instance;
+
+PJ_DEF(pj_status_t) pjmedia_converter_mgr_create(pj_pool_t *pool,
+ pjmedia_converter_mgr **p_mgr)
+{
+ pjmedia_converter_mgr *mgr;
+
+ mgr = PJ_POOL_ALLOC_T(pool, pjmedia_converter_mgr);
+ pj_list_init(&mgr->factory_list);
+
+ if (!converter_manager_instance)
+ converter_manager_instance = mgr;
+
+ 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..dfe0c45
--- /dev/null
+++ b/pjmedia/src/pjmedia/converter_libswscale.c
@@ -0,0 +1,202 @@
+/* $Id$ */
+/*
+ * 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,
+ src->apply_param.planes, src->apply_param.strides,
+ 0, src->apply_param.size.h,
+ dst->apply_param.planes, dst->apply_param.strides);
+
+ return h==(int)dst->apply_param.size.h ? PJ_SUCCESS : PJ_EUNKNOWN;
+}
+
+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,
+ pj_pool_t *pool)
+{
+ libswscale_factory.op = &libswscale_factory_op;
+ PJ_UNUSED_ARG(pool);
+ 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);
+ 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
index c0a316f..c6be7ed 100644
--- a/pjmedia/src/pjmedia/delaybuf.c
+++ b/pjmedia/src/pjmedia/delaybuf.c
@@ -21,6 +21,7 @@
#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>
diff --git a/pjmedia/src/pjmedia/dummy.c b/pjmedia/src/pjmedia/dummy.c
new file mode 100644
index 0000000..805de1c
--- /dev/null
+++ b/pjmedia/src/pjmedia/dummy.c
@@ -0,0 +1,24 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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
index 82eec6e..345603f 100644
--- a/pjmedia/src/pjmedia/echo_common.c
+++ b/pjmedia/src/pjmedia/echo_common.c
@@ -20,6 +20,7 @@
#include <pjmedia/echo.h>
#include <pjmedia/delaybuf.h>
+#include <pjmedia/frame.h>
#include <pjmedia/errno.h>
#include <pj/assert.h>
#include <pj/list.h>
diff --git a/pjmedia/src/pjmedia/echo_port.c b/pjmedia/src/pjmedia/echo_port.c
index 16b970d..de23d73 100644
--- a/pjmedia/src/pjmedia/echo_port.c
+++ b/pjmedia/src/pjmedia/echo_port.c
@@ -38,7 +38,7 @@
static pj_status_t ec_put_frame(pjmedia_port *this_port,
- const pjmedia_frame *frame);
+ 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);
@@ -52,25 +52,29 @@
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);
- PJ_ASSERT_RETURN(dn_port->info.bits_per_sample==16 && tail_ms,
+
+ 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,
- dn_port->info.clock_rate,
- dn_port->info.channel_count,
- dn_port->info.bits_per_sample,
- dn_port->info.samples_per_frame);
+ afd->clock_rate,
+ afd->channel_count,
+ afd->bits_per_sample,
+ PJMEDIA_AFD_SPF(afd));
- status = pjmedia_echo_create2(pool, dn_port->info.clock_rate,
- dn_port->info.channel_count,
- dn_port->info.samples_per_frame,
+ 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;
@@ -89,7 +93,7 @@
static pj_status_t ec_put_frame( pjmedia_port *this_port,
- const pjmedia_frame *frame)
+ pjmedia_frame *frame)
{
struct ec *ec = (struct ec*)this_port;
@@ -99,7 +103,7 @@
return pjmedia_port_put_frame(ec->dn_port, frame);
}
- PJ_ASSERT_RETURN(frame->size == this_port->info.samples_per_frame * 2,
+ 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);
@@ -119,7 +123,7 @@
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,
- this_port->info.samples_per_frame);
+ PJMEDIA_PIA_SPF(&this_port->info));
}
pjmedia_echo_playback(ec->ec, (pj_int16_t*)frame->buf);
diff --git a/pjmedia/src/pjmedia/echo_speex.c b/pjmedia/src/pjmedia/echo_speex.c
index 5d5203a..40357aa 100644
--- a/pjmedia/src/pjmedia/echo_speex.c
+++ b/pjmedia/src/pjmedia/echo_speex.c
@@ -20,6 +20,7 @@
#include <pjmedia/echo.h>
#include <pjmedia/errno.h>
+#include <pjmedia/frame.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/pool.h>
diff --git a/pjmedia/src/pjmedia/echo_suppress.c b/pjmedia/src/pjmedia/echo_suppress.c
index 3102f3b..296e98d 100644
--- a/pjmedia/src/pjmedia/echo_suppress.c
+++ b/pjmedia/src/pjmedia/echo_suppress.c
@@ -20,6 +20,7 @@
#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>
diff --git a/pjmedia/src/pjmedia/errno.c b/pjmedia/src/pjmedia/errno.c
index d86eb58..7e24026 100644
--- a/pjmedia/src/pjmedia/errno.c
+++ b/pjmedia/src/pjmedia/errno.c
@@ -114,6 +114,7 @@
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" ),
@@ -142,6 +143,7 @@
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" ),
@@ -266,3 +268,20 @@
return errstr;
}
+/*
+ * 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;
+
+ /* Error not found. */
+ errstr.ptr = buf;
+ errstr.slen = pj_ansi_snprintf(buf, bufsize,
+ "Unknown pjmedia-videodev error %d",
+ statcode);
+
+ return errstr;
+}
+
diff --git a/pjmedia/src/pjmedia/ffmpeg_util.c b/pjmedia/src/pjmedia/ffmpeg_util.c
new file mode 100644
index 0000000..a2def0e
--- /dev/null
+++ b/pjmedia/src/pjmedia/ffmpeg_util.c
@@ -0,0 +1,148 @@
+/* $Id$ */
+/*
+ * 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>
+
+#if PJMEDIA_HAS_LIBAVFORMAT && PJMEDIA_HAS_LIBAVUTIL
+
+#include "ffmpeg_util.h"
+#include <libavformat/avformat.h>
+
+/* 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_RGB24},
+ { PJMEDIA_FORMAT_BGRA, PIX_FMT_BGRA},
+
+ { 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_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_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;
+
+void pjmedia_ffmpeg_add_ref()
+{
+ if (pjmedia_ffmpeg_ref_cnt++ == 0) {
+ 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;
+}
+
+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..70e9aaa
--- /dev/null
+++ b/pjmedia/src/pjmedia/ffmpeg_util.h
@@ -0,0 +1,55 @@
+/* $Id$ */
+/*
+ * 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..a5612da
--- /dev/null
+++ b/pjmedia/src/pjmedia/format.c
@@ -0,0 +1,366 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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>
+
+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);
+
+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_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, 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_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(void) pjmedia_format_init_video( pjmedia_format *fmt,
+ pj_uint32_t fmt_id,
+ unsigned width,
+ unsigned height,
+ unsigned fps_num,
+ unsigned 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_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 {
+ pj_assert(!assert_valid || !"Invalid audio format detail");
+ return NULL;
+ }
+}
+
+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;
+ }
+}
+
+PJ_DEF(pjmedia_format*) pjmedia_format_copy(pjmedia_format *dst,
+ const pjmedia_format *src)
+{
+ return (pjmedia_format*)pj_memcpy(dst, src, sizeof(*src));
+}
+
+
+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;
+}
+
+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;
+}
+
diff --git a/pjmedia/src/pjmedia/master_port.c b/pjmedia/src/pjmedia/master_port.c
index 800b091..d247e7e 100644
--- a/pjmedia/src/pjmedia/master_port.c
+++ b/pjmedia/src/pjmedia/master_port.c
@@ -56,38 +56,41 @@
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_port->info.clock_rate == d_port->info.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(u_port->info.samples_per_frame==
- d_port->info.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_port->info.channel_count == d_port->info.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_port->info.clock_rate;
- samples_per_frame = u_port->info.samples_per_frame;
- channel_count = u_port->info.channel_count;
+ 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 = u_port->info.bytes_per_frame;
- if (d_port->info.bytes_per_frame > bytes_per_frame)
- bytes_per_frame = d_port->info.bytes_per_frame;
+ 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 */
@@ -207,13 +210,16 @@
{
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(
- port->info.clock_rate/port->info.samples_per_frame==
- m->d_port->info.clock_rate/m->d_port->info.samples_per_frame,
+ PJMEDIA_PIA_PTIME(&port->info) ==
+ PJMEDIA_PIA_PTIME(&m->d_port->info),
PJMEDIA_ENCSAMPLESPFRAME
);
}
@@ -246,13 +252,16 @@
{
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(
- port->info.clock_rate/port->info.samples_per_frame==
- m->u_port->info.clock_rate/m->u_port->info.samples_per_frame,
+ PJMEDIA_PIA_PTIME(&port->info) ==
+ PJMEDIA_PIA_PTIME(&m->u_port->info),
PJMEDIA_ENCSAMPLESPFRAME
);
}
diff --git a/pjmedia/src/pjmedia/mem_capture.c b/pjmedia/src/pjmedia/mem_capture.c
index f8e5218..2a1f605 100644
--- a/pjmedia/src/pjmedia/mem_capture.c
+++ b/pjmedia/src/pjmedia/mem_capture.c
@@ -46,7 +46,7 @@
static pj_status_t rec_put_frame(pjmedia_port *this_port,
- const pjmedia_frame *frame);
+ 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);
@@ -140,7 +140,7 @@
static pj_status_t rec_put_frame( pjmedia_port *this_port,
- const pjmedia_frame *frame)
+ pjmedia_frame *frame)
{
struct mem_rec *rec;
char *endpos;
diff --git a/pjmedia/src/pjmedia/mem_player.c b/pjmedia/src/pjmedia/mem_player.c
index 9a4e2cc..40fa567 100644
--- a/pjmedia/src/pjmedia/mem_player.c
+++ b/pjmedia/src/pjmedia/mem_player.c
@@ -48,7 +48,7 @@
static pj_status_t mem_put_frame(pjmedia_port *this_port,
- const pjmedia_frame *frame);
+ 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);
@@ -125,7 +125,7 @@
static pj_status_t mem_put_frame( pjmedia_port *this_port,
- const pjmedia_frame *frame)
+ pjmedia_frame *frame)
{
PJ_UNUSED_ARG(this_port);
PJ_UNUSED_ARG(frame);
@@ -165,7 +165,7 @@
player->eof = PJ_FALSE;
}
- size_needed = this_port->info.bytes_per_frame;
+ size_needed = PJMEDIA_PIA_AVG_FSZ(&this_port->info);
size_written = 0;
endpos = player->buffer + player->buf_size;
@@ -200,11 +200,11 @@
}
}
- frame->size = this_port->info.bytes_per_frame;
+ frame->size = PJMEDIA_PIA_AVG_FSZ(&this_port->info);
frame->timestamp.u64 = player->timestamp.u64;
frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
- player->timestamp.u64 += this_port->info.samples_per_frame;
+ player->timestamp.u64 += PJMEDIA_PIA_SPF(&this_port->info);
return PJ_SUCCESS;
}
diff --git a/pjmedia/src/pjmedia/null_port.c b/pjmedia/src/pjmedia/null_port.c
index 0557583..c775a7c 100644
--- a/pjmedia/src/pjmedia/null_port.c
+++ b/pjmedia/src/pjmedia/null_port.c
@@ -29,7 +29,7 @@
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,
- const pjmedia_frame *frame);
+ pjmedia_frame *frame);
static pj_status_t null_on_destroy(pjmedia_port *this_port);
@@ -67,7 +67,7 @@
* Put frame to file.
*/
static pj_status_t null_put_frame(pjmedia_port *this_port,
- const pjmedia_frame *frame)
+ pjmedia_frame *frame)
{
PJ_UNUSED_ARG(this_port);
PJ_UNUSED_ARG(frame);
@@ -82,10 +82,10 @@
pjmedia_frame *frame)
{
frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
- frame->size = this_port->info.samples_per_frame * 2;
- frame->timestamp.u32.lo += this_port->info.samples_per_frame;
+ 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,
- this_port->info.samples_per_frame);
+ PJMEDIA_PIA_SPF(&this_port->info));
return PJ_SUCCESS;
}
diff --git a/pjmedia/src/pjmedia/port.c b/pjmedia/src/pjmedia/port.c
index 922489c..5089f4f 100644
--- a/pjmedia/src/pjmedia/port.c
+++ b/pjmedia/src/pjmedia/port.c
@@ -21,6 +21,7 @@
#include <pjmedia/errno.h>
#include <pj/assert.h>
#include <pj/log.h>
+#include <pj/pool.h>
#define THIS_FILE "port.c"
@@ -37,24 +38,41 @@
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->name = *name;
info->signature = signature;
- info->type = PJMEDIA_TYPE_AUDIO;
- info->has_info = PJ_TRUE;
- info->need_info = PJ_FALSE;
- info->pt = 0xFF;
- info->encoding_name = pj_str("pcm");
- info->clock_rate = clock_rate;
- info->channel_count = channel_count;
- info->bits_per_sample = bits_per_sample;
- info->samples_per_frame = samples_per_frame;
- info->bytes_per_frame = samples_per_frame * bits_per_sample / 8;
+ 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 frame from the port (and subsequent downstream ports).
@@ -77,7 +95,7 @@
* Put a frame to the port (and subsequent downstream ports).
*/
PJ_DEF(pj_status_t) pjmedia_port_put_frame( pjmedia_port *port,
- const pjmedia_frame *frame )
+ pjmedia_frame *frame )
{
PJ_ASSERT_RETURN(port && frame, PJ_EINVAL);
diff --git a/pjmedia/src/pjmedia/resample_port.c b/pjmedia/src/pjmedia/resample_port.c
index 7813aaf..44de582 100644
--- a/pjmedia/src/pjmedia/resample_port.c
+++ b/pjmedia/src/pjmedia/resample_port.c
@@ -42,7 +42,7 @@
static pj_status_t resample_put_frame(pjmedia_port *this_port,
- const pjmedia_frame *frame);
+ 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);
@@ -57,25 +57,25 @@
{
const pj_str_t name = pj_str("resample");
struct resample_port *rport;
- unsigned ptime;
+ 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(dn_port->info.bits_per_sample == 16, PJMEDIA_ENCBITS);
+ PJ_ASSERT_RETURN(PJMEDIA_PIA_BITS(&dn_port->info) == 16, PJMEDIA_ENCBITS);
- ptime = dn_port->info.samples_per_frame * 1000 /
- dn_port->info.clock_rate;
-
+ d_afd = pjmedia_format_get_audio_format_detail(&dn_port->info.fmt, 1);
+ r_afd = pjmedia_format_get_audio_format_detail(&rport->base.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,
- dn_port->info.channel_count, BYTES_PER_SAMPLE * 8,
- clock_rate * ptime / 1000);
+ d_afd->channel_count, BYTES_PER_SAMPLE * 8,
+ clock_rate * d_afd->frame_time_usec / 1000000);
rport->dn_port = dn_port;
rport->options = opt;
@@ -86,11 +86,11 @@
* both functions may run simultaneously.
*/
rport->get_buf = (pj_int16_t*)
- pj_pool_alloc(pool, dn_port->info.bytes_per_frame);
+ 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, dn_port->info.bytes_per_frame);
+ pj_pool_alloc(pool, PJMEDIA_PIA_AVG_FSZ(&dn_port->info));
PJ_ASSERT_RETURN(rport->put_buf != NULL, PJ_ENOMEM);
@@ -98,10 +98,10 @@
status = pjmedia_resample_create(pool,
(opt&PJMEDIA_RESAMPLE_USE_LINEAR)==0,
(opt&PJMEDIA_RESAMPLE_USE_SMALL_FILTER)==0,
- dn_port->info.channel_count,
- dn_port->info.clock_rate,
- rport->base.info.clock_rate,
- dn_port->info.samples_per_frame,
+ 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;
@@ -110,10 +110,10 @@
status = pjmedia_resample_create(pool,
(opt&PJMEDIA_RESAMPLE_USE_LINEAR)==0,
(opt&PJMEDIA_RESAMPLE_USE_SMALL_FILTER)==0,
- dn_port->info.channel_count,
- rport->base.info.clock_rate,
- dn_port->info.clock_rate,
- rport->base.info.samples_per_frame,
+ d_afd->channel_count,
+ r_afd->clock_rate,
+ d_afd->clock_rate,
+ PJMEDIA_PIA_SPF(&rport->base.info),
&rport->resample_put);
/* Media port interface */
@@ -131,7 +131,7 @@
static pj_status_t resample_put_frame(pjmedia_port *this_port,
- const pjmedia_frame *frame)
+ pjmedia_frame *frame)
{
struct resample_port *rport = (struct resample_port*) this_port;
pjmedia_frame downstream_frame;
@@ -147,7 +147,7 @@
rport->put_buf);
downstream_frame.buf = rport->put_buf;
- downstream_frame.size = rport->dn_port->info.bytes_per_frame;
+ downstream_frame.size = PJMEDIA_PIA_AVG_FSZ(&rport->dn_port->info);
} else {
downstream_frame.buf = frame->buf;
downstream_frame.size = frame->size;
@@ -175,7 +175,7 @@
}
tmp_frame.buf = rport->get_buf;
- tmp_frame.size = rport->dn_port->info.bytes_per_frame;
+ 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;
@@ -187,8 +187,8 @@
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 < rport->base.info.bytes_per_frame ?
- tmp_frame.size : rport->base.info.bytes_per_frame;
+ 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,
@@ -201,7 +201,7 @@
(const pj_int16_t*) tmp_frame.buf,
(pj_int16_t*) frame->buf);
- frame->size = rport->base.info.bytes_per_frame;
+ frame->size = PJMEDIA_PIA_AVG_FSZ(&rport->base.info);
frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
return PJ_SUCCESS;
diff --git a/pjmedia/src/pjmedia/silencedet.c b/pjmedia/src/pjmedia/silencedet.c
index 40c9099..dba663e 100644
--- a/pjmedia/src/pjmedia/silencedet.c
+++ b/pjmedia/src/pjmedia/silencedet.c
@@ -23,6 +23,7 @@
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/pool.h>
+#include <pj/string.h>
#define THIS_FILE "silencedet.c"
diff --git a/pjmedia/src/pjmedia/sound_port.c b/pjmedia/src/pjmedia/sound_port.c
index 1029fc3..66324fc 100644
--- a/pjmedia/src/pjmedia/sound_port.c
+++ b/pjmedia/src/pjmedia/sound_port.c
@@ -637,24 +637,25 @@
PJ_DEF(pj_status_t) pjmedia_snd_port_connect( pjmedia_snd_port *snd_port,
pjmedia_port *port)
{
- pjmedia_port_info *pinfo;
+ 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.
*/
- pinfo = &port->info;
- if (pinfo->clock_rate != snd_port->clock_rate)
+ if (afd->clock_rate != snd_port->clock_rate)
return PJMEDIA_ENCCLOCKRATE;
- if (pinfo->samples_per_frame != snd_port->samples_per_frame)
+ if (PJMEDIA_AFD_SPF(afd) != snd_port->samples_per_frame)
return PJMEDIA_ENCSAMPLESPFRAME;
- if (pinfo->channel_count != snd_port->channel_count)
+ if (afd->channel_count != snd_port->channel_count)
return PJMEDIA_ENCCHANNEL;
- if (pinfo->bits_per_sample != snd_port->bits_per_sample)
+ if (afd->bits_per_sample != snd_port->bits_per_sample)
return PJMEDIA_ENCBITS;
/* Port is okay. */
diff --git a/pjmedia/src/pjmedia/splitcomb.c b/pjmedia/src/pjmedia/splitcomb.c
index 4e2e628..05f96dd 100644
--- a/pjmedia/src/pjmedia/splitcomb.c
+++ b/pjmedia/src/pjmedia/splitcomb.c
@@ -189,13 +189,13 @@
* Prototypes.
*/
static pj_status_t put_frame(pjmedia_port *this_port,
- const pjmedia_frame *frame);
+ 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,
- const pjmedia_frame *frame);
+ 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);
@@ -284,7 +284,7 @@
PJ_ASSERT_RETURN(sc->base.info.signature == SIGNATURE, PJ_EINVAL);
/* Check the channel number */
- PJ_ASSERT_RETURN(ch_num < sc->base.info.channel_count, PJ_EINVAL);
+ PJ_ASSERT_RETURN(ch_num < PJMEDIA_PIA_CCNT(&sc->base.info), PJ_EINVAL);
/* options is unused for now */
PJ_UNUSED_ARG(options);
@@ -308,7 +308,8 @@
const pj_str_t name = pj_str("scomb-rev");
struct splitcomb *sc = (struct splitcomb*) splitcomb;
struct reverse_port *rport;
- unsigned buf_cnt, ptime;
+ unsigned buf_cnt;
+ const pjmedia_audio_format_detail *sc_afd, *p_afd;
pjmedia_port *port;
pj_status_t status;
@@ -319,11 +320,13 @@
PJ_ASSERT_RETURN(sc->base.info.signature == SIGNATURE, PJ_EINVAL);
/* Check the channel number */
- PJ_ASSERT_RETURN(ch_num < sc->base.info.channel_count, PJ_EINVAL);
+ 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;
@@ -332,10 +335,12 @@
/* Initialize port info... */
port = &rport->base;
pjmedia_port_info_init(&port->info, &name, SIGNATURE_PORT,
- splitcomb->info.clock_rate, 1,
- splitcomb->info.bits_per_sample,
- splitcomb->info.samples_per_frame /
- splitcomb->info.channel_count);
+ 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;
@@ -347,30 +352,27 @@
if (buf_cnt == 0)
buf_cnt = MAX_BUF_CNT;
- ptime = port->info.samples_per_frame * 1000 / port->info.clock_rate /
- port->info.channel_count;
-
rport->max_burst = MAX_BURST;
rport->max_null_frames = MAX_NULL_FRAMES;
/* Create downstream/put buffers */
status = pjmedia_delay_buf_create(pool, "scombdb-dn",
- port->info.clock_rate,
- port->info.samples_per_frame,
- port->info.channel_count,
- buf_cnt * ptime, 0,
- &rport->buf[DIR_DOWNSTREAM].dbuf);
+ 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",
- port->info.clock_rate,
- port->info.samples_per_frame,
- port->info.channel_count,
- buf_cnt * ptime, 0,
- &rport->buf[DIR_UPSTREAM].dbuf);
+ 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;
@@ -378,7 +380,8 @@
/* And temporary upstream/get buffer */
rport->tmp_up_buf = (pj_int16_t*)
- pj_pool_alloc(pool, port->info.bytes_per_frame);
+ pj_pool_alloc(pool,
+ PJMEDIA_PIA_AVG_FSZ(&port->info));
/* Save port in the splitcomb */
sc->port_desc[ch_num].port = &rport->base;
@@ -436,7 +439,7 @@
rport->buf[dir].level += op;
if (op == OP_PUT) {
- rport->buf[dir].ts.u64 += rport->base.info.samples_per_frame;
+ rport->buf[dir].ts.u64 += PJMEDIA_PIA_SPF(&rport->base.info);
}
if (rport->buf[dir].paused) {
@@ -484,14 +487,14 @@
* it to the appropriate port.
*/
static pj_status_t put_frame(pjmedia_port *this_port,
- const pjmedia_frame *frame)
+ 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 < this_port->info.channel_count; ++ch) {
+ for (ch=0; ch < PJMEDIA_PIA_CCNT(&this_port->info); ++ch) {
pjmedia_port *port = sc->port_desc[ch].port;
if (!port) continue;
@@ -533,7 +536,7 @@
/* Generate zero frame. */
pjmedia_zero_samples(sc->put_buf,
- this_port->info.samples_per_frame);
+ PJMEDIA_PIA_SPF(&this_port->info));
/* Put frame to delay buffer */
pjmedia_delay_buf_put(rport->buf[DIR_DOWNSTREAM].dbuf,
@@ -547,13 +550,13 @@
/* Not sure how we would handle partial frame, so better reject
* it for now.
*/
- PJ_ASSERT_RETURN(frame->size == this_port->info.bytes_per_frame,
+ PJ_ASSERT_RETURN(frame->size == PJMEDIA_PIA_AVG_FSZ(&this_port->info),
PJ_EINVAL);
/*
* Write mono frame into each channels
*/
- for (ch=0; ch < this_port->info.channel_count; ++ch) {
+ for (ch=0; ch < PJMEDIA_PIA_CCNT(&this_port->info); ++ch) {
pjmedia_port *port = sc->port_desc[ch].port;
if (!port)
@@ -561,17 +564,17 @@
/* Extract the mono frame to temporary buffer */
extract_mono_frame((const pj_int16_t*)frame->buf, sc->put_buf, ch,
- this_port->info.channel_count,
+ PJMEDIA_PIA_CCNT(&this_port->info),
frame->size * 8 /
- this_port->info.bits_per_sample /
- this_port->info.channel_count);
+ 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 / this_port->info.channel_count;
+ mono_frame.size = frame->size / PJMEDIA_PIA_CCNT(&this_port->info);
mono_frame.type = frame->type;
mono_frame.timestamp.u64 = frame->timestamp.u64;
@@ -612,20 +615,20 @@
pj_bool_t has_frame = PJ_FALSE;
/* Read frame from each port */
- for (ch=0; ch < this_port->info.channel_count; ++ch) {
+ 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,
- this_port->info.samples_per_frame /
- this_port->info.channel_count);
+ 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 = port->info.bytes_per_frame;
+ mono_frame.size = PJMEDIA_PIA_AVG_FSZ(&port->info);
mono_frame.timestamp.u64 = frame->timestamp.u64;
status = pjmedia_port_get_frame(port, &mono_frame);
@@ -633,7 +636,7 @@
mono_frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
{
pjmedia_zero_samples(sc->get_buf,
- port->info.samples_per_frame);
+ PJMEDIA_PIA_SPF(&port->info));
}
frame->timestamp.u64 = mono_frame.timestamp.u64;
@@ -651,7 +654,7 @@
} else {
pjmedia_zero_samples(sc->get_buf,
- port->info.samples_per_frame);
+ PJMEDIA_PIA_SPF(&port->info));
}
frame->timestamp.u64 = rport->buf[DIR_UPSTREAM].ts.u64;
@@ -660,9 +663,9 @@
/* Combine the mono frame into multichannel frame */
store_mono_frame(sc->get_buf,
(pj_int16_t*)frame->buf, ch,
- this_port->info.channel_count,
- this_port->info.samples_per_frame /
- this_port->info.channel_count);
+ PJMEDIA_PIA_CCNT(&this_port->info),
+ PJMEDIA_PIA_SPF(&this_port->info) /
+ PJMEDIA_PIA_CCNT(&this_port->info));
has_frame = PJ_TRUE;
}
@@ -670,7 +673,7 @@
/* Return NO_FRAME is we don't get any frames from downstream ports */
if (has_frame) {
frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
- frame->size = this_port->info.bytes_per_frame;
+ frame->size = PJMEDIA_PIA_AVG_FSZ(&this_port->info);
} else
frame->type = PJMEDIA_FRAME_TYPE_NONE;
@@ -694,11 +697,11 @@
* will be picked up by get_frame() above.
*/
static pj_status_t rport_put_frame(pjmedia_port *this_port,
- const pjmedia_frame *frame)
+ pjmedia_frame *frame)
{
struct reverse_port *rport = (struct reverse_port*) this_port;
- pj_assert(frame->size <= rport->base.info.bytes_per_frame);
+ pj_assert(frame->size <= PJMEDIA_PIA_AVG_FSZ(&rport->base.info));
/* Handle NULL frame */
if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO) {
@@ -730,7 +733,7 @@
/* Generate zero frame. */
pjmedia_zero_samples(rport->tmp_up_buf,
- this_port->info.samples_per_frame);
+ PJMEDIA_PIA_SPF(&this_port->info));
/* Put frame to delay buffer */
return pjmedia_delay_buf_put(rport->buf[DIR_UPSTREAM].dbuf,
@@ -738,7 +741,7 @@
}
/* Not sure how to handle partial frame, so better reject for now */
- PJ_ASSERT_RETURN(frame->size == this_port->info.bytes_per_frame,
+ PJ_ASSERT_RETURN(frame->size == PJMEDIA_PIA_AVG_FSZ(&this_port->info),
PJ_EINVAL);
/* Reset NULL frame counter */
@@ -755,7 +758,7 @@
* modifies the frame content.
*/
pjmedia_copy_samples(rport->tmp_up_buf, (const pj_int16_t*)frame->buf,
- this_port->info.samples_per_frame);
+ PJMEDIA_PIA_SPF(&this_port->info));
/* Put frame to delay buffer */
return pjmedia_delay_buf_put(rport->buf[DIR_UPSTREAM].dbuf,
@@ -783,7 +786,7 @@
}
/* Get frame from delay buffer */
- frame->size = this_port->info.bytes_per_frame;
+ frame->size = PJMEDIA_PIA_AVG_FSZ(&this_port->info);
frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
frame->timestamp.u64 = rport->buf[DIR_DOWNSTREAM].ts.u64;
diff --git a/pjmedia/src/pjmedia/stereo_port.c b/pjmedia/src/pjmedia/stereo_port.c
index fe9b51c..15ffac9 100644
--- a/pjmedia/src/pjmedia/stereo_port.c
+++ b/pjmedia/src/pjmedia/stereo_port.c
@@ -39,7 +39,7 @@
static pj_status_t stereo_put_frame(pjmedia_port *this_port,
- const pjmedia_frame *frame);
+ 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);
@@ -60,24 +60,27 @@
PJ_ASSERT_RETURN(pool && dn_port && channel_count && p_port, PJ_EINVAL);
/* Only supports 16bit samples per frame */
- PJ_ASSERT_RETURN(dn_port->info.bits_per_sample == 16, PJMEDIA_ENCBITS);
+ PJ_ASSERT_RETURN(PJMEDIA_PIA_BITS(&dn_port->info) == 16,
+ PJMEDIA_ENCBITS);
/* Validate channel counts */
- PJ_ASSERT_RETURN(((dn_port->info.channel_count>1 && channel_count==1) ||
- (dn_port->info.channel_count==1 && channel_count>1)),
+ PJ_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 = dn_port->info.samples_per_frame * channel_count /
- dn_port->info.channel_count;
+ 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,
- dn_port->info.clock_rate,
+ PJMEDIA_PIA_SRATE(&dn_port->info),
channel_count,
- dn_port->info.bits_per_sample,
+ PJMEDIA_PIA_BITS(&dn_port->info),
samples_per_frame);
sport->dn_port = dn_port;
@@ -85,12 +88,14 @@
/* We always need buffer for put_frame */
sport->put_buf = (pj_int16_t*)
- pj_pool_alloc(pool, dn_port->info.bytes_per_frame);
+ pj_pool_alloc(pool,
+ PJMEDIA_PIA_AVG_FSZ(&dn_port->info));
/* See if we need buffer for get_frame */
- if (dn_port->info.channel_count > channel_count) {
+ if (PJMEDIA_PIA_CCNT(&dn_port->info) > channel_count) {
sport->get_buf = (pj_int16_t*)
- pj_pool_alloc(pool, dn_port->info.bytes_per_frame);
+ pj_pool_alloc(pool,
+ PJMEDIA_PIA_AVG_FSZ(&dn_port->info));
}
/* Media port interface */
@@ -106,9 +111,10 @@
}
static pj_status_t stereo_put_frame(pjmedia_port *this_port,
- const pjmedia_frame *frame)
+ 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. */
@@ -116,23 +122,27 @@
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 (sport->dn_port->info.channel_count == 1) {
+ if (dn_afd->channel_count == 1) {
pjmedia_convert_channel_nto1((pj_int16_t*)tmp_frame.buf,
(const pj_int16_t*)frame->buf,
- sport->base.info.channel_count,
- sport->base.info.samples_per_frame,
+ 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,
- sport->dn_port->info.channel_count,
- sport->base.info.samples_per_frame,
+ dn_afd->channel_count,
+ PJMEDIA_AFD_SPF(s_afd),
sport->options);
}
- tmp_frame.size = sport->dn_port->info.bytes_per_frame;
+ tmp_frame.size = PJMEDIA_AFD_AVG_FSZ(dn_afd);
} else {
tmp_frame.buf = frame->buf;
tmp_frame.size = frame->size;
@@ -150,6 +160,7 @@
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;
@@ -159,8 +170,12 @@
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 = sport->dn_port->info.bytes_per_frame;
+ 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;
@@ -177,21 +192,21 @@
return PJ_SUCCESS;
}
- if (sport->base.info.channel_count == 1) {
+ if (s_afd->channel_count == 1) {
pjmedia_convert_channel_nto1((pj_int16_t*)frame->buf,
(const pj_int16_t*)tmp_frame.buf,
- sport->dn_port->info.channel_count,
- sport->dn_port->info.samples_per_frame,
+ 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,
- sport->base.info.channel_count,
- sport->dn_port->info.samples_per_frame,
+ s_afd->channel_count,
+ PJMEDIA_AFD_SPF(dn_afd),
sport->options);
}
- frame->size = sport->base.info.bytes_per_frame;
+ frame->size = PJMEDIA_AFD_AVG_FSZ(s_afd);
frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
return PJ_SUCCESS;
diff --git a/pjmedia/src/pjmedia/stream.c b/pjmedia/src/pjmedia/stream.c
index 7a62dbe..f0bcf37 100644
--- a/pjmedia/src/pjmedia/stream.c
+++ b/pjmedia/src/pjmedia/stream.c
@@ -77,6 +77,7 @@
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. */
};
@@ -177,8 +178,6 @@
/**< Normalized ts length per frame
received according to
'erroneous' definition */
- pj_uint32_t rtp_rx_last_ts;/**< Last received RTP timestamp
- for timestamp checking */
unsigned rtp_rx_last_cnt;/**< Nb of frames in last pkt */
unsigned rtp_rx_check_cnt;
/**< Counter of remote timestamp
@@ -208,6 +207,13 @@
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*/
+
+ pjmedia_vid_codec *vcodec; /**< Codec instance being used. */
+ pjmedia_vid_codec_info vcodec_info; /**< Codec param. */
+ pjmedia_vid_codec_param vcodec_param; /**< Codec param. */
+
};
@@ -471,7 +477,7 @@
/* Lock jitter buffer mutex first */
pj_mutex_lock( stream->jb_mutex );
- samples_required = stream->port.info.samples_per_frame;
+ 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 /
@@ -725,7 +731,7 @@
* until we have enough frames according to codec's ptime.
*/
- samples_required = stream->port.info.samples_per_frame;
+ 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 /
@@ -876,7 +882,7 @@
*first = 1;
}
- digit->duration += stream->port.info.samples_per_frame;
+ digit->duration += PJMEDIA_PIA_SPF(&stream->port.info);
event->event = (pj_uint8_t)digit->event;
event->e_vol = 10;
@@ -1087,7 +1093,7 @@
/* How many samples are needed */
count = stream->codec_param.info.enc_ptime *
- stream->port.info.clock_rate / 1000;
+ PJMEDIA_PIA_SRATE(&stream->port.info) / 1000;
/* See if we have enough samples */
if (stream->enc_buf_count >= count) {
@@ -1110,7 +1116,7 @@
* put_frame_imp()
*/
static pj_status_t put_frame_imp( pjmedia_port *port,
- const pjmedia_frame *frame )
+ pjmedia_frame *frame )
{
pjmedia_stream *stream = (pjmedia_stream*) port->port_data.pdata;
pjmedia_channel *channel = stream->enc;
@@ -1151,8 +1157,8 @@
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 = stream->port.info.samples_per_frame /
- stream->port.info.channel_count;
+ ts_len = PJMEDIA_PIA_SPF(&stream->port.info) /
+ PJMEDIA_PIA_CCNT(&stream->port.info);
else
ts_len = 0;
@@ -1219,7 +1225,7 @@
*/
} else if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO &&
frame->buf == NULL &&
- stream->port.info.format.id == PJMEDIA_FORMAT_L16 &&
+ 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 *
@@ -1362,7 +1368,7 @@
* RTP packet, and transmit to peer.
*/
static pj_status_t put_frame( pjmedia_port *port,
- const pjmedia_frame *frame )
+ pjmedia_frame *frame )
{
pjmedia_stream *stream = (pjmedia_stream*) port->port_data.pdata;
pjmedia_frame tmp_zero_frame;
@@ -1410,7 +1416,8 @@
*/
if (stream->vad_enabled != stream->codec_param.setting.vad &&
(stream->tx_duration - stream->ts_vad_disabled) >
- stream->port.info.clock_rate * PJMEDIA_STREAM_VAD_SUSPEND_MSEC / 1000)
+ PJMEDIA_PIA_SRATE(&stream->port.info) *
+ PJMEDIA_STREAM_VAD_SUSPEND_MSEC / 1000)
{
stream->codec_param.setting.vad = stream->vad_enabled;
stream->codec->op->modify(stream->codec, &stream->codec_param);
@@ -1573,7 +1580,6 @@
const void *payload;
unsigned payloadlen;
pjmedia_rtp_status seq_st;
- pj_bool_t check_pt;
pj_status_t status;
pj_bool_t pkt_discarded = PJ_FALSE;
@@ -1603,15 +1609,8 @@
/* Update RTP session (also checks if RTP session can accept
* the incoming packet.
*/
- check_pt = (hdr->pt != stream->rx_event_pt) && PJMEDIA_STREAM_CHECK_RTP_PT;
- pjmedia_rtp_session_update2(&channel->rtp, hdr, &seq_st, check_pt);
-#if !PJMEDIA_STREAM_CHECK_RTP_PT
- if (!check_pt && hdr->pt != channel->rtp.out_pt &&
- hdr->pt != stream->rx_event_pt)
- {
- seq_st.status.flag.badpt = 1;
- }
-#endif
+ 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, "
@@ -1672,6 +1671,63 @@
status = pjmedia_jbuf_reset(stream->jb);
PJ_LOG(4,(stream->port.info.name.ptr, "Jitter buffer reset"));
+ } else if (stream->vcodec) {
+ pj_timestamp ts;
+ pj_uint8_t *p;
+ pj_size_t p_len;
+
+ /* Video stream */
+
+ ts.u64 = pj_ntohl(hdr->ts);
+
+ /* Put any buffered bitstream if timestamp is changed,
+ * in case of RTP packet lost.
+ */
+ if (stream->rtp_rx_last_ts != ts.u64 && channel->out_pkt_len)
+ {
+ int seq;
+
+ seq = stream->rtp_rx_last_ts/stream->rtp_rx_ts_len_per_frame;
+ pjmedia_jbuf_put_frame(stream->jb, channel->out_pkt,
+ channel->out_pkt_len, seq);
+ channel->out_pkt_len = 0;
+ }
+
+ p = (pj_uint8_t*)channel->out_pkt + channel->out_pkt_len;
+ p_len = channel->out_pkt_size - channel->out_pkt_len;
+
+ /* Parse the payload. */
+ status = (*stream->vcodec->op->unpacketize)(
+ stream->vcodec,
+ payload, payloadlen,
+ p, &p_len);
+ if (status != PJ_SUCCESS) {
+ LOGERR_((stream->port.info.name.ptr,
+ "Codec parse() error",
+ status));
+ channel->out_pkt_len = 0;
+ } else {
+ channel->out_pkt_len += p_len;
+ if (channel->out_pkt_len > channel->out_pkt_size) {
+ channel->out_pkt_len = channel->out_pkt_size;
+ PJ_LOG(3, (THIS_FILE, "Video bitstream trucated because of"
+ "not enough buffer"));
+ }
+
+ /* Check if RTP header specifies end of frame mark */
+ PJ_TODO(find_better_way_to_find_out_end_of_frame_mark);
+ if (hdr->m) {
+ int seq;
+
+ seq = (int)(ts.u64/stream->rtp_rx_ts_len_per_frame);
+ pjmedia_jbuf_put_frame(stream->jb, channel->out_pkt,
+ channel->out_pkt_len, seq);
+ channel->out_pkt_len = 0;
+ }
+ }
+
+ stream->rtp_rx_last_ts = (pj_uint32_t)ts.u64;
+
} else {
/*
* Packets may contain more than one frames, while the jitter
@@ -1726,10 +1782,10 @@
* are only 160 and 320, 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==stream->port.info.samples_per_frame
+ if(stream->codec_param.info.pt == PJMEDIA_RTP_PT_G722 &&
+ (peer_frm_ts_diff==PJMEDIA_PIA_SPF(&stream->port.info)
|| peer_frm_ts_diff ==
- stream->port.info.samples_per_frame >> 1))
+ PJMEDIA_PIA_SPF(&stream->port.info) >>1))
{
if (peer_frm_ts_diff < stream->rtp_rx_ts_len_per_frame)
stream->rtp_rx_ts_len_per_frame = peer_frm_ts_diff;
@@ -1882,13 +1938,19 @@
/* Allocate buffer for outgoing packet. */
- 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)
- channel->out_pkt_size = PJMEDIA_MAX_MTU;
+ if (param->type == PJMEDIA_TYPE_VIDEO) {
+ channel->out_pkt_size = sizeof(pjmedia_rtp_hdr) +
+ stream->frame_size;
+ } else 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)
+ channel->out_pkt_size = PJMEDIA_MAX_MTU;
+ } else {
+ return PJ_ENOTSUP;
+ }
/* It should big enough to hold (minimally) RTCP SR with an SDES. */
min_out_pkt_size = sizeof(pjmedia_rtcp_sr_pkt) +
@@ -1927,6 +1989,13 @@
}
+static pj_status_t video_stream_create(pjmedia_endpt *endpt,
+ pj_pool_t *pool,
+ const pjmedia_stream_info *info,
+ pjmedia_transport *tp,
+ void *user_data,
+ pjmedia_stream **p_stream);
+
/*
* Create media stream.
*/
@@ -1942,12 +2011,23 @@
pjmedia_stream *stream;
pj_str_t name;
unsigned jb_init, jb_max, jb_min_pre, jb_max_pre, len;
+ pjmedia_audio_format_detail *afd;
char *p;
pj_status_t status;
PJ_ASSERT_RETURN(pool && info && p_stream, PJ_EINVAL);
+ if (info->type == PJMEDIA_TYPE_VIDEO) {
+ status = video_stream_create(endpt, pool, info, tp,
+ user_data, &stream);
+ if (status != PJ_SUCCESS)
+ goto err_cleanup;
+
+ *p_stream = stream;
+ return PJ_SUCCESS;
+ }
+
/* Allocate the media stream: */
stream = PJ_POOL_ZALLOC_T(pool, pjmedia_stream);
@@ -1964,12 +2044,14 @@
PJMEDIA_PORT_SIGNATURE('S', 'T', 'R', 'M'),
info->fmt.clock_rate, info->fmt.channel_cnt,
16, 80);
+ afd = pjmedia_format_get_audio_format_detail(&stream->port.info.fmt, 1);
/* Init port. */
- pj_strdup(pool, &stream->port.info.encoding_name, &info->fmt.encoding_name);
- stream->port.info.clock_rate = info->fmt.clock_rate;
- stream->port.info.channel_count = info->fmt.channel_cnt;
+ //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: */
@@ -2040,33 +2122,33 @@
goto err_cleanup;
/* Set additional info and callbacks. */
- stream->port.info.bits_per_sample = 16;
- stream->port.info.samples_per_frame = info->fmt.clock_rate *
- stream->codec_param.info.channel_cnt *
- stream->codec_param.info.frm_ptime *
- stream->codec_param.setting.frm_per_pkt /
- 1000;
- stream->port.info.format.id = stream->codec_param.info.fmt_id;
+ 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 */
- stream->port.info.bytes_per_frame = stream->port.info.samples_per_frame *
- stream->port.info.bits_per_sample / 8;
+ afd->avg_bps = afd->max_bps = afd->clock_rate *
+ afd->bits_per_sample / 8;
+
stream->port.put_frame = &put_frame;
stream->port.get_frame = &get_frame;
} else {
/* Encoded format */
- stream->port.info.bytes_per_frame = stream->codec_param.info.max_bps *
- stream->codec_param.info.frm_ptime *
- stream->codec_param.setting.frm_per_pkt /
- 8 / 1000;
- if ((stream->codec_param.info.max_bps * stream->codec_param.info.frm_ptime *
- stream->codec_param.setting.frm_per_pkt) % 8000 != 0)
+ 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;
@@ -2083,14 +2165,13 @@
stream->enc_samples_per_pkt = stream->codec_param.info.enc_ptime *
stream->codec_param.info.channel_cnt *
- stream->port.info.clock_rate / 1000;
+ afd->clock_rate / 1000;
/* Set buffer size as twice the largest ptime value between
* stream's ptime, encoder ptime, or decoder ptime.
*/
- ptime = stream->port.info.samples_per_frame * 1000 /
- stream->port.info.clock_rate;
+ ptime = afd->frame_time_usec / 1000;
if (stream->codec_param.info.enc_ptime > ptime)
ptime = stream->codec_param.info.enc_ptime;
@@ -2101,12 +2182,12 @@
ptime <<= 1;
/* Allocate buffer */
- stream->enc_buf_size = stream->port.info.clock_rate * ptime / 1000;
+ 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 = stream->port.info.samples_per_frame;
+ stream->enc_samples_per_pkt = PJMEDIA_AFD_SPF(afd);
}
@@ -2139,7 +2220,7 @@
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 = stream->port.info.samples_per_frame /
+ stream->rtp_rx_ts_len_per_frame = PJMEDIA_AFD_SPF(afd) /
stream->codec_param.info.channel_cnt;
if (info->fmt.pt == PJMEDIA_RTP_PT_G722) {
@@ -2212,7 +2293,7 @@
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 = stream->port.info.samples_per_frame;
+ 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 */
@@ -2423,6 +2504,12 @@
stream->codec = NULL;
}
+ if (stream->vcodec) {
+ stream->vcodec->op->close(stream->vcodec);
+ pjmedia_vid_codec_mgr_dealloc_codec(NULL, stream->vcodec);
+ stream->vcodec = NULL;
+ }
+
/* Free mutex */
if (stream->jb_mutex) {
@@ -2738,3 +2825,525 @@
return PJ_SUCCESS;
}
+static pj_status_t put_vid_frame(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 rtp_ts_len;
+ void *rtphdr;
+ int rtphdrlen;
+ unsigned processed = 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 * stream->port.info.clock_rate)
+ {
+ 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;
+ }
+
+ /* Increment transmit duration */
+ rtp_ts_len = stream->rtp_tx_ts_len_per_pkt;
+ stream->tx_duration += rtp_ts_len;
+
+ /* Init frame_out buffer. */
+ frame_out.buf = ((char*)channel->out_pkt) + sizeof(pjmedia_rtp_hdr);
+ frame_out.size = 0;
+
+ /* Encode! */
+ status = (*stream->vcodec->op->encode)(stream->vcodec, 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;
+ }
+
+
+ while (processed < frame_out.size) {
+ pj_uint8_t *payload, *rtp_pkt;
+ pj_size_t payload_len;
+
+ /* Generate RTP payload */
+ status = (*stream->vcodec->op->packetize)(
+ stream->vcodec,
+ (pj_uint8_t*)frame_out.buf,
+ frame_out.size,
+ &processed,
+ &payload, &payload_len);
+ if (status != PJ_SUCCESS) {
+ LOGERR_((stream->port.info.name.ptr,
+ "Codec pack() error", status));
+ return status;
+ }
+
+ /* Encapsulate. */
+ status = pjmedia_rtp_encode_rtp( &channel->rtp,
+ channel->pt,
+ (processed==frame_out.size?1:0),
+ payload_len,
+ rtp_ts_len,
+ (const void**)&rtphdr,
+ &rtphdrlen);
+
+ if (status != PJ_SUCCESS) {
+ LOGERR_((stream->port.info.name.ptr,
+ "RTP encode_rtp() error", status));
+ return status;
+ }
+
+ /* Next packets use same timestamp */
+ rtp_ts_len = 0;
+
+ rtp_pkt = payload - sizeof(pjmedia_rtp_hdr);
+
+ /* Copy RTP header to the beginning of packet */
+ pj_memcpy(rtp_pkt, rtphdr, sizeof(pjmedia_rtp_hdr));
+
+ /* Send the RTP packet to the transport. */
+ pjmedia_transport_send_rtp(stream->transport, rtp_pkt,
+ payload_len + sizeof(pjmedia_rtp_hdr));
+ }
+
+ /* 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;
+ }
+
+
+ /* Set RTP marker bit if currently not streaming */
+ if (stream->is_streaming == PJ_FALSE) {
+ // RTP header M in video packet is usually for end of frame flag.
+ //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;
+
+ /* 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;
+}
+
+static pj_status_t get_vid_frame(pjmedia_port *port,
+ pjmedia_frame *frame)
+{
+ pjmedia_stream *stream = (pjmedia_stream*) port->port_data.pdata;
+ pjmedia_channel *channel = stream->dec;
+ pj_uint8_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 );
+
+ p_out_samp = (pj_uint8_t*) frame->buf;
+ {
+ 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_NORMAL_FRAME) {
+ const char *with_plc = "";
+
+ /* Activate PLC */
+ if (stream->vcodec->op->recover &&
+ //stream->codec_param.setting.plc &&
+ stream->plc_cnt < stream->max_plc_cnt)
+ {
+ status = (*stream->codec->op->recover)(stream->codec,
+ frame->size,
+ frame);
+
+ ++stream->plc_cnt;
+ with_plc = ", plc invoked";
+ } 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);
+ frame->size = 0;
+ }
+
+ if (frame_type != stream->jb_last_frm) {
+ if (frame_type == PJMEDIA_JB_MISSING_FRAME) {
+ 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));
+ } else {
+ /* 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 {
+ /* 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_VIDEO; /* ignored */
+
+ frame_out.buf = p_out_samp;
+ frame_out.size = frame->size;
+ status = stream->vcodec->op->decode(stream->vcodec, &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);
+ frame_out.size = 0;
+ }
+
+ 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++;
+ }
+
+ frame->size = frame_out.size;
+ }
+ }
+
+
+ /* Unlock jitter buffer mutex. */
+ pj_mutex_unlock( stream->jb_mutex );
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t video_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, len;
+ pjmedia_video_format_detail *vfd_enc;
+ char *p;
+ pj_status_t status;
+
+ stream = PJ_POOL_ZALLOC_T(pool, pjmedia_stream);
+ PJ_ASSERT_RETURN(stream != NULL, PJ_ENOMEM);
+ PJ_ASSERT_RETURN(pjmedia_vid_codec_mgr_instance(), PJMEDIA_CODEC_EFAILED);
+
+ /* Init stream/port name */
+ name.ptr = (char*) pj_pool_alloc(pool, M);
+ name.slen = pj_ansi_snprintf(name.ptr, M, "vstrm%p", stream);
+
+ /* Create and initialize codec: */
+ stream->vcodec_info = info->vid_codec_info;
+ status = pjmedia_vid_codec_mgr_alloc_codec(NULL, &info->vid_codec_info,
+ &stream->vcodec);
+ if (status != PJ_SUCCESS)
+ return status;
+
+
+ /* Get codec param: */
+ if (info->vid_codec_param)
+ stream->vcodec_param = *info->vid_codec_param;
+ else {
+ status = pjmedia_vid_codec_mgr_get_default_param(NULL,
+ &info->vid_codec_info,
+ &stream->vcodec_param);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ vfd_enc = pjmedia_format_get_video_format_detail(
+ &stream->vcodec_param.enc_fmt, 1);
+
+ /* 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->vid_codec_info.clock_rate / 1000;
+
+ stream->jb_last_frm = PJMEDIA_JB_NORMAL_FRAME;
+ stream->tx_event_pt = -1;
+ stream->rx_event_pt = -1;
+
+#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;
+
+ /* 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;
+
+ /* Open the codec. */
+ stream->vcodec_param.dir = info->dir;
+ stream->vcodec_param.enc_mtu = PJMEDIA_MAX_MTU - 40;
+ status = stream->vcodec->op->init(stream->vcodec, pool);
+ if (status != PJ_SUCCESS)
+ return status;
+ status = stream->vcodec->op->open(stream->vcodec, &stream->vcodec_param);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Set additional info and callbacks. */
+ stream->port.put_frame = &put_vid_frame;
+ stream->port.get_frame = &get_vid_frame;
+
+ /* Get the frame size */
+ //stream->frame_size = vfd_enc->max_bps *
+ // vfd_enc->fps.denum / vfd_enc->fps.num;
+ stream->frame_size = 128000;
+
+ /* How many consecutive PLC frames can be generated */
+ stream->max_plc_cnt = MAX_PLC_MSEC *
+ vfd_enc->fps.num / vfd_enc->fps.denum / 1000;
+
+ stream->rtp_rx_check_cnt = 5;
+ stream->rtp_rx_last_ts = 0;
+ stream->rtp_rx_last_cnt = 0;
+ stream->rtp_tx_ts_len_per_pkt =
+ info->vid_codec_info.clock_rate *
+ vfd_enc->fps.denum / vfd_enc->fps.num;
+ stream->rtp_rx_ts_len_per_frame = stream->rtp_tx_ts_len_per_pkt;
+
+ /* Init jitter buffer parameters: */
+ jb_max = info->jb_max *
+ vfd_enc->fps.num / vfd_enc->fps.denum / 1000;
+ jb_min_pre = info->jb_min_pre *
+ vfd_enc->fps.num / vfd_enc->fps.denum / 1000;
+ jb_max_pre = info->jb_max_pre *
+ vfd_enc->fps.num / vfd_enc->fps.denum / 1000;
+ jb_init = info->jb_init *
+ vfd_enc->fps.num / vfd_enc->fps.denum / 1000;
+
+ /* When JB max frame count==0, set to 0.5s */
+ if (jb_max == 0)
+ jb_max = (vfd_enc->fps.num + vfd_enc->fps.denum/2) /
+ vfd_enc->fps.denum / 2;
+
+ /* When JB max prefetch==0, set to (4/5 * jb_max) */
+ if (jb_max_pre == 0)
+ jb_max_pre = jb_max * 4 / 5;
+
+ /* Create jitter buffer */
+ status = pjmedia_jbuf_create(pool, &stream->port.info.name,
+ stream->frame_size,
+ 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);
+
+ /* Create decoder channel: */
+
+ status = create_channel( pool, stream, PJMEDIA_DIR_DECODING,
+ stream->vcodec_param.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;
+
+
+ /* 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->vid_codec_info.clock_rate;
+ rtcp_setting.samples_per_frame = stream->rtp_tx_ts_len_per_pkt;
+
+ pjmedia_rtcp_init2(&stream->rtcp, &rtcp_setting);
+ }
+
+ /* 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 */
+ len = create_rtcp_sdes(stream, (pj_uint8_t*)stream->enc->out_pkt,
+ stream->enc->out_pkt_size);
+ if (len != 0) {
+ pjmedia_transport_send_rtcp(stream->transport,
+ stream->enc->out_pkt, len);
+ }
+
+#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_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
+
+ /* Init some port-info. Some parts of the info will be set later
+ * once we have more info about the codec.
+ */
+ pjmedia_port_info_init2(&stream->port.info, &name,
+ PJMEDIA_PORT_SIGNATURE('S', 'T', 'R', 'M'),
+ info->dir, &stream->vcodec_param.dec_fmt);
+
+ /* Init port. */
+ stream->port.port_data.pdata = stream;
+
+ /* Success! */
+ *p_stream = stream;
+
+ PJ_LOG(5,(THIS_FILE, "Stream %s created", stream->port.info.name.ptr));
+
+ return PJ_SUCCESS;
+}
diff --git a/pjmedia/src/pjmedia/tonegen.c b/pjmedia/src/pjmedia/tonegen.c
index 2a54505..17cc993 100644
--- a/pjmedia/src/pjmedia/tonegen.c
+++ b/pjmedia/src/pjmedia/tonegen.c
@@ -558,7 +558,7 @@
{
struct tonegen *tonegen = (struct tonegen*) port;
short *dst, *end;
- unsigned clock_rate = tonegen->base.info.clock_rate;
+ unsigned clock_rate = PJMEDIA_PIA_SRATE(&tonegen->base.info);
PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);
@@ -622,7 +622,7 @@
}
dst = (short*) frame->buf;
- end = dst + port->info.samples_per_frame;
+ end = dst + PJMEDIA_PIA_SPF(&port->info);
while (dst < end) {
pjmedia_tone_desc *dig = &tonegen->digits[tonegen->cur_digit];
@@ -636,7 +636,8 @@
if (tonegen->dig_samples == 0 &&
(tonegen->count!=1 || !(dig->flags & PJMEDIA_TONE_INITIALIZED)))
{
- init_generate_tone(&tonegen->state, port->info.clock_rate,
+ 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) {
@@ -651,7 +652,8 @@
cnt = on_samp - tonegen->dig_samples;
if (cnt > required)
cnt = required;
- generate_tone(&tonegen->state, port->info.channel_count,
+ generate_tone(&tonegen->state,
+ PJMEDIA_PIA_CCNT(&port->info),
cnt, dst);
dst += cnt;
@@ -729,7 +731,7 @@
pjmedia_zero_samples(dst, end-dst);
frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
- frame->size = port->info.bytes_per_frame;
+ 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)));
diff --git a/pjmedia/src/pjmedia/types.c b/pjmedia/src/pjmedia/types.c
new file mode 100644
index 0000000..09219e3
--- /dev/null
+++ b/pjmedia/src/pjmedia/types.c
@@ -0,0 +1,24 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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>
+#include <pj/pool.h>
+
diff --git a/pjmedia/src/pjmedia/vid_codec.c b/pjmedia/src/pjmedia/vid_codec.c
new file mode 100644
index 0000000..9eaca92
--- /dev/null
+++ b/pjmedia/src/pjmedia/vid_codec.c
@@ -0,0 +1,645 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 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>
+
+#define THIS_FILE "vid_codec.c"
+
+static pjmedia_vid_codec_mgr *def_vid_codec_mgr;
+
+/* Definition of default codecs parameters */
+struct pjmedia_vid_codec_default_param
+{
+ pjmedia_vid_codec_param *param;
+};
+
+
+/* Sort codecs in codec manager based on priorities */
+static void sort_codecs(pjmedia_vid_codec_mgr *mgr);
+
+
+/*
+ * 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);
+ 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 static 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 && pt>=0 && pt < 96, 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;
+}
+
+
+/*
+ * Convert codec info struct into a unique codec identifier.
+ * A codec identifier looks something like "L16/44100/2".
+ */
+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->clock_rate);
+
+ 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,
+ 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,
+ pj_pool_t *pool,
+ 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;
+ pjmedia_vid_codec_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) {
+ 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);
+ return PJ_SUCCESS;
+ }
+
+ p = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_codec_param);
+ p = codec_desc->def_param;
+
+ /* Update codec param */
+ pj_memcpy(p, param, sizeof(pjmedia_vid_codec_param));
+ for (i = 0; i < param->dec_fmtp.cnt; ++i) {
+ pj_strdup(pool, &p->dec_fmtp.param[i].name,
+ ¶m->dec_fmtp.param[i].name);
+ pj_strdup(pool, &p->dec_fmtp.param[i].val,
+ ¶m->dec_fmtp.param[i].val);
+ }
+ for (i = 0; i < param->dec_fmtp.cnt; ++i) {
+ pj_strdup(pool, &p->dec_fmtp.param[i].name,
+ ¶m->dec_fmtp.param[i].name);
+ pj_strdup(pool, &p->dec_fmtp.param[i].val,
+ ¶m->dec_fmtp.param[i].val);
+ }
+
+ pj_mutex_unlock(mgr->mutex);
+
+ 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);
+}
+
diff --git a/pjmedia/src/pjmedia/videoport.c b/pjmedia/src/pjmedia/videoport.c
new file mode 100644
index 0000000..0d10938
--- /dev/null
+++ b/pjmedia/src/pjmedia/videoport.c
@@ -0,0 +1,549 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2010 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/videoport.h>
+#include <pjmedia/clock.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+
+#define THIS_FILE "videoport.c"
+
+typedef struct vid_pasv_port vid_pasv_port;
+
+enum role
+{
+ ROLE_NONE,
+ ROLE_ACTIVE,
+ ROLE_PASSIVE
+};
+
+struct pjmedia_vid_port
+{
+ pj_str_t dev_name;
+ pjmedia_dir dir;
+ pjmedia_rect_size cap_size;
+ pjmedia_vid_stream *strm;
+ pjmedia_vid_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;
+
+ pjmedia_clock *enc_clock,
+ *dec_clock;
+
+ pjmedia_frame *enc_frm_buf,
+ *dec_frm_buf;
+
+ pj_mutex_t *enc_frm_mutex,
+ *dec_frm_mutex;
+};
+
+struct vid_pasv_port
+{
+ pjmedia_port base;
+ pjmedia_vid_port *vp;
+};
+
+static pj_status_t vidstream_cap_cb(pjmedia_vid_stream *stream,
+ void *user_data,
+ pjmedia_frame *frame);
+static pj_status_t vidstream_render_cb(pjmedia_vid_stream *stream,
+ void *user_data,
+ pjmedia_frame *frame);
+static pj_status_t vidstream_event_cb(pjmedia_vid_stream *stream,
+ void *user_data,
+ pjmedia_vid_event *event);
+
+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));
+}
+
+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)
+{
+ enum { USEC_IN_SEC = 1000000 };
+ pjmedia_vid_port *vp;
+ pjmedia_vid_dev_index dev_id = PJMEDIA_VID_INVALID_DEV;
+ pjmedia_vid_dev_info di;
+ const pjmedia_video_format_detail *vfd;
+ pjmedia_vid_cb vid_cb;
+ pj_bool_t need_frame_buf = PJ_FALSE;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pool && prm && p_vid_port, PJ_EINVAL);
+ PJ_ASSERT_RETURN(prm->vidparam.frame_rate.num, PJ_EINVAL);
+ PJ_ASSERT_RETURN(prm->vidparam.fmt.type == PJMEDIA_TYPE_VIDEO,
+ PJ_EINVAL);
+
+ /* Retrieve the video format detail */
+ vfd = pjmedia_format_get_video_format_detail(&prm->vidparam.fmt, PJ_TRUE);
+ if (!vfd)
+ return PJ_EINVAL;
+
+ /* Allocate videoport */
+ vp = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_port);
+ vp->role = prm->active ? ROLE_ACTIVE : ROLE_PASSIVE;
+ vp->dir = prm->vidparam.dir;
+ vp->cap_size = vfd->size;
+
+ /* Determine the device id to get the video device info */
+ if ((vp->dir & PJMEDIA_DIR_CAPTURE) &&
+ prm->vidparam.cap_id != PJMEDIA_VID_INVALID_DEV)
+ {
+ dev_id = prm->vidparam.cap_id;
+ } else if ((vp->dir & PJMEDIA_DIR_RENDER) &&
+ prm->vidparam.rend_id != PJMEDIA_VID_INVALID_DEV)
+ {
+ dev_id = prm->vidparam.rend_id;
+ } else
+ return PJ_EINVAL;
+
+ /* Get device info */
+ status = pjmedia_vid_dev_get_info(dev_id, &di);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ PJ_LOG(4,(THIS_FILE, "Opening %s..", di.name));
+
+ pj_strdup2_with_null(pool, &vp->dev_name, di.name);
+ vp->stream_role = di.has_callback ? ROLE_ACTIVE : ROLE_PASSIVE;
+
+ /* 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;
+ vid_cb.on_event_cb = &vidstream_event_cb;
+
+ status = pjmedia_vid_stream_create(&prm->vidparam, &vid_cb, vp,
+ &vp->strm);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ if (vp->role==ROLE_ACTIVE && vp->stream_role==ROLE_PASSIVE) {
+ /* Active role is wanted, but our device is passive, so create
+ * master clocks to run the media flow.
+ */
+ need_frame_buf = PJ_TRUE;
+
+ if (vp->dir & PJMEDIA_DIR_ENCODING) {
+ status = pjmedia_clock_create2(pool,
+ USEC_IN_SEC *
+ prm->vidparam.frame_rate.denum /
+ prm->vidparam.frame_rate.num,
+ prm->vidparam.clock_rate,
+ PJMEDIA_CLOCK_NO_HIGHEST_PRIO,
+ &enc_clock_cb, vp, &vp->enc_clock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ if (vp->dir & PJMEDIA_DIR_DECODING) {
+ status = pjmedia_clock_create2(pool,
+ USEC_IN_SEC *
+ prm->vidparam.frame_rate.denum /
+ prm->vidparam.frame_rate.num,
+ prm->vidparam.clock_rate,
+ PJMEDIA_CLOCK_NO_HIGHEST_PRIO,
+ &dec_clock_cb, vp, &vp->dec_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_PORT_SIGNATURE('v', 'i', 'd', 'p'),
+ 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, prm->vidparam.fmt.id);
+ if (!vfi) {
+ status = PJ_ENOTFOUND;
+ goto on_error;
+ }
+
+ pj_bzero(&vafp, sizeof(vafp));
+ vafp.size = vfd->size;
+ status = vfi->apply_fmt(vfi, &vafp);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ if (vp->dir & PJMEDIA_DIR_ENCODING) {
+ vp->enc_frm_buf = PJ_POOL_ZALLOC_T(pool, pjmedia_frame);
+ vp->enc_frm_buf->buf = pj_pool_alloc(pool, vafp.framebytes);
+ vp->enc_frm_buf->size = vafp.framebytes;
+ vp->enc_frm_buf->type = PJMEDIA_FRAME_TYPE_NONE;
+
+ status = pj_mutex_create_simple(pool, vp->dev_name.ptr,
+ &vp->enc_frm_mutex);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ if (vp->dir & PJMEDIA_DIR_DECODING) {
+ vp->dec_frm_buf = PJ_POOL_ZALLOC_T(pool, pjmedia_frame);
+ vp->dec_frm_buf->buf = pj_pool_alloc(pool, vafp.framebytes);
+ vp->dec_frm_buf->size = vafp.framebytes;
+ vp->dec_frm_buf->type = PJMEDIA_FRAME_TYPE_NONE;
+
+ status = pj_mutex_create_simple(pool, vp->dev_name.ptr,
+ &vp->dec_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_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_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(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;
+ 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);
+ 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_stream_start(vp->strm);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ if (vp->enc_clock) {
+ status = pjmedia_clock_start(vp->enc_clock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ if (vp->dec_clock) {
+ status = pjmedia_clock_start(vp->dec_clock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ return PJ_SUCCESS;
+
+on_error:
+ pjmedia_vid_port_stop(vp);
+ return status;
+}
+
+PJ_DEF(pj_status_t) pjmedia_vid_port_stop(pjmedia_vid_port *vp)
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(vp, PJ_EINVAL);
+
+ status = pjmedia_vid_stream_stop(vp->strm);
+
+ if (vp->enc_clock) {
+ status = pjmedia_clock_stop(vp->enc_clock);
+ }
+
+ if (vp->dec_clock) {
+ status = pjmedia_clock_stop(vp->dec_clock);
+ }
+
+ 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->enc_clock) {
+ pjmedia_clock_destroy(vp->enc_clock);
+ vp->enc_clock = NULL;
+ }
+ if (vp->dec_clock) {
+ pjmedia_clock_destroy(vp->dec_clock);
+ vp->dec_clock = NULL;
+ }
+ if (vp->strm) {
+ pjmedia_vid_stream_destroy(vp->strm);
+ vp->strm = NULL;
+ }
+ if (vp->client_port) {
+ if (vp->destroy_client_port)
+ pjmedia_port_destroy(vp->client_port);
+ vp->client_port = NULL;
+ }
+ if (vp->enc_frm_mutex) {
+ pj_mutex_destroy(vp->enc_frm_mutex);
+ vp->enc_frm_mutex = NULL;
+ }
+ if (vp->dec_frm_mutex) {
+ pj_mutex_destroy(vp->dec_frm_mutex);
+ vp->dec_frm_mutex = NULL;
+ }
+
+}
+
+/*
+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);
+}
+*/
+
+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;
+ pj_status_t status;
+
+ pj_assert(vp->role==ROLE_ACTIVE && vp->stream_role==ROLE_PASSIVE);
+
+ PJ_UNUSED_ARG(ts);
+
+ if (!vp->client_port)
+ return;
+
+ status = pjmedia_vid_stream_get_frame(vp->strm, vp->enc_frm_buf);
+ if (status != PJ_SUCCESS)
+ return;
+
+ //save_rgb_frame(vp->cap_size.w, vp->cap_size.h, vp->enc_frm_buf);
+
+ status = pjmedia_port_put_frame(vp->client_port, vp->enc_frm_buf);
+}
+
+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;
+
+ pj_assert(vp->role==ROLE_ACTIVE && vp->stream_role==ROLE_PASSIVE);
+
+ PJ_UNUSED_ARG(ts);
+
+ if (!vp->client_port)
+ return;
+
+ status = pjmedia_port_get_frame(vp->client_port, vp->dec_frm_buf);
+ if (status != PJ_SUCCESS)
+ return;
+
+ status = pjmedia_vid_stream_put_frame(vp->strm, vp->dec_frm_buf);
+}
+
+static void copy_frame(pjmedia_frame *dst, const pjmedia_frame *src)
+{
+ PJ_ASSERT_ON_FAIL(dst->size >= src->size, return);
+
+ pj_memcpy(dst, src, sizeof(*src));
+ pj_memcpy(dst->buf, src->buf, src->size);
+ dst->size = src->size;
+}
+
+static pj_status_t vidstream_cap_cb(pjmedia_vid_stream *stream,
+ void *user_data,
+ pjmedia_frame *frame)
+{
+ pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
+
+ if (vp->role==ROLE_ACTIVE) {
+ if (vp->client_port)
+ return pjmedia_port_put_frame(vp->client_port, frame);
+ } else {
+ pj_mutex_lock(vp->enc_frm_mutex);
+ copy_frame(vp->enc_frm_buf, frame);
+ pj_mutex_unlock(vp->enc_frm_mutex);
+ }
+ 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_stream *stream,
+ void *user_data,
+ pjmedia_frame *frame)
+{
+ pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
+
+ if (vp->role==ROLE_ACTIVE) {
+ if (vp->client_port)
+ return pjmedia_port_get_frame(vp->client_port, frame);
+ } else {
+ pj_mutex_lock(vp->dec_frm_mutex);
+ copy_frame(frame, vp->dec_frm_buf);
+ pj_mutex_unlock(vp->dec_frm_mutex);
+ }
+ 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 vidstream_event_cb(pjmedia_vid_stream *stream,
+ void *user_data,
+ pjmedia_vid_event *event)
+{
+ pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
+
+ if (vp->strm_cb.on_event_cb)
+ return (*vp->strm_cb.on_event_cb)(stream, vp->strm_cb_data, event);
+ 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) {
+ return pjmedia_vid_stream_put_frame(vp->strm, frame);
+ } else {
+ pj_mutex_lock(vp->dec_frm_mutex);
+ copy_frame(vp->dec_frm_buf, frame);
+ pj_mutex_unlock(vp->dec_frm_mutex);
+ }
+
+ 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;
+
+ if (vp->stream_role==ROLE_PASSIVE) {
+ return pjmedia_vid_stream_get_frame(vp->strm, frame);
+ } else {
+ pj_mutex_lock(vp->enc_frm_mutex);
+ copy_frame(frame, vp->enc_frm_buf);
+ pj_mutex_unlock(vp->enc_frm_mutex);
+ }
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjmedia/src/pjmedia/wav_player.c b/pjmedia/src/pjmedia/wav_player.c
index e0279b2..0504b69 100644
--- a/pjmedia/src/pjmedia/wav_player.c
+++ b/pjmedia/src/pjmedia/wav_player.c
@@ -168,7 +168,10 @@
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;
@@ -331,17 +334,15 @@
fport->options = options;
/* Update port info. */
- fport->base.info.channel_count = wave_hdr.fmt_hdr.nchan;
- fport->base.info.clock_rate = wave_hdr.fmt_hdr.sample_rate;
- fport->base.info.bits_per_sample = BITS_PER_SAMPLE;
- fport->base.info.samples_per_frame = fport->base.info.clock_rate *
- wave_hdr.fmt_hdr.nchan *
- ptime / 1000;
- fport->base.info.bytes_per_frame =
- fport->base.info.samples_per_frame *
- fport->base.info.bits_per_sample / 8;
-
- pj_strdup2(pool, &fport->base.info.name, filename);
+ 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
@@ -358,9 +359,7 @@
/* samples_per_frame must be smaller than bufsize (because get_frame()
* doesn't handle this case).
*/
- if (fport->base.info.samples_per_frame * fport->bytes_per_sample >=
- fport->bufsize)
- {
+ if (samples_per_frame * fport->bytes_per_sample >= fport->bufsize) {
pj_file_close(fport->fd);
return PJ_EINVAL;
}
@@ -394,8 +393,8 @@
"filesize=%luKB",
(int)fport->base.info.name.slen,
fport->base.info.name.ptr,
- fport->base.info.clock_rate,
- fport->base.info.channel_count,
+ ad->clock_rate,
+ ad->channel_count,
fport->bufsize / 1000,
(unsigned long)(fport->fsize / 1000)));
diff --git a/pjmedia/src/pjmedia/wav_playlist.c b/pjmedia/src/pjmedia/wav_playlist.c
index 33db0df..b8cfbb8 100644
--- a/pjmedia/src/pjmedia/wav_playlist.c
+++ b/pjmedia/src/pjmedia/wav_playlist.c
@@ -236,6 +236,7 @@
pjmedia_port **p_port)
{
struct playlist_port *fport;
+ pjmedia_audio_format_detail *afd;
pj_off_t pos;
pj_status_t status;
int index;
@@ -280,6 +281,8 @@
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;
@@ -466,15 +469,13 @@
* that the WAV file has the same attributes as previous files.
*/
if (!has_wave_info) {
- fport->base.info.channel_count = wavehdr.fmt_hdr.nchan;
- fport->base.info.clock_rate = wavehdr.fmt_hdr.sample_rate;
- fport->base.info.bits_per_sample = wavehdr.fmt_hdr.bits_per_sample;
- fport->base.info.samples_per_frame = fport->base.info.clock_rate *
- wavehdr.fmt_hdr.nchan *
- ptime / 1000;
- fport->base.info.bytes_per_frame =
- fport->base.info.samples_per_frame *
- fport->base.info.bits_per_sample / 8;
+ 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 / 8;
has_wave_info = PJ_TRUE;
@@ -483,9 +484,9 @@
/* Check that this file has the same characteristics as the other
* files.
*/
- if (wavehdr.fmt_hdr.nchan != fport->base.info.channel_count ||
- wavehdr.fmt_hdr.sample_rate != fport->base.info.clock_rate ||
- wavehdr.fmt_hdr.bits_per_sample != fport->base.info.bits_per_sample)
+ 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.
@@ -519,8 +520,8 @@
"WAV playlist '%.*s' created: samp.rate=%d, ch=%d, bufsize=%uKB",
(int)port_label->slen,
port_label->ptr,
- fport->base.info.clock_rate,
- fport->base.info.channel_count,
+ afd->clock_rate,
+ afd->channel_count,
fport->bufsize / 1000));
return PJ_SUCCESS;
diff --git a/pjmedia/src/pjmedia/wav_writer.c b/pjmedia/src/pjmedia/wav_writer.c
index a24fbbd..6c749c8 100644
--- a/pjmedia/src/pjmedia/wav_writer.c
+++ b/pjmedia/src/pjmedia/wav_writer.c
@@ -51,7 +51,7 @@
};
static pj_status_t file_put_frame(pjmedia_port *this_port,
- const pjmedia_frame *frame);
+ 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);
@@ -198,7 +198,7 @@
fport->bufsize = buff_size;
/* Check that buffer size is greater than bytes per frame */
- pj_assert(fport->bufsize >= fport->base.info.bytes_per_frame);
+ pj_assert(fport->bufsize >= PJMEDIA_PIA_AVG_FSZ(&fport->base.info));
/* Allocate buffer and set initial write position */
@@ -216,7 +216,7 @@
"File writer '%.*s' created: samp.rate=%d, bufsize=%uKB",
(int)fport->base.info.name.slen,
fport->base.info.name.ptr,
- fport->base.info.clock_rate,
+ PJMEDIA_PIA_SRATE(&fport->base.info),
fport->bufsize / 1000));
@@ -308,7 +308,7 @@
* to the file.
*/
static pj_status_t file_put_frame(pjmedia_port *this_port,
- const pjmedia_frame *frame)
+ pjmedia_frame *frame)
{
struct file_port *fport = (struct file_port *)this_port;
unsigned frame_size;
diff --git a/pjmedia/src/test/mips_test.c b/pjmedia/src/test/mips_test.c
index 392c634..d315606 100644
--- a/pjmedia/src/test/mips_test.c
+++ b/pjmedia/src/test/mips_test.c
@@ -684,7 +684,7 @@
static pj_status_t codec_put_frame(struct pjmedia_port *this_port,
- const pjmedia_frame *frame)
+ pjmedia_frame *frame)
{
struct codec_port *cp = (struct codec_port*)this_port;
pjmedia_frame out_frame;
@@ -1131,13 +1131,13 @@
pj_status_t status;
while (pjmedia_circ_buf_get_len(wp->circbuf) <
- wp->base.info.samples_per_frame * (CIRC_BUF_FRAME_CNT-1))
+ 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,
- wp->base.info.samples_per_frame);
+ PJMEDIA_PIA_SPF(&wp->base.info));
pj_assert(status==PJ_SUCCESS);
}
@@ -1149,7 +1149,7 @@
pjmedia_circ_buf_get_read_regions(wp->circbuf, ®1, ®1_len,
®2, ®2_len);
- del_cnt = wp->base.info.samples_per_frame;
+ 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);
@@ -2010,7 +2010,7 @@
}
static pj_status_t delaybuf_put_frame(struct pjmedia_port *this_port,
- const pjmedia_frame *frame)
+ pjmedia_frame *frame)
{
struct delaybuf_port *dp = (struct delaybuf_port*)this_port;
pj_status_t status;
@@ -2219,7 +2219,7 @@
}
/* Port may decide to use different ptime (e.g. iLBC) */
- samples_per_frame = port->info.samples_per_frame;
+ samples_per_frame = PJMEDIA_PIA_SPF(&port->info);
gen_port = create_gen_port(pool, clock_rate, 1,
samples_per_frame, 100);
diff --git a/pjmedia/src/test/test.c b/pjmedia/src/test/test.c
index 385eee1..bd4e406 100644
--- a/pjmedia/src/test/test.c
+++ b/pjmedia/src/test/test.c
@@ -56,6 +56,14 @@
mem = &caching_pool.factory;
+#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
diff --git a/pjmedia/src/test/test.h b/pjmedia/src/test/test.h
index 96dc9d5..085f4c0 100644
--- a/pjmedia/src/test/test.h
+++ b/pjmedia/src/test/test.h
@@ -23,6 +23,8 @@
#include <pjmedia.h>
#include <pjlib.h>
+#define HAS_VID_DEV_TEST 1
+#define HAS_VID_CODEC_TEST 1
#define HAS_SDP_NEG_TEST 1
#define HAS_JBUF_TEST 1
#define HAS_MIPS_TEST 1
@@ -35,6 +37,8 @@
int sdp_neg_test(void);
int mips_test(void);
int codec_test_vectors(void);
+int vid_codec_test(void);
+int vid_dev_test(void);
extern pj_pool_factory *mem;
void app_perror(pj_status_t status, const char *title);
diff --git a/pjmedia/src/test/vid_codec_test.c b/pjmedia/src/test/vid_codec_test.c
new file mode 100644
index 0000000..d32e176
--- /dev/null
+++ b/pjmedia/src/test/vid_codec_test.c
@@ -0,0 +1,359 @@
+#include "test.h"
+#include <pjmedia-codec/ffmpeg_codecs.h>
+#include <pjmedia-videodev/videodev.h>
+#include <pjmedia/vid_codec.h>
+#include <pjmedia/port.h>
+
+#define THIS_FILE "vid_codec.c"
+
+#define BYPASS_CODEC 0
+
+typedef struct codec_port_data_t
+{
+ pjmedia_vid_codec *codec;
+ pjmedia_port *dn_port;
+ pj_uint8_t *enc_buf;
+ pj_size_t enc_buf_size;
+} codec_port_data_t;
+
+static pj_status_t codec_put_frame(pjmedia_port *port,
+ pjmedia_frame *frame)
+{
+ codec_port_data_t *port_data = (codec_port_data_t*)port->port_data.pdata;
+ pjmedia_vid_codec *codec = port_data->codec;
+ pjmedia_frame enc_frame;
+ pj_status_t status;
+
+ enc_frame.buf = port_data->enc_buf;
+ enc_frame.size = port_data->enc_buf_size;
+
+#if !BYPASS_CODEC
+ status = codec->op->encode(codec, frame, enc_frame.size, &enc_frame);
+ if (status != PJ_SUCCESS) goto on_error;
+ status = codec->op->decode(codec, &enc_frame, frame->size, frame);
+ if (status != PJ_SUCCESS) goto on_error;
+#endif
+
+ status = pjmedia_port_put_frame(port_data->dn_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_format_id raw_fmt_id)
+{
+
+ 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;
+
+ 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;
+ pj_status_t status;
+ int rc = 0;
+
+ PJ_LOG(3, (THIS_FILE, " encode decode test"));
+
+ /* 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;
+ }
+ }
+
+ /* Lookup colorbar source */
+ status = pjmedia_vid_dev_lookup("Colorbar", "Colorbar generator", &cap_idx);
+ if (status != PJ_SUCCESS) {
+ rc = 206; goto on_return;
+ }
+
+ /* Lookup SDL renderer */
+ status = pjmedia_vid_dev_lookup("SDL", "SDL renderer", &rdr_idx);
+ if (status != PJ_SUCCESS) {
+ rc = 207; goto on_return;
+ }
+
+ /* Raw format ID "not specified", lets find common format among the codec
+ * and the video devices
+ */
+ if (raw_fmt_id == 0) {
+ pjmedia_vid_dev_info cap_info, rdr_info;
+ unsigned i, j, k;
+
+ pjmedia_vid_dev_get_info(cap_idx, &cap_info);
+ pjmedia_vid_dev_get_info(rdr_idx, &rdr_info);
+
+ for (i=0; i<codec_info->dec_fmt_id_cnt && !raw_fmt_id; ++i) {
+ for (j=0; j<cap_info.fmt_cnt && !raw_fmt_id; ++j) {
+ if (codec_info->dec_fmt_id[i]==(int)cap_info.fmt[j].id) {
+ for (k=0; k<rdr_info.fmt_cnt && !raw_fmt_id; ++k) {
+ if (codec_info->dec_fmt_id[i]==(int)rdr_info.fmt[k].id)
+ {
+ raw_fmt_id = codec_info->dec_fmt_id[i];
+ }
+ }
+ }
+ }
+ }
+
+ if (raw_fmt_id == 0) {
+ PJ_LOG(3, (THIS_FILE, " No common format ID among the codec "
+ "and the video devices"));
+ status = PJ_ENOTFOUND;
+ rc = 210;
+ goto on_return;
+ }
+ }
+
+ 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;
+ }
+ vport_param.vidparam.fmt.id = raw_fmt_id;
+ 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;
+ }
+
+ /* Prepare codec */
+ {
+ pj_str_t codec_id_st;
+ unsigned info_cnt = 1;
+ const pjmedia_vid_codec_info *codec_info;
+ pj_str_t port_name = {"codec", 5};
+ pj_uint8_t *enc_buf = NULL;
+ pj_size_t enc_buf_size = 0;
+
+
+ /* 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;
+ }
+
+ pjmedia_format_copy(&codec_param.dec_fmt, &vport_param.vidparam.fmt);
+
+#if !BYPASS_CODEC
+
+ /* Open codec */
+ status = pjmedia_vid_codec_mgr_alloc_codec(NULL, codec_info,
+ &codec);
+ if (status != PJ_SUCCESS) {
+ rc = 250; goto on_return;
+ }
+
+ status = codec->op->init(codec, pool);
+ if (status != PJ_SUCCESS) {
+ rc = 251; goto on_return;
+ }
+
+ status = codec->op->open(codec, &codec_param);
+ if (status != PJ_SUCCESS) {
+ rc = 252; goto on_return;
+ }
+
+ /* Alloc encoding buffer */
+ enc_buf_size = codec_param.dec_fmt.det.vid.size.w *
+ codec_param.dec_fmt.det.vid.size.h * 4
+ + 16; /*< padding, just in case */
+ enc_buf = pj_pool_alloc(pool,enc_buf_size);
+
+#endif /* !BYPASS_CODEC */
+
+ /* 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.dn_port = pjmedia_vid_port_get_passive_port(renderer);
+ codec_port_data.enc_buf = enc_buf;
+ codec_port_data.enc_buf_size = enc_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: %c%c%c%c<->%s %dx%d",
+ ((codec_param.dec_fmt.id & 0x000000FF) >> 0),
+ ((codec_param.dec_fmt.id & 0x0000FF00) >> 8),
+ ((codec_param.dec_fmt.id & 0x00FF0000) >> 16),
+ ((codec_param.dec_fmt.id & 0xFF000000) >> 24),
+ codec_id,
+ 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);
+ pjmedia_vid_port_destroy(capture);
+ }
+ if (renderer) {
+ pjmedia_vid_port_stop(renderer);
+ pjmedia_vid_port_destroy(renderer);
+ }
+ if (codec) {
+ codec->op->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;
+
+ PJ_LOG(3, (THIS_FILE, "Performing video codec tests.."));
+
+ pool = pj_pool_create(mem, "Vid codec test", 256, 256, 0);
+
+ status = pjmedia_vid_subsys_init(mem);
+ if (status != PJ_SUCCESS)
+ return -10;
+
+ status = pjmedia_codec_ffmpeg_init(NULL, mem);
+ if (status != PJ_SUCCESS)
+ return -20;
+
+ rc = enum_codecs();
+ if (rc != 0)
+ goto on_return;
+
+ rc = encode_decode_test(pool, "mjpeg", 0);
+ if (rc != 0)
+ goto on_return;
+
+on_return:
+ pjmedia_codec_ffmpeg_deinit();
+ pjmedia_vid_subsys_shutdown();
+ pj_pool_release(pool);
+
+ return rc;
+}
+
+
diff --git a/pjmedia/src/test/vid_dev_test.c b/pjmedia/src/test/vid_dev_test.c
new file mode 100644
index 0000000..007ef3b
--- /dev/null
+++ b/pjmedia/src/test/vid_dev_test.c
@@ -0,0 +1,529 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 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_codecs.h>
+#include <pjmedia/vid_codec.h>
+#include <pjmedia_videodev.h>
+
+#define THIS_FILE "vid_dev_test.c"
+
+pj_status_t pjmedia_libswscale_converter_init(pjmedia_converter_mgr *mgr,
+ pj_pool_t *pool);
+
+typedef struct codec_port_data_t
+{
+ pjmedia_vid_codec *codec;
+ pjmedia_port *src_port;
+ pj_uint8_t *enc_buf;
+ pj_size_t enc_buf_size;
+
+ pjmedia_converter *conv;
+} codec_port_data_t;
+
+typedef struct avi_port_t
+{
+ pjmedia_vid_port *vid_port;
+ pjmedia_aud_stream *aud_stream;
+ pj_bool_t is_running;
+} avi_port_t;
+
+static int enum_devs(void)
+{
+ unsigned i, dev_cnt;
+ pj_status_t status;
+
+ PJ_LOG(3, (THIS_FILE, " device enums"));
+ dev_cnt = pjmedia_vid_dev_count();
+ for (i = 0; i < dev_cnt; ++i) {
+ pjmedia_vid_dev_info info;
+ status = pjmedia_vid_dev_get_info(i, &info);
+ if (status == PJ_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "%3d: %s - %s", i, info.driver, info.name));
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t avi_play_cb(void *user_data, pjmedia_frame *frame)
+{
+ return pjmedia_port_get_frame((pjmedia_port*)user_data, frame);
+}
+
+static pj_status_t avi_event_cb(pjmedia_vid_stream *stream,
+ void *user_data,
+ pjmedia_vid_event *event)
+{
+ avi_port_t *ap = (avi_port_t *)user_data;
+
+ PJ_UNUSED_ARG(stream);
+
+ if (event->event_type != PJMEDIA_EVENT_MOUSEBUTTONDOWN)
+ return PJ_SUCCESS;
+
+ if (ap->is_running) {
+ pjmedia_vid_port_stop(ap->vid_port);
+ if (ap->aud_stream)
+ pjmedia_aud_stream_stop(ap->aud_stream);
+ } else {
+ pjmedia_vid_port_start(ap->vid_port);
+ if (ap->aud_stream)
+ pjmedia_aud_stream_start(ap->aud_stream);
+ }
+ ap->is_running = !ap->is_running;
+
+ /* We handled the event on our own, so return non-PJ_SUCCESS here */
+ return -1;
+}
+
+static pj_status_t codec_get_frame(pjmedia_port *port,
+ pjmedia_frame *frame)
+{
+ codec_port_data_t *port_data = (codec_port_data_t*)port->port_data.pdata;
+ pjmedia_vid_codec *codec = port_data->codec;
+ pjmedia_frame enc_frame;
+ pj_status_t status;
+
+ enc_frame.buf = port_data->enc_buf;
+ enc_frame.size = port_data->enc_buf_size;
+
+ if (port_data->conv) {
+ pj_size_t frame_size = frame->size;
+
+ status = pjmedia_port_get_frame(port_data->src_port, frame);
+ if (status != PJ_SUCCESS) goto on_error;
+
+ status = codec->op->decode(codec, frame, frame->size, &enc_frame);
+ if (status != PJ_SUCCESS) goto on_error;
+
+ frame->size = frame_size;
+ status = pjmedia_converter_convert(port_data->conv, &enc_frame, frame);
+ if (status != PJ_SUCCESS) goto on_error;
+
+ return PJ_SUCCESS;
+ }
+
+ status = pjmedia_port_get_frame(port_data->src_port, &enc_frame);
+ if (status != PJ_SUCCESS) goto on_error;
+
+ status = codec->op->decode(codec, &enc_frame, frame->size, frame);
+ if (status != PJ_SUCCESS) goto on_error;
+
+ return PJ_SUCCESS;
+
+on_error:
+ pj_perror(3, THIS_FILE, status, "codec_get_frame() error");
+ return status;
+}
+
+static int aviplay_test(pj_pool_t *pool)
+{
+ pjmedia_vid_port *renderer=NULL;
+ pjmedia_vid_port_param param;
+ pjmedia_aud_param aparam;
+ pjmedia_video_format_detail *vfd;
+ pjmedia_audio_format_detail *afd;
+ pjmedia_aud_stream *strm = NULL;
+ pj_status_t status;
+ int rc = 0;
+ pjmedia_avi_streams *avi_streams;
+ pjmedia_avi_stream *vid_stream, *aud_stream;
+ pjmedia_port *vid_port = NULL, *aud_port = NULL;
+ pjmedia_vid_codec *codec=NULL;
+ avi_port_t avi_port;
+#if PJ_WIN32
+ const char *fname = "C:\\Users\\Liong Sauw Ming\\Desktop\\piratesmjpg.avi";
+#else
+ const char *fname = "/home/bennylp/Desktop/installer/video/movies/pirates.avi";
+#endif
+
+ pj_bzero(&avi_port, sizeof(avi_port));
+
+ status = pjmedia_avi_player_create_streams(pool, fname, 0, &avi_streams);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(2,("", status, " Error playing %s (ignored)", fname));
+ rc = 210; goto on_return;
+ }
+
+ vid_stream = pjmedia_avi_streams_get_stream_by_media(avi_streams,
+ 0,
+ PJMEDIA_TYPE_VIDEO);
+ vid_port = pjmedia_avi_stream_get_port(vid_stream);
+
+ if (vid_port) {
+ pjmedia_vid_port_param_default(¶m);
+
+ status = pjmedia_vid_dev_default_param(pool,
+ PJMEDIA_VID_DEFAULT_RENDER_DEV,
+ ¶m.vidparam);
+ if (status != PJ_SUCCESS) {
+ rc = 220; goto on_return;
+ }
+
+ /* Create renderer, set it to active */
+ param.active = PJ_TRUE;
+ param.vidparam.dir = PJMEDIA_DIR_RENDER;
+ vfd = pjmedia_format_get_video_format_detail(&vid_port->info.fmt,
+ PJ_TRUE);
+ pjmedia_format_init_video(¶m.vidparam.fmt,
+ vid_port->info.fmt.id,
+ vfd->size.w, vfd->size.h,
+ vfd->fps.num, vfd->fps.denum);
+
+ if (vid_port->info.fmt.id == PJMEDIA_FORMAT_MJPEG ||
+ vid_port->info.fmt.id == PJMEDIA_FORMAT_H263)
+ {
+ /* Prepare codec */
+ pj_str_t codec_id_st;
+ unsigned info_cnt = 1, i, k;
+ const pjmedia_vid_codec_info *codec_info;
+ pj_str_t port_name = {"codec", 5};
+ pj_uint8_t *enc_buf = NULL;
+ pj_size_t enc_buf_size = 0;
+ pjmedia_vid_dev_info rdr_info;
+ pjmedia_port codec_port;
+ codec_port_data_t codec_port_data;
+ pjmedia_vid_codec_param codec_param;
+ struct {
+ pj_uint32_t pjmedia_id;
+ const char *codec_id;
+ } codec_fmts[] =
+ {{PJMEDIA_FORMAT_MJPEG, "mjpeg"},
+ {PJMEDIA_FORMAT_H263, "h263"}};
+ const char *codec_id = NULL;
+
+
+ status = pjmedia_codec_ffmpeg_init(NULL, mem);
+ if (status != PJ_SUCCESS)
+ return -20;
+
+ /* Lookup codec */
+ for (i = 0; i < sizeof(codec_fmts)/sizeof(codec_fmts[0]); i++) {
+ if (vid_port->info.fmt.id == codec_fmts[i].pjmedia_id) {
+ codec_id = codec_fmts[i].codec_id;
+ break;
+ }
+ }
+ if (!codec_id) {
+ rc = 242; goto on_return;
+ }
+ 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;
+ }
+
+ pjmedia_vid_dev_get_info(param.vidparam.rend_id, &rdr_info);
+ for (i=0; i<codec_info->dec_fmt_id_cnt; ++i) {
+ for (k=0; k<rdr_info.fmt_cnt; ++k) {
+ if (codec_info->dec_fmt_id[i]==(int)rdr_info.fmt[k].id)
+ {
+ param.vidparam.fmt.id = codec_info->dec_fmt_id[i];
+ }
+ }
+ }
+
+ /* Open codec */
+ status = pjmedia_vid_codec_mgr_alloc_codec(NULL, codec_info,
+ &codec);
+ if (status != PJ_SUCCESS) {
+ rc = 250; goto on_return;
+ }
+
+ status = codec->op->init(codec, pool);
+ if (status != PJ_SUCCESS) {
+ rc = 251; goto on_return;
+ }
+
+ pjmedia_format_copy(&codec_param.dec_fmt, ¶m.vidparam.fmt);
+
+ status = codec->op->open(codec, &codec_param);
+ if (status != PJ_SUCCESS) {
+ rc = 252; goto on_return;
+ }
+
+ /* Alloc encoding buffer */
+ enc_buf_size = codec_param.dec_fmt.det.vid.size.w *
+ codec_param.dec_fmt.det.vid.size.h * 4
+ + 16; /*< padding, just in case */
+ enc_buf = pj_pool_alloc(pool,enc_buf_size);
+
+ /* 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;
+ }
+ pj_bzero(&codec_port_data, sizeof(codec_port_data));
+ codec_port_data.codec = codec;
+ codec_port_data.src_port = vid_port;
+ codec_port_data.enc_buf = enc_buf;
+ codec_port_data.enc_buf_size = enc_buf_size;
+
+ codec_port.get_frame = &codec_get_frame;
+ codec_port.port_data.pdata = &codec_port_data;
+
+ if (vid_port->info.fmt.id == PJMEDIA_FORMAT_MJPEG) {
+ pjmedia_conversion_param conv_param;
+
+ status = pjmedia_libswscale_converter_init(NULL, pool);
+
+ pjmedia_format_copy(&conv_param.src, ¶m.vidparam.fmt);
+ pjmedia_format_copy(&conv_param.dst, ¶m.vidparam.fmt);
+ conv_param.dst.id = PJMEDIA_FORMAT_I420;
+ param.vidparam.fmt.id = conv_param.dst.id;
+
+ status = pjmedia_converter_create(NULL, pool, &conv_param,
+ &codec_port_data.conv);
+ if (status != PJ_SUCCESS) {
+ rc = 270; goto on_return;
+ }
+ }
+
+ status = pjmedia_vid_port_create(pool, ¶m, &renderer);
+ if (status != PJ_SUCCESS) {
+ rc = 230; goto on_return;
+ }
+
+ status = pjmedia_vid_port_connect(renderer, &codec_port,
+ PJ_FALSE);
+ } else {
+ status = pjmedia_vid_port_create(pool, ¶m, &renderer);
+ if (status != PJ_SUCCESS) {
+ rc = 230; goto on_return;
+ }
+
+ /* Connect avi port to renderer */
+ status = pjmedia_vid_port_connect(renderer, vid_port,
+ PJ_FALSE);
+ }
+
+ if (status != PJ_SUCCESS) {
+ rc = 240; goto on_return;
+ }
+ }
+
+ aud_stream = pjmedia_avi_streams_get_stream_by_media(avi_streams,
+ 0,
+ PJMEDIA_TYPE_AUDIO);
+ aud_port = pjmedia_avi_stream_get_port(aud_stream);
+
+ if (aud_port) {
+ status = pjmedia_aud_dev_default_param(
+ PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV,
+ &aparam);
+ if (status != PJ_SUCCESS) {
+ rc = 310; goto on_return;
+ }
+
+ aparam.dir = PJMEDIA_DIR_PLAYBACK;
+ afd = pjmedia_format_get_audio_format_detail(&aud_port->info.fmt,
+ PJ_TRUE);
+ aparam.clock_rate = afd->clock_rate;
+ aparam.channel_count = afd->channel_count;
+ aparam.bits_per_sample = afd->bits_per_sample;
+ aparam.samples_per_frame = afd->frame_time_usec * aparam.clock_rate *
+ aparam.channel_count / 1000000;
+
+ status = pjmedia_aud_stream_create(&aparam, NULL, &avi_play_cb,
+ aud_port,
+ &strm);
+ if (status != PJ_SUCCESS) {
+ rc = 320; goto on_return;
+ }
+
+ /* Start audio streaming.. */
+ status = pjmedia_aud_stream_start(strm);
+ if (status != PJ_SUCCESS) {
+ rc = 330; goto on_return;
+ }
+ }
+
+ if (vid_port) {
+ pjmedia_vid_cb cb;
+
+ pj_bzero(&cb, sizeof(cb));
+ cb.on_event_cb = avi_event_cb;
+ avi_port.aud_stream = strm;
+ avi_port.vid_port = renderer;
+ avi_port.is_running = PJ_TRUE;
+ pjmedia_vid_port_set_cb(renderer, &cb, &avi_port);
+
+ /* Start video streaming.. */
+ status = pjmedia_vid_port_start(renderer);
+ if (status != PJ_SUCCESS) {
+ rc = 270; goto on_return;
+ }
+ }
+
+ pj_thread_sleep(150000);
+
+on_return:
+ if (strm) {
+ pjmedia_aud_stream_stop(strm);
+ pjmedia_aud_stream_destroy(strm);
+ }
+ if (renderer)
+ pjmedia_vid_port_destroy(renderer);
+ if (vid_port)
+ pjmedia_port_destroy(vid_port);
+ if (codec) {
+ codec->op->close(codec);
+ pjmedia_vid_codec_mgr_dealloc_codec(NULL, codec);
+ }
+
+ return rc;
+}
+
+static int loopback_test(pj_pool_t *pool)
+{
+ pjmedia_vid_port *capture=NULL, *renderer=NULL;
+ pjmedia_vid_port_param param;
+ pjmedia_video_format_detail *vfd;
+ pj_status_t status;
+ int rc = 0;
+
+ PJ_LOG(3, (THIS_FILE, " loopback test"));
+
+ pjmedia_vid_port_param_default(¶m);
+
+ /* Create capture, set it to active (master) */
+ status = pjmedia_vid_dev_default_param(pool,
+ PJMEDIA_VID_DEFAULT_CAPTURE_DEV,
+// 3, /* Hard-coded capture device */
+ ¶m.vidparam);
+ if (status != PJ_SUCCESS) {
+ rc = 100; goto on_return;
+ }
+ param.vidparam.dir = PJMEDIA_DIR_CAPTURE;
+ 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(¶m.vidparam.fmt, PJ_TRUE);
+ if (vfd == NULL) {
+ rc = 105; goto on_return;
+ }
+
+ status = pjmedia_vid_port_create(pool, ¶m, &capture);
+ if (status != PJ_SUCCESS) {
+ rc = 110; goto on_return;
+ }
+
+ /* Create renderer, set it to passive (slave) */
+ param.active = PJ_FALSE;
+ param.vidparam.dir = PJMEDIA_DIR_RENDER;
+ param.vidparam.rend_id = PJMEDIA_VID_DEFAULT_RENDER_DEV;
+// param.vidparam.rend_id = 6; /* Hard-coded render device */
+ param.vidparam.disp_size = vfd->size;
+
+ status = pjmedia_vid_port_create(pool, ¶m, &renderer);
+ if (status != PJ_SUCCESS) {
+ rc = 130; goto on_return;
+ }
+
+ /* 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... */
+ pj_thread_sleep(20000);
+
+on_return:
+ PJ_PERROR(3, (THIS_FILE, status, " error"));
+ if (capture)
+ pjmedia_vid_port_destroy(capture);
+ if (renderer)
+ pjmedia_vid_port_destroy(renderer);
+
+ return rc;
+}
+
+int vid_dev_test(void)
+{
+ pj_pool_t *pool;
+ int rc = 0;
+ pj_status_t status;
+
+ PJ_LOG(3, (THIS_FILE, "Video device tests.."));
+
+ pool = pj_pool_create(mem, "Viddev test", 256, 256, 0);
+
+ status = pjmedia_vid_subsys_init(mem);
+ if (status != PJ_SUCCESS)
+ return -10;
+
+ status = pjmedia_aud_subsys_init(mem);
+ if (status != PJ_SUCCESS) {
+ return -20;
+ }
+
+ rc = enum_devs();
+ if (rc != 0)
+ goto on_return;
+
+ rc = aviplay_test(pool);
+ //if (rc != 0)
+ // goto on_return;
+ // Ignore error
+ rc = 0;
+
+ rc = loopback_test(pool);
+ if (rc != 0)
+ goto on_return;
+
+on_return:
+ pjmedia_aud_subsys_shutdown();
+ pjmedia_vid_subsys_shutdown();
+ pj_pool_release(pool);
+
+ return rc;
+}
+
+