diff --git a/deps/pjsip/pjmedia/include/pjmedia-audiodev/audiodev.h b/deps/pjsip/pjmedia/include/pjmedia-audiodev/audiodev.h index 996561a4..e5533633 100644 --- a/deps/pjsip/pjmedia/include/pjmedia-audiodev/audiodev.h +++ b/deps/pjsip/pjmedia/include/pjmedia-audiodev/audiodev.h @@ -1,764 +1,764 @@ -/* $Id: audiodev.h 4243 2012-08-31 11:42:17Z nanang $ */ +/* $Id: audiodev.h 5201 2015-11-19 04:03:00Z ming $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef __PJMEDIA_AUDIODEV_AUDIODEV_H__ #define __PJMEDIA_AUDIODEV_AUDIODEV_H__ /** * @file audiodev.h * @brief Audio device API. */ #include #include #include #include #include #include #include PJ_BEGIN_DECL /** * @defgroup s2_audio_device_reference Audio Device API Reference * @ingroup audio_device_api * @brief API Reference * @{ */ /** * Type for device index. */ typedef pj_int32_t pjmedia_aud_dev_index; /** * Device index constants. */ enum { /** * Constant to denote default capture device */ PJMEDIA_AUD_DEFAULT_CAPTURE_DEV = -1, /** * Constant to denote default playback device */ PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV = -2, /** * Constant to denote invalid device index. */ PJMEDIA_AUD_INVALID_DEV = -3 }; /** * This enumeration identifies various audio device capabilities. These audio * capabilities indicates what features are supported by the underlying * audio device implementation. * * Applications get these capabilities in the #pjmedia_aud_dev_info structure. * * Application can also set the specific features/capabilities when opening * the audio stream by setting the \a flags member of #pjmedia_aud_param * structure. * * Once audio stream is running, application can also retrieve or set some * specific audio capability, by using #pjmedia_aud_stream_get_cap() and * #pjmedia_aud_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_aud_dev_cap { /** * Support for audio formats other than PCM. The value of this capability * is represented by #pjmedia_format structure. */ PJMEDIA_AUD_DEV_CAP_EXT_FORMAT = 1, /** * Support for audio input latency control or query. The value of this * capability is an unsigned integer containing milliseconds value of * the latency. */ PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY = 2, /** * Support for audio output latency control or query. The value of this * capability is an unsigned integer containing milliseconds value of * the latency. */ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY = 4, /** * Support for setting/retrieving the audio input device volume level. * The value of this capability is an unsigned integer representing * the input audio volume setting in percent. */ PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING = 8, /** * Support for setting/retrieving the audio output device volume level. * The value of this capability is an unsigned integer representing * the output audio volume setting in percent. */ PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING = 16, /** * Support for monitoring the current audio input signal volume. * The value of this capability is an unsigned integer representing * the audio volume in percent. */ PJMEDIA_AUD_DEV_CAP_INPUT_SIGNAL_METER = 32, /** * Support for monitoring the current audio output signal volume. * The value of this capability is an unsigned integer representing * the audio volume in percent. */ PJMEDIA_AUD_DEV_CAP_OUTPUT_SIGNAL_METER = 64, /** * Support for audio input routing. The value of this capability is an * integer containing #pjmedia_aud_dev_route enumeration. */ PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE = 128, /** * Support for audio output routing (e.g. loudspeaker vs earpiece). The * value of this capability is an integer containing #pjmedia_aud_dev_route * enumeration. */ PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE = 256, /** * The audio device has echo cancellation feature. The value of this * capability is a pj_bool_t containing boolean PJ_TRUE or PJ_FALSE. */ PJMEDIA_AUD_DEV_CAP_EC = 512, /** * The audio device supports setting echo cancellation fail length. The * value of this capability is an unsigned integer representing the * echo tail in milliseconds. */ PJMEDIA_AUD_DEV_CAP_EC_TAIL = 1024, /** * The audio device has voice activity detection feature. The value * of this capability is a pj_bool_t containing boolean PJ_TRUE or * PJ_FALSE. */ PJMEDIA_AUD_DEV_CAP_VAD = 2048, /** * The audio device has comfort noise generation feature. The value * of this capability is a pj_bool_t containing boolean PJ_TRUE or * PJ_FALSE. */ PJMEDIA_AUD_DEV_CAP_CNG = 4096, /** * The audio device has packet loss concealment feature. The value * of this capability is a pj_bool_t containing boolean PJ_TRUE or * PJ_FALSE. */ PJMEDIA_AUD_DEV_CAP_PLC = 8192, /** * End of capability */ PJMEDIA_AUD_DEV_CAP_MAX = 16384 } pjmedia_aud_dev_cap; /** * This enumeration describes audio routing setting. */ typedef enum pjmedia_aud_dev_route { /** * Default route, it is the default audio route of the audio framework * backend, as in opening audio device without specifying any route * setting or with specifying neutral route setting. */ PJMEDIA_AUD_DEV_ROUTE_DEFAULT = 0, /** Route to loudspeaker */ PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER = 1, /** Route to earpiece */ PJMEDIA_AUD_DEV_ROUTE_EARPIECE = 2, /** Route to paired Bluetooth device */ PJMEDIA_AUD_DEV_ROUTE_BLUETOOTH = 4 } pjmedia_aud_dev_route; /** * Device information structure returned by #pjmedia_aud_dev_get_info(). */ typedef struct pjmedia_aud_dev_info { /** * The device name */ char name[PJMEDIA_AUD_DEV_INFO_NAME_LEN]; /** * Maximum number of input channels supported by this device. If the * value is zero, the device does not support input operation (i.e. * it is a playback only device). */ unsigned input_count; /** * Maximum number of output channels supported by this device. If the * value is zero, the device does not support output operation (i.e. * it is an input only device). */ unsigned output_count; /** * Default sampling rate. */ unsigned default_samples_per_sec; /** * The underlying driver name */ char driver[32]; /** * Device capabilities, as bitmask combination of #pjmedia_aud_dev_cap. */ unsigned caps; /** * Supported audio device routes, as bitmask combination of * #pjmedia_aud_dev_route. The value may be zero if the device * does not support audio routing. */ unsigned routes; /** * Number of audio formats supported by this device. The value may be * zero if the device does not support non-PCM format. */ unsigned ext_fmt_cnt; /** * Array of supported extended audio formats */ pjmedia_format ext_fmt[8]; } pjmedia_aud_dev_info; /** * This callback is called by player stream when it needs additional data * to be played by the device. Application must fill in the whole of output * buffer with audio samples. * * The frame argument contains the following values: * - timestamp Playback 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 user_data User data associated with the stream. * @param frame Audio frame, which buffer is to be filled in by * the application. * * @return Returning non-PJ_SUCCESS will cause the audio stream * to stop */ typedef pj_status_t (*pjmedia_aud_play_cb)(void *user_data, pjmedia_frame *frame); /** * This callback is called by recorder stream when it has captured the whole * packet worth of audio samples. * * @param user_data User data associated with the stream. * @param frame Captured frame. * * @return Returning non-PJ_SUCCESS will cause the audio stream * to stop */ typedef pj_status_t (*pjmedia_aud_rec_cb)(void *user_data, pjmedia_frame *frame); /** * This structure specifies the parameters to open the audio stream. */ typedef struct pjmedia_aud_param { /** * The audio direction. This setting is mandatory. */ pjmedia_dir dir; /** * The audio recorder device ID. This setting is mandatory if the audio * direction includes input/capture direction. */ pjmedia_aud_dev_index rec_id; /** * The audio playback device ID. This setting is mandatory if the audio * direction includes output/playback direction. */ pjmedia_aud_dev_index play_id; /** * Clock rate/sampling rate. This setting is mandatory. */ unsigned clock_rate; /** * Number of channels. This setting is mandatory. */ unsigned channel_count; /** * Number of samples per frame. This setting is mandatory. */ unsigned samples_per_frame; /** * Number of bits per sample. This setting is mandatory. */ unsigned bits_per_sample; /** * This flags specifies which of the optional settings are valid in this * structure. The flags is bitmask combination of pjmedia_aud_dev_cap. */ unsigned flags; /** * Set the audio format. This setting is optional, and will only be used * if PJMEDIA_AUD_DEV_CAP_EXT_FORMAT is set in the flags. */ pjmedia_format ext_fmt; /** * Input latency, in milliseconds. This setting is optional, and will * only be used if PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY is set in the flags. */ unsigned input_latency_ms; /** * Input latency, in milliseconds. This setting is optional, and will * only be used if PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY is set in the flags. */ unsigned output_latency_ms; /** * Input volume setting, in percent. This setting is optional, and will * only be used if PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING is set in * the flags. */ unsigned input_vol; /** * Output volume setting, in percent. This setting is optional, and will * only be used if PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING is set in * the flags. */ unsigned output_vol; /** * Set the audio input route. This setting is optional, and will only be * used if PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE is set in the flags. */ pjmedia_aud_dev_route input_route; /** * Set the audio output route. This setting is optional, and will only be * used if PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE is set in the flags. */ pjmedia_aud_dev_route output_route; /** * Enable/disable echo canceller, if the device supports it. This setting * is optional, and will only be used if PJMEDIA_AUD_DEV_CAP_EC is set in * the flags. */ pj_bool_t ec_enabled; /** * Set echo canceller tail length in milliseconds, if the device supports * it. This setting is optional, and will only be used if * PJMEDIA_AUD_DEV_CAP_EC_TAIL is set in the flags. */ unsigned ec_tail_ms; /** * Enable/disable PLC. This setting is optional, and will only be used * if PJMEDIA_AUD_DEV_CAP_PLC is set in the flags. */ pj_bool_t plc_enabled; /** * Enable/disable CNG. This setting is optional, and will only be used * if PJMEDIA_AUD_DEV_CAP_CNG is set in the flags. */ pj_bool_t cng_enabled; /** * Enable/disable VAD. This setting is optional, and will only be used * if PJMEDIA_AUD_DEV_CAP_VAD is set in the flags. */ pj_bool_t vad_enabled; } pjmedia_aud_param; typedef enum pjmedia_aud_dev_event { PJMEDIA_AUD_DEV_DEFAULT_INPUT_CHANGED, PJMEDIA_AUD_DEV_DEFAULT_OUTPUT_CHANGED, PJMEDIA_AUD_DEV_LIST_WILL_REFRESH, PJMEDIA_AUD_DEV_LIST_DID_REFRESH } pjmedia_aud_dev_event; typedef void (*pjmedia_aud_dev_observer_callback)(pjmedia_aud_dev_event event); /** * This structure specifies the parameters to set an audio device observer */ typedef struct pjmedia_aud_dev_observer { pjmedia_aud_dev_observer_callback cb; pj_pool_t *pool; pj_mutex_t *lock; pj_thread_t *thread; pj_thread_desc thread_desc; } pjmedia_aud_dev_observer; /** Forward declaration for pjmedia_aud_stream */ typedef struct pjmedia_aud_stream pjmedia_aud_stream; /** Forward declaration for audio device factory */ typedef struct pjmedia_aud_dev_factory pjmedia_aud_dev_factory; /* typedef for factory creation function */ typedef pjmedia_aud_dev_factory* (*pjmedia_aud_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_aud_dev_cap_name(pjmedia_aud_dev_cap cap, const char **p_desc); /** * Set a capability field value in #pjmedia_aud_param structure. This will * also set the flags field for the specified capability in the structure. * * @param param The structure. * @param cap The audio capability which value is to be set. * @param pval Pointer to value. Please see the type of value to * be supplied in the pjmedia_aud_dev_cap documentation. * * @return PJ_SUCCESS on successful operation or the appropriate * error code. */ PJ_DECL(pj_status_t) pjmedia_aud_param_set_cap(pjmedia_aud_param *param, pjmedia_aud_dev_cap cap, const void *pval); /** * Get a capability field value from #pjmedia_aud_param structure. This * function will return PJMEDIA_EAUD_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 audio capability which value is to be retrieved. * @param pval Pointer to value. Please see the type of value to * be supplied in the pjmedia_aud_dev_cap documentation. * * @return PJ_SUCCESS on successful operation or the appropriate * error code. */ PJ_DECL(pj_status_t) pjmedia_aud_param_get_cap(const pjmedia_aud_param *param, pjmedia_aud_dev_cap cap, void *pval); /** * Initialize the audio subsystem. This will register all supported audio * device factories to the audio subsystem. This function may be called * more than once, but each call to this function must have the * corresponding #pjmedia_aud_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_aud_subsys_init(pj_pool_factory *pf); /** * Get the pool factory registered to the audio subsystem. * * @return The pool factory. */ PJ_DECL(pj_pool_factory*) pjmedia_aud_subsys_get_pool_factory(void); /** * Shutdown the audio subsystem. This will destroy all audio device factories * registered in the audio subsystem. Note that currently opened audio streams * may or may not be closed, depending on the implementation of the audio * device factories. * * @return PJ_SUCCESS on successful operation or the appropriate * error code. */ PJ_DECL(pj_status_t) pjmedia_aud_subsys_shutdown(void); /** * Register a supported audio device factory to the audio subsystem. This * function can only be called after calling #pjmedia_aud_subsys_init(). * * @param adf The audio device factory. * * @return PJ_SUCCESS on successful operation or the appropriate * error code. */ PJ_DECL(pj_status_t) pjmedia_aud_register_factory(pjmedia_aud_dev_factory_create_func_ptr adf); /** * Unregister an audio device factory from the audio subsystem. This * function can only be called after calling #pjmedia_aud_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 adf The audio device factory. * * @return PJ_SUCCESS on successful operation or the appropriate * error code. */ PJ_DECL(pj_status_t) pjmedia_aud_unregister_factory(pjmedia_aud_dev_factory_create_func_ptr adf); /** * Refresh the list of sound devices installed in the system. This function * will only refresh the list of audio device so all active audio streams will * be unaffected. After refreshing the device list, application MUST make sure * to update all index references to audio devices (i.e. all variables of type * pjmedia_aud_dev_index) before calling any function that accepts audio device * index as its parameter. * * @return PJ_SUCCESS on successful operation or the appropriate * error code. */ PJ_DECL(pj_status_t) pjmedia_aud_dev_refresh(void); /** * Get the number of sound devices installed in the system. * * @return The number of sound devices installed in the system. */ PJ_DECL(unsigned) pjmedia_aud_dev_count(void); /** * Get device information. * * @param id The audio 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_aud_dev_get_info(pjmedia_aud_dev_index id, pjmedia_aud_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_aud_dev_lookup(const char *drv_name, const char *dev_name, pjmedia_aud_dev_index *id); /** * Initialize the audio device parameters with default values for the * specified device. * * @param id The audio device ID. * @param param The audio 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_aud_dev_default_param(pjmedia_aud_dev_index id, pjmedia_aud_param *param); /** * Open audio stream object using the specified parameters. * * @param param Sound device parameters to be used for the stream. * @param rec_cb Callback to be called on every input frame captured. * @param play_cb Callback to be called everytime the sound device needs * audio frames to be played back. * @param user_data Arbitrary user data, which will be given back in the * callbacks. * @param p_strm Pointer to receive the audio stream. * * @return PJ_SUCCESS on successful operation or the appropriate * error code. */ PJ_DECL(pj_status_t) pjmedia_aud_stream_create(const pjmedia_aud_param *param, pjmedia_aud_rec_cb rec_cb, pjmedia_aud_play_cb play_cb, void *user_data, pjmedia_aud_stream **p_strm); /** * Get the running parameters for the specified audio stream. * * @param strm The audio stream. * @param param Audio 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_aud_stream_get_param(pjmedia_aud_stream *strm, pjmedia_aud_param *param); /** * Get the value of a specific capability of the audio stream. * * @param strm The audio stream. * @param cap The audio 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_aud_dev_cap * documentation. * * @return PJ_SUCCESS on successful operation or the appropriate * error code. */ PJ_DECL(pj_status_t) pjmedia_aud_stream_get_cap(pjmedia_aud_stream *strm, pjmedia_aud_dev_cap cap, void *value); /** * Set the value of a specific capability of the audio stream. * * @param strm The audio stream. * @param cap The audio capability which value is to be set. * @param value Pointer to value. Please see the type of value to * be supplied in the pjmedia_aud_dev_cap documentation. * * @return PJ_SUCCESS on successful operation or the appropriate * error code. */ PJ_DECL(pj_status_t) pjmedia_aud_stream_set_cap(pjmedia_aud_stream *strm, pjmedia_aud_dev_cap cap, const void *value); /** * Start the stream. * * @param strm The audio stream. * * @return PJ_SUCCESS on successful operation or the appropriate * error code. */ PJ_DECL(pj_status_t) pjmedia_aud_stream_start(pjmedia_aud_stream *strm); /** * Stop the stream. * * @param strm The audio stream. * * @return PJ_SUCCESS on successful operation or the appropriate * error code. */ PJ_DECL(pj_status_t) pjmedia_aud_stream_stop(pjmedia_aud_stream *strm); /** * Destroy the stream. * * @param strm The audio stream. * * @return PJ_SUCCESS on successful operation or the appropriate * error code. */ PJ_DECL(pj_status_t) pjmedia_aud_stream_destroy(pjmedia_aud_stream *strm); /** * Set an audio device observer callback. * * @param cb The callback that needs to be registred, or NULL in * in case it needs to be unregistered. Only one callback * can be registered. * * @return PJ_SUCCESS on successful operation or the appropriate * error code. */ PJ_DECL(pj_status_t) pjmedia_aud_dev_set_observer_cb(pjmedia_aud_dev_observer_callback cb); /** * @} */ PJ_END_DECL #endif /* __PJMEDIA_AUDIODEV_AUDIODEV_H__ */ diff --git a/deps/pjsip/pjmedia/include/pjmedia-audiodev/config.h b/deps/pjsip/pjmedia/include/pjmedia-audiodev/config.h index df7c5671..1823d770 100644 --- a/deps/pjsip/pjmedia/include/pjmedia-audiodev/config.h +++ b/deps/pjsip/pjmedia/include/pjmedia-audiodev/config.h @@ -1,363 +1,363 @@ -/* $Id: config.h 4435 2013-03-11 06:32:58Z nanang $ */ +/* $Id: config.h 5201 2015-11-19 04:03:00Z ming $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef __PJMEDIA_AUDIODEV_CONFIG_H__ #define __PJMEDIA_AUDIODEV_CONFIG_H__ /** * @file config.h * @brief Audio config. */ #include #include PJ_BEGIN_DECL /** * @defgroup audio_device_api Audio Device API * @brief PJMEDIA audio device abstraction API. */ /** * @defgroup s1_audio_device_config Compile time configurations * @ingroup audio_device_api * @brief Compile time configurations * @{ */ /** * This setting controls whether native ALSA support should be included. */ #ifndef PJMEDIA_AUDIO_DEV_HAS_ALSA # define PJMEDIA_AUDIO_DEV_HAS_ALSA 0 #endif /** * This setting controls whether null audio support should be included. */ #ifndef PJMEDIA_AUDIO_DEV_HAS_NULL_AUDIO # define PJMEDIA_AUDIO_DEV_HAS_NULL_AUDIO 0 #endif /** * This setting controls whether coreaudio support should be included. */ #ifndef PJMEDIA_AUDIO_DEV_HAS_COREAUDIO # define PJMEDIA_AUDIO_DEV_HAS_COREAUDIO 0 #endif /** * This setting controls whether WMME support should be included. */ #ifndef PJMEDIA_AUDIO_DEV_HAS_WMME # define PJMEDIA_AUDIO_DEV_HAS_WMME 0 #endif /** * This setting controls the buffer length of audio device name. * * Default: 128 for Windows platforms, 64 for others */ #ifndef PJMEDIA_AUD_DEV_INFO_NAME_LEN # if (defined(PJ_WIN32) && PJ_WIN32!=0) || \ (defined(PJ_WIN64) && PJ_WIN64!=0) # define PJMEDIA_AUD_DEV_INFO_NAME_LEN 128 # else # define PJMEDIA_AUD_DEV_INFO_NAME_LEN 64 # endif #endif /** * @} */ PJ_END_DECL #endif /* __PJMEDIA_AUDIODEV_CONFIG_H__ */ /* --------------------- DOCUMENTATION FOLLOWS --------------------------- */ /** * @addtogroup audio_device_api Audio Device API * @{ PJMEDIA Audio Device API is a cross-platform audio API appropriate for use with VoIP applications and many other types of audio streaming applications. The API abstracts many different audio API's on various platforms, such as: - PortAudio back-end for Win32, Windows Mobile, Linux, Unix, dan MacOS X. - native WMME audio for Win32 and Windows Mobile devices - native Symbian audio streaming/multimedia framework (MMF) implementation - native Nokia Audio Proxy Server (APS) implementation - null-audio implementation - and more to be implemented in the future The Audio 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 audio capabilities of audio 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 audio features built-in in the device, such as: - echo cancellation, - built-in codecs, - audio routing (e.g. to earpiece or loudspeaker), - volume control, - etc. - Codec support: \n Some audio devices such as Nokia/Symbian Audio Proxy Server (APS) and Nokia VoIP Audio Services (VAS) support built-in hardware audio codecs (e.g. G.729, iLBC, and AMR), and application can use the sound device in encoded mode to make use of these hardware codecs. - Multiple backends: \n The new API supports multiple audio backends (called factories or drivers in the code) to be active simultaneously, and audio backends may be added or removed during run-time. @section using Overview on using the API @subsection getting_started Getting started -# Configure the application's project settings.\n Add the following include: \code #include \endcode\n And add pjmedia-audiodev library to your application link specifications.\n -# Compile time settings.\n Use the compile time settings to enable or disable specific audio drivers. For more information, please see \ref s1_audio_device_config. -# API initialization and cleaning up.\n Before anything else, application must initialize the API by calling: \code pjmedia_aud_subsys_init(pf);\endcode\n And add this in the application cleanup sequence \code pjmedia_aud_subsys_shutdown();\endcode @subsection devices Working with devices -# The following code prints the list of audio devices detected in the system. \code int dev_count; pjmedia_aud_dev_index dev_idx; pj_status_t status; dev_count = pjmedia_aud_dev_count(); printf("Got %d audio devices\n", dev_count); for (dev_idx=0; dev_idx * * 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_CODEC_PJMEDIA_CODEC_H__ #define __PJMEDIA_CODEC_PJMEDIA_CODEC_H__ /** * @file pjmedia-codec.h * @brief Include all codecs API in PJMEDIA-CODEC */ #include #include #include #include #include #include #include #include #include #include #include #endif /* __PJMEDIA_CODEC_PJMEDIA_CODEC_H__ */ diff --git a/deps/pjsip/pjmedia/include/pjmedia-codec/config.h b/deps/pjsip/pjmedia/include/pjmedia-codec/config.h index 27bd1e97..1fe715fd 100644 --- a/deps/pjsip/pjmedia/include/pjmedia-codec/config.h +++ b/deps/pjsip/pjmedia/include/pjmedia-codec/config.h @@ -1,241 +1,241 @@ -/* $Id: config.h 4331 2013-01-23 06:18:18Z ming $ */ +/* $Id: config.h 5239 2016-02-04 06:11:58Z ming $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef __PJMEDIA_CODEC_CONFIG_H__ #define __PJMEDIA_CODEC_CONFIG_H__ /** * @file config.h * @brief PJMEDIA-CODEC compile time settings */ /** * @defgroup pjmedia_codec_config PJMEDIA-CODEC Compile Time Settings * @ingroup PJMEDIA_CODEC * @brief Various compile time settings such as to enable/disable codecs * @{ */ #include /* * Include config_auto.h if autoconf is used (PJ_AUTOCONF is set) */ #if defined(PJ_AUTOCONF) # include #endif /** * Unless specified otherwise, L16 codec is included by default. */ #ifndef PJMEDIA_HAS_L16_CODEC # define PJMEDIA_HAS_L16_CODEC 1 #endif /** * Unless specified otherwise, GSM codec is included by default. */ #ifndef PJMEDIA_HAS_GSM_CODEC # define PJMEDIA_HAS_GSM_CODEC 1 #endif /** * Unless specified otherwise, Speex codec is included by default. */ #ifndef PJMEDIA_HAS_SPEEX_CODEC # define PJMEDIA_HAS_SPEEX_CODEC 1 #endif /** * Speex codec default complexity setting. */ #ifndef PJMEDIA_CODEC_SPEEX_DEFAULT_COMPLEXITY # define PJMEDIA_CODEC_SPEEX_DEFAULT_COMPLEXITY 2 #endif /** * Speex codec default quality setting. Please note that pjsua-lib may override * this setting via its codec quality setting (i.e PJSUA_DEFAULT_CODEC_QUALITY). */ #ifndef PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY # define PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY 8 #endif /** * Unless specified otherwise, iLBC codec is included by default. */ #ifndef PJMEDIA_HAS_ILBC_CODEC # define PJMEDIA_HAS_ILBC_CODEC 1 #endif /** * Unless specified otherwise, G.722 codec is included by default. */ #ifndef PJMEDIA_HAS_G722_CODEC # define PJMEDIA_HAS_G722_CODEC 1 #endif /** * Default G.722 codec encoder and decoder level adjustment. The G.722 * specifies that it uses 14 bit PCM for input and output, while PJMEDIA * normally uses 16 bit PCM, so the conversion is done by applying * level adjustment. If the value is non-zero, then PCM input samples to * the encoder will be shifted right by this value, and similarly PCM * output samples from the decoder will be shifted left by this value. * * This can be changed at run-time after initialization by calling * #pjmedia_codec_g722_set_pcm_shift(). * * Default: 2. */ #ifndef PJMEDIA_G722_DEFAULT_PCM_SHIFT # define PJMEDIA_G722_DEFAULT_PCM_SHIFT 2 #endif /** * Specifies whether G.722 PCM shifting should be stopped when clipping * detected in the decoder. Enabling this feature can be useful when * talking to G.722 implementation that uses 16 bit PCM for G.722 input/ * output (for any reason it seems to work) and the PCM shifting causes * audio clipping. * * See also #PJMEDIA_G722_DEFAULT_PCM_SHIFT. * * Default: enabled. */ #ifndef PJMEDIA_G722_STOP_PCM_SHIFT_ON_CLIPPING # define PJMEDIA_G722_STOP_PCM_SHIFT_ON_CLIPPING 1 #endif /** * Unless specified otherwise, opus codec is included by default. */ #ifndef PJMEDIA_HAS_OPUS_CODEC # define PJMEDIA_HAS_OPUS_CODEC 1 #endif /** * Enable Passthrough codecs. * * Default: 0 */ #ifndef PJMEDIA_HAS_PASSTHROUGH_CODECS # define PJMEDIA_HAS_PASSTHROUGH_CODECS 0 #endif /** * G.722.1 codec is disabled by default. */ #ifndef PJMEDIA_HAS_G7221_CODEC # define PJMEDIA_HAS_G7221_CODEC 0 #endif /** * Default G.722.1 codec encoder and decoder level adjustment. * If the value is non-zero, then PCM input samples to the encoder will * be shifted right by this value, and similarly PCM output samples from * the decoder will be shifted left by this value. * * This can be changed at run-time after initialization by calling * #pjmedia_codec_g7221_set_pcm_shift(). */ #ifndef PJMEDIA_G7221_DEFAULT_PCM_SHIFT # define PJMEDIA_G7221_DEFAULT_PCM_SHIFT 1 #endif /** * Enabling both G.722.1 codec implementations, internal PJMEDIA and IPP, * may cause problem in SDP, i.e: payload types duplications. So, let's * just trap such case here at compile time. * * Application can control which implementation to be used by manipulating * PJMEDIA_HAS_G7221_CODEC and PJMEDIA_HAS_INTEL_IPP_CODEC_G722_1 in * config_site.h. */ #if (PJMEDIA_HAS_G7221_CODEC != 0) && (PJMEDIA_HAS_INTEL_IPP != 0) && \ (PJMEDIA_HAS_INTEL_IPP_CODEC_G722_1 != 0) # error Only one G.722.1 implementation can be enabled at the same time. \ Please use PJMEDIA_HAS_G7221_CODEC and \ PJMEDIA_HAS_INTEL_IPP_CODEC_G722_1 in your config_site.h \ 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 /** * Specify if FFMPEG video codecs are available. * * Default: PJMEDIA_HAS_FFMPEG_CODEC */ #ifndef PJMEDIA_HAS_FFMPEG_VID_CODEC # define PJMEDIA_HAS_FFMPEG_VID_CODEC PJMEDIA_HAS_FFMPEG_CODEC #endif /** * Enable FFMPEG H263+/H263-1998 codec. * * Default: 1 */ #ifndef PJMEDIA_HAS_FFMPEG_CODEC_H263P # define PJMEDIA_HAS_FFMPEG_CODEC_H263P PJMEDIA_HAS_FFMPEG_VID_CODEC #endif /** * Enable FFMPEG H264 codec (requires libx264). * * Default: 0 */ #ifndef PJMEDIA_HAS_FFMPEG_CODEC_H264 # define PJMEDIA_HAS_FFMPEG_CODEC_H264 PJMEDIA_HAS_FFMPEG_VID_CODEC #endif /** * Compile VPX support, unless explicitly disabled */ #ifndef PJMEDIA_HAS_VPX_CODEC # define PJMEDIA_HAS_VPX_CODEC PJMEDIA_HAS_LIBVPX #endif /** * @} */ #endif /* __PJMEDIA_CODEC_CONFIG_H__ */ diff --git a/deps/pjsip/pjmedia/include/pjmedia-codec/types.h b/deps/pjsip/pjmedia/include/pjmedia-codec/types.h index 8bc31285..e3bf3e17 100644 --- a/deps/pjsip/pjmedia/include/pjmedia-codec/types.h +++ b/deps/pjsip/pjmedia/include/pjmedia-codec/types.h @@ -1,132 +1,132 @@ -/* $Id: types.h 4264 2012-09-24 06:58:16Z nanang $ */ +/* $Id: types.h 5239 2016-02-04 06:11:58Z ming $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef __PJMEDIA_CODEC_TYPES_H__ #define __PJMEDIA_CODEC_TYPES_H__ /** * @file types.h * @brief PJMEDIA-CODEC types and constants */ #include #include /** * @defgroup pjmedia_codec_types PJMEDIA-CODEC Types and Constants * @ingroup PJMEDIA_CODEC * @brief Constants used by PJMEDIA-CODEC * @{ */ /** * These are the dynamic payload types that are used by audio codecs in * this library. Also see the header file for list * of static payload types. */ enum pjmedia_audio_pt { /* According to IANA specifications, dynamic payload types are to be in * the range 96-127 (inclusive). This enum is structured to place the * values of the payload types specified below into that range. * * PJMEDIA_RTP_PT_DYNAMIC is defined in . It is defined * to be 96. * * PJMEDIA_RTP_PT_TELEPHONE_EVENTS is defined in . * The default value is 96. */ #if PJMEDIA_RTP_PT_TELEPHONE_EVENTS PJMEDIA_RTP_PT_START = PJMEDIA_RTP_PT_TELEPHONE_EVENTS, #else PJMEDIA_RTP_PT_START = (PJMEDIA_RTP_PT_DYNAMIC-1), #endif PJMEDIA_RTP_PT_SPEEX_NB, /**< Speex narrowband/8KHz */ PJMEDIA_RTP_PT_SPEEX_WB, /**< Speex wideband/16KHz */ PJMEDIA_RTP_PT_SPEEX_UWB, /**< Speex 32KHz */ PJMEDIA_RTP_PT_SILK_NB, /**< SILK narrowband/8KHz */ PJMEDIA_RTP_PT_SILK_MB, /**< SILK mediumband/12KHz */ PJMEDIA_RTP_PT_SILK_WB, /**< SILK wideband/16KHz */ PJMEDIA_RTP_PT_SILK_SWB, /**< SILK 24KHz */ PJMEDIA_RTP_PT_ILBC, /**< iLBC (13.3/15.2Kbps) */ PJMEDIA_RTP_PT_AMR, /**< AMR (4.75 - 12.2Kbps) */ PJMEDIA_RTP_PT_AMRWB, /**< AMRWB (6.6 - 23.85Kbps)*/ PJMEDIA_RTP_PT_AMRWBE, /**< AMRWBE */ PJMEDIA_RTP_PT_OPUS, /**< OPUS */ PJMEDIA_RTP_PT_G726_16, /**< G726 @ 16Kbps */ PJMEDIA_RTP_PT_G726_24, /**< G726 @ 24Kbps */ PJMEDIA_RTP_PT_G726_32, /**< G726 @ 32Kbps */ PJMEDIA_RTP_PT_G726_40, /**< G726 @ 40Kbps */ PJMEDIA_RTP_PT_G722_1_16, /**< G722.1 (16Kbps) */ PJMEDIA_RTP_PT_G722_1_24, /**< G722.1 (24Kbps) */ PJMEDIA_RTP_PT_G722_1_32, /**< G722.1 (32Kbps) */ PJMEDIA_RTP_PT_G7221C_24, /**< G722.1 Annex C (24Kbps)*/ PJMEDIA_RTP_PT_G7221C_32, /**< G722.1 Annex C (32Kbps)*/ PJMEDIA_RTP_PT_G7221C_48, /**< G722.1 Annex C (48Kbps)*/ PJMEDIA_RTP_PT_G7221_RSV1, /**< G722.1 reserve */ PJMEDIA_RTP_PT_G7221_RSV2, /**< G722.1 reserve */ PJMEDIA_RTP_PT_L16_8KHZ_MONO, /**< L16 @ 8KHz, mono */ PJMEDIA_RTP_PT_L16_8KHZ_STEREO, /**< L16 @ 8KHz, stereo */ //PJMEDIA_RTP_PT_L16_11KHZ_MONO, /**< L16 @ 11KHz, mono */ //PJMEDIA_RTP_PT_L16_11KHZ_STEREO, /**< L16 @ 11KHz, stereo */ PJMEDIA_RTP_PT_L16_16KHZ_MONO, /**< L16 @ 16KHz, mono */ PJMEDIA_RTP_PT_L16_16KHZ_STEREO, /**< L16 @ 16KHz, stereo */ //PJMEDIA_RTP_PT_L16_22KHZ_MONO, /**< L16 @ 22KHz, mono */ //PJMEDIA_RTP_PT_L16_22KHZ_STEREO, /**< L16 @ 22KHz, stereo */ //PJMEDIA_RTP_PT_L16_32KHZ_MONO, /**< L16 @ 32KHz, mono */ //PJMEDIA_RTP_PT_L16_32KHZ_STEREO, /**< L16 @ 32KHz, stereo */ //PJMEDIA_RTP_PT_L16_48KHZ_MONO, /**< L16 @ 48KHz, mono */ //PJMEDIA_RTP_PT_L16_48KHZ_STEREO, /**< L16 @ 48KHz, stereo */ /* Caution! * Ensure the value of the last pt above is <= 127. */ }; /** * These are the dynamic payload types that are used by video codecs in * this library. */ enum pjmedia_video_pt { /* Video payload types */ PJMEDIA_RTP_PT_VID_START = (PJMEDIA_RTP_PT_DYNAMIC-1), PJMEDIA_RTP_PT_H263P, PJMEDIA_RTP_PT_H264, PJMEDIA_RTP_PT_H264_RSV1, PJMEDIA_RTP_PT_H264_RSV2, PJMEDIA_RTP_PT_H264_RSV3, PJMEDIA_RTP_PT_H264_RSV4, PJMEDIA_RTP_PT_VP8, /* Caution! * Ensure the value of the last pt above is <= 127. */ }; /** * @} */ #endif /* __PJMEDIA_CODEC_TYPES_H__ */ diff --git a/deps/pjsip/pjmedia/include/pjmedia-videodev/avi_dev.h b/deps/pjsip/pjmedia/include/pjmedia-videodev/avi_dev.h index 0324cd7c..f005c362 100644 --- a/deps/pjsip/pjmedia/include/pjmedia-videodev/avi_dev.h +++ b/deps/pjsip/pjmedia/include/pjmedia-videodev/avi_dev.h @@ -1,139 +1,139 @@ -/* $Id: avi_dev.h 4016 2012-04-04 05:05:50Z bennylp $ */ +/* $Id: avi_dev.h 5117 2015-06-25 04:51:59Z ming $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef __PJMEDIA_VIDEODEV_AVI_DEV_H__ #define __PJMEDIA_VIDEODEV_AVI_DEV_H__ /** * @file avi_dev.h * @brief AVI player virtual device */ #include #include PJ_BEGIN_DECL /** * @defgroup avi_dev AVI Player Virtual Device * @ingroup video_device_api * @brief AVI player virtual device * @{ * This describes a virtual capture device which takes its input from an AVI * file. */ /** * Settings for the AVI player virtual device. This param corresponds to * PJMEDIA_VID_DEV_CAP_AVI_PLAY capability of the video device/stream. */ typedef struct pjmedia_avi_dev_param { /** * Specifies the full path of the AVI file to be played. */ pj_str_t path; /** * If this setting is specified when setting the device, this specifies * the title to be assigned as the device name. If this setting not * specified, the filename part of the path will be used. */ pj_str_t title; /** * The underlying AVI streams created by the device. If the value is NULL, * that means the device has not been configured yet. Application can use * this field to retrieve the audio stream of the AVI. This setting is * "get"-only and will be ignored in "set capability" operation. */ pjmedia_avi_streams *avi_streams; } pjmedia_avi_dev_param; /** * Reset pjmedia_avi_dev_param with the default settings. This mostly will * reset all values to NULL or zero. * * @param p The parameter to be initialized. */ PJ_DECL(void) pjmedia_avi_dev_param_default(pjmedia_avi_dev_param *p); /** * Create a AVI device factory, and register it to the video device * subsystem. At least one factory needs to be created before an AVI * device can be allocated and used, and normally only one factory is * needed per application. * * @param pf Pool factory to be used. * @param max_dev Number of devices to be reserved. * @param p_ret Pointer to return the factory instance, to be * used when allocating a virtual device. * * @return PJ_SUCCESS on success or the appropriate error code. */ PJ_DECL(pj_status_t) pjmedia_avi_dev_create_factory( pj_pool_factory *pf, unsigned max_dev, pjmedia_vid_dev_factory **p_ret); /** * Allocate one device ID to be used to play the specified AVI file in * the parameter. * * @param param The parameter, with at least the AVI file path * set. * @param p_id Optional pointer to receive device ID to play * the file. * * @return PJ_SUCCESS or the appropriate error code. * */ PJ_DECL(pj_status_t) pjmedia_avi_dev_alloc(pjmedia_vid_dev_factory *f, pjmedia_avi_dev_param *param, pjmedia_vid_dev_index *p_id); /** * Retrieve the parameters set for the virtual device. * * @param id Device ID. * @param prm Structure to receive the settings. * * @return PJ_SUCCESS or the appropriate error code. */ PJ_DECL(pj_status_t) pjmedia_avi_dev_get_param(pjmedia_vid_dev_index id, pjmedia_avi_dev_param *param); /** * Free the resources associated with the virtual device. * * @param id The device ID. * * @return PJ_SUCCESS or the appropriate error code. */ PJ_DECL(pj_status_t) pjmedia_avi_dev_free(pjmedia_vid_dev_index id); /** * @} */ PJ_END_DECL #endif /* __PJMEDIA_VIDEODEV_AVI_DEV_H__ */ diff --git a/deps/pjsip/pjmedia/include/pjmedia-videodev/config.h b/deps/pjsip/pjmedia/include/pjmedia-videodev/config.h index 5485d946..cc74971c 100644 --- a/deps/pjsip/pjmedia/include/pjmedia-videodev/config.h +++ b/deps/pjsip/pjmedia/include/pjmedia-videodev/config.h @@ -1,204 +1,204 @@ -/* $Id: config.h 4414 2013-03-05 08:21:02Z riza $ */ +/* $Id: config.h 5125 2015-07-03 06:21:30Z ming $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef __PJMEDIA_VIDEODEV_CONFIG_H__ #define __PJMEDIA_VIDEODEV_CONFIG_H__ /** * @file config.h * @brief Video config. */ #include #include 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 the maximum number of formats that can be * supported by a video device. * * Default: 64 */ #ifndef PJMEDIA_VID_DEV_INFO_FMT_CNT # define PJMEDIA_VID_DEV_INFO_FMT_CNT 64 #endif #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) /** * This setting controls the maximum number of supported video device drivers. * * Default: 8 */ #ifndef PJMEDIA_VID_DEV_MAX_DRIVERS # define PJMEDIA_VID_DEV_MAX_DRIVERS 8 #endif /** * This setting controls the maximum number of supported video devices. * * Default: 16 */ #ifndef PJMEDIA_VID_DEV_MAX_DEVS # define PJMEDIA_VID_DEV_MAX_DEVS 16 #endif /** * This setting controls whether AVFoundation support should be included. * * Default: 0 (or detected by configure) */ #ifndef PJMEDIA_VIDEO_DEV_HAS_AVF # define PJMEDIA_VIDEO_DEV_HAS_AVF 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 /** * Video4Linux2 * * Default: 0 (or detected by configure) */ #ifndef PJMEDIA_VIDEO_DEV_HAS_V4L2 # define PJMEDIA_VIDEO_DEV_HAS_V4L2 0 #endif /** * Enable support for AVI player virtual capture device. * * Default: 1 */ #ifndef PJMEDIA_VIDEO_DEV_HAS_AVI # define PJMEDIA_VIDEO_DEV_HAS_AVI 1 #endif /** * Enable support for frame buffer render device. * * Default: 1 */ #ifndef PJMEDIA_VIDEO_DEV_HAS_FB # define PJMEDIA_VIDEO_DEV_HAS_FB 1 #endif #endif /* defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) */ /** * @} */ 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/deps/pjsip/pjmedia/include/pjmedia/config.h b/deps/pjsip/pjmedia/include/pjmedia/config.h index 5d4b8c04..b78b447f 100644 --- a/deps/pjsip/pjmedia/include/pjmedia/config.h +++ b/deps/pjsip/pjmedia/include/pjmedia/config.h @@ -1,1330 +1,1330 @@ -/* $Id: config.h 4443 2013-03-20 06:56:19Z nanang $ */ +/* $Id: config.h 5242 2016-02-18 03:15:19Z riza $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef __PJMEDIA_CONFIG_H__ #define __PJMEDIA_CONFIG_H__ /** * @file pjmedia/config.h Compile time config * @brief Contains some compile time constants. */ #include /** * @defgroup PJMEDIA_BASE Base Types and Configurations */ /** * @defgroup PJMEDIA_CONFIG Compile time configuration * @ingroup PJMEDIA_BASE * @brief Some compile time configuration settings. * @{ */ /* * Include config_auto.h if autoconf is used (PJ_AUTOCONF is set) */ #if defined(PJ_AUTOCONF) # include #endif /** * Specify whether we prefer to use audio switch board rather than * conference bridge. * * Audio switch board is a kind of simplified version of conference * bridge, but not really the subset of conference bridge. It has * stricter rules on audio routing among the pjmedia ports and has * no audio mixing capability. The power of it is it could work with * encoded audio frames where conference brigde couldn't. * * Default: 0 */ #ifndef PJMEDIA_CONF_USE_SWITCH_BOARD # define PJMEDIA_CONF_USE_SWITCH_BOARD 0 #endif /** * Specify buffer size for audio switch board, in bytes. This buffer will * be used for transmitting/receiving audio frame data (and some overheads, * i.e: pjmedia_frame structure) among conference ports in the audio * switch board. For example, if a port uses PCM format @44100Hz mono * and frame time 20ms, the PCM audio data will require 1764 bytes, * so with overhead, a safe buffer size will be ~1900 bytes. * * Default: PJMEDIA_MAX_MTU */ #ifndef PJMEDIA_CONF_SWITCH_BOARD_BUF_SIZE # define PJMEDIA_CONF_SWITCH_BOARD_BUF_SIZE PJMEDIA_MAX_MTU #endif /* * Types of sound stream backends. */ /** * This macro has been deprecated in releasee 1.1. Please see * http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more information. */ #if defined(PJMEDIA_SOUND_IMPLEMENTATION) # error PJMEDIA_SOUND_IMPLEMENTATION has been deprecated #endif /** * This macro has been deprecated in releasee 1.1. Please see * http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more information. */ #if defined(PJMEDIA_PREFER_DIRECT_SOUND) # error PJMEDIA_PREFER_DIRECT_SOUND has been deprecated #endif /** * This macro controls whether the legacy sound device API is to be * implemented, for applications that still use the old sound device * API (sound.h). If this macro is set to non-zero, the sound_legacy.c * will be included in the compilation. The sound_legacy.c is an * implementation of old sound device (sound.h) using the new Audio * Device API. * * Please see http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more * info. */ #ifndef PJMEDIA_HAS_LEGACY_SOUND_API # define PJMEDIA_HAS_LEGACY_SOUND_API 1 #endif /** * Specify default sound device latency, in milisecond. */ #ifndef PJMEDIA_SND_DEFAULT_REC_LATENCY # define PJMEDIA_SND_DEFAULT_REC_LATENCY 100 #endif /** * Specify default sound device latency, in milisecond. * * Default is 160ms for Windows Mobile and 140ms for other platforms. */ #ifndef PJMEDIA_SND_DEFAULT_PLAY_LATENCY # if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE!=0 # define PJMEDIA_SND_DEFAULT_PLAY_LATENCY 160 # else # define PJMEDIA_SND_DEFAULT_PLAY_LATENCY 140 # endif #endif /* * Types of WSOLA backend algorithm. */ /** * This denotes implementation of WSOLA using null algorithm. Expansion * will generate zero frames, and compression will just discard some * samples from the input. * * This type of implementation may be used as it requires the least * processing power. */ #define PJMEDIA_WSOLA_IMP_NULL 0 /** * This denotes implementation of WSOLA using fixed or floating point WSOLA * algorithm. This implementation provides the best quality of the result, * at the expense of one frame delay and intensive processing power * requirement. */ #define PJMEDIA_WSOLA_IMP_WSOLA 1 /** * This denotes implementation of WSOLA algorithm with faster waveform * similarity calculation. This implementation provides fair quality of * the result with the main advantage of low processing power requirement. */ #define PJMEDIA_WSOLA_IMP_WSOLA_LITE 2 /** * Specify type of Waveform based Similarity Overlap and Add (WSOLA) backend * implementation to be used. WSOLA is an algorithm to expand and/or compress * audio frames without changing the pitch, and used by the delaybuf and as PLC * backend algorithm. * * Default is PJMEDIA_WSOLA_IMP_WSOLA */ #ifndef PJMEDIA_WSOLA_IMP # define PJMEDIA_WSOLA_IMP PJMEDIA_WSOLA_IMP_WSOLA #endif /** * Specify the default maximum duration of synthetic audio that is generated * by WSOLA. This value should be long enough to cover burst of packet losses. * but not too long, because as the duration increases the quality would * degrade considerably. * * Note that this limit is only applied when fading is enabled in the WSOLA * session. * * Default: 80 */ #ifndef PJMEDIA_WSOLA_MAX_EXPAND_MSEC # define PJMEDIA_WSOLA_MAX_EXPAND_MSEC 80 #endif /** * Specify WSOLA template length, in milliseconds. The longer the template, * the smoother signal to be generated at the expense of more computation * needed, since the algorithm will have to compare more samples to find * the most similar pitch. * * Default: 5 */ #ifndef PJMEDIA_WSOLA_TEMPLATE_LENGTH_MSEC # define PJMEDIA_WSOLA_TEMPLATE_LENGTH_MSEC 5 #endif /** * Specify WSOLA algorithm delay, in milliseconds. The algorithm delay is * used to merge synthetic samples with real samples in the transition * between real to synthetic and vice versa. The longer the delay, the * smoother signal to be generated, at the expense of longer latency and * a slighty more computation. * * Default: 5 */ #ifndef PJMEDIA_WSOLA_DELAY_MSEC # define PJMEDIA_WSOLA_DELAY_MSEC 5 #endif /** * Set this to non-zero to disable fade-out/in effect in the PLC when it * instructs WSOLA to generate synthetic frames. The use of fading may * or may not improve the quality of audio, depending on the nature of * packet loss and the type of audio input (e.g. speech vs music). * Disabling fading also implicitly remove the maximum limit of synthetic * audio samples generated by WSOLA (see PJMEDIA_WSOLA_MAX_EXPAND_MSEC). * * Default: 0 */ #ifndef PJMEDIA_WSOLA_PLC_NO_FADING # define PJMEDIA_WSOLA_PLC_NO_FADING 0 #endif /** * Limit the number of calls by stream to the PLC to generate synthetic * frames to this duration. If packets are still lost after this maximum * duration, silence will be generated by the stream instead. Since the * PLC normally should have its own limit on the maximum duration of * synthetic frames to be generated (for PJMEDIA's PLC, the limit is * PJMEDIA_WSOLA_MAX_EXPAND_MSEC), we can set this value to a large number * to give additional flexibility should the PLC wants to do something * clever with the lost frames. * * Default: 240 ms */ #ifndef PJMEDIA_MAX_PLC_DURATION_MSEC # define PJMEDIA_MAX_PLC_DURATION_MSEC 240 #endif /** * Specify number of sound buffers. Larger number is better for sound * stability and to accommodate sound devices that are unable to send frames * in timely manner, however it would probably cause more audio delay (and * definitely will take more memory). One individual buffer is normally 10ms * or 20 ms long, depending on ptime settings (samples_per_frame value). * * The setting here currently is used by the conference bridge, the splitter * combiner port, and dsound.c. * * Default: (PJMEDIA_SND_DEFAULT_PLAY_LATENCY+20)/20 */ #ifndef PJMEDIA_SOUND_BUFFER_COUNT # define PJMEDIA_SOUND_BUFFER_COUNT ((PJMEDIA_SND_DEFAULT_PLAY_LATENCY+20)/20) #endif /** * Specify which A-law/U-law conversion algorithm to use. * By default the conversion algorithm uses A-law/U-law table which gives * the best performance, at the expense of 33 KBytes of static data. * If this option is disabled, a smaller but slower algorithm will be used. */ #ifndef PJMEDIA_HAS_ALAW_ULAW_TABLE # define PJMEDIA_HAS_ALAW_ULAW_TABLE 1 #endif /** * Unless specified otherwise, G711 codec is included by default. */ #ifndef PJMEDIA_HAS_G711_CODEC # define PJMEDIA_HAS_G711_CODEC 1 #endif /* * Warn about obsolete macros. * * PJMEDIA_HAS_SMALL_FILTER has been deprecated in 0.7. */ #if defined(PJMEDIA_HAS_SMALL_FILTER) # ifdef _MSC_VER # pragma message("Warning: PJMEDIA_HAS_SMALL_FILTER macro is deprecated"\ " and has no effect") # else # warning "PJMEDIA_HAS_SMALL_FILTER macro is deprecated and has no effect" # endif #endif /* * Warn about obsolete macros. * * PJMEDIA_HAS_LARGE_FILTER has been deprecated in 0.7. */ #if defined(PJMEDIA_HAS_LARGE_FILTER) # ifdef _MSC_VER # pragma message("Warning: PJMEDIA_HAS_LARGE_FILTER macro is deprecated"\ " and has no effect") # else # warning "PJMEDIA_HAS_LARGE_FILTER macro is deprecated" # endif #endif /* * These macros are obsolete in 0.7.1 so it will trigger compilation error. * Please use PJMEDIA_RESAMPLE_IMP to select the resample implementation * to use. */ #ifdef PJMEDIA_HAS_LIBRESAMPLE # error "PJMEDIA_HAS_LIBRESAMPLE macro is deprecated. Use '#define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_LIBRESAMPLE'" #endif #ifdef PJMEDIA_HAS_SPEEX_RESAMPLE # error "PJMEDIA_HAS_SPEEX_RESAMPLE macro is deprecated. Use '#define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_SPEEX'" #endif /* * Sample rate conversion backends. * Select one of these backends in PJMEDIA_RESAMPLE_IMP. */ #define PJMEDIA_RESAMPLE_NONE 1 /**< No resampling. */ #define PJMEDIA_RESAMPLE_LIBRESAMPLE 2 /**< Sample rate conversion using libresample. */ #define PJMEDIA_RESAMPLE_SPEEX 3 /**< Sample rate conversion using Speex. */ #define PJMEDIA_RESAMPLE_LIBSAMPLERATE 4 /**< Sample rate conversion using libsamplerate (a.k.a Secret Rabbit Code) */ /** * Select which resample implementation to use. Currently pjmedia supports: * - #PJMEDIA_RESAMPLE_LIBRESAMPLE, to use libresample-1.7, this is the default * implementation to be used. * - #PJMEDIA_RESAMPLE_LIBSAMPLERATE, to use libsamplerate implementation * (a.k.a. Secret Rabbit Code). * - #PJMEDIA_RESAMPLE_SPEEX, to use experimental sample rate conversion in * Speex library. * - #PJMEDIA_RESAMPLE_NONE, to disable sample rate conversion. Any calls to * resample function will return error. * * Default is PJMEDIA_RESAMPLE_LIBRESAMPLE */ #ifndef PJMEDIA_RESAMPLE_IMP # define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_LIBRESAMPLE #endif /** * Specify whether libsamplerate, when used, should be linked statically * into the application. This option is only useful for Visual Studio * projects, and when this static linking is enabled */ /** * Default file player/writer buffer size. */ #ifndef PJMEDIA_FILE_PORT_BUFSIZE # define PJMEDIA_FILE_PORT_BUFSIZE 4000 #endif /** * Maximum frame duration (in msec) to be supported. * This (among other thing) will affect the size of buffers to be allocated * for outgoing packets. */ #ifndef PJMEDIA_MAX_FRAME_DURATION_MS # define PJMEDIA_MAX_FRAME_DURATION_MS 200 #endif /** * Max packet size for transmitting direction. */ #ifndef PJMEDIA_MAX_MTU # define PJMEDIA_MAX_MTU 1500 #endif /** * Max packet size for receiving direction. */ #ifndef PJMEDIA_MAX_MRU # define PJMEDIA_MAX_MRU 2000 #endif /** * DTMF/telephone-event duration, in timestamp. */ #ifndef PJMEDIA_DTMF_DURATION # define PJMEDIA_DTMF_DURATION 1600 /* in timestamp */ #endif /** * Number of RTP packets received from different source IP address from the * remote address required to make the stream switch transmission * to the source address. */ #ifndef PJMEDIA_RTP_NAT_PROBATION_CNT # define PJMEDIA_RTP_NAT_PROBATION_CNT 10 #endif /** * Number of RTCP packets received from different source IP address from the * remote address required to make the stream switch RTCP transmission * to the source address. */ #ifndef PJMEDIA_RTCP_NAT_PROBATION_CNT # define PJMEDIA_RTCP_NAT_PROBATION_CNT 3 #endif /** * Specify whether RTCP should be advertised in SDP. This setting would * affect whether RTCP candidate will be added in SDP when ICE is used. * Application might want to disable RTCP advertisement in SDP to * reduce the message size. * * Default: 1 (yes) */ #ifndef PJMEDIA_ADVERTISE_RTCP # define PJMEDIA_ADVERTISE_RTCP 1 #endif /** * Interval to send RTCP packets, in msec */ #ifndef PJMEDIA_RTCP_INTERVAL # define PJMEDIA_RTCP_INTERVAL 5000 /* msec*/ #endif /** * Tell RTCP to ignore the first N packets when calculating the * jitter statistics. From experimentation, the first few packets * (25 or so) have relatively big jitter, possibly because during * this time, the program is also busy setting up the signaling, * so they make the average jitter big. * * Default: 25. */ #ifndef PJMEDIA_RTCP_IGNORE_FIRST_PACKETS # define PJMEDIA_RTCP_IGNORE_FIRST_PACKETS 25 #endif /** * Specify whether RTCP statistics includes raw jitter statistics. * Raw jitter is defined as absolute value of network transit time * difference of two consecutive packets; refering to "difference D" * term in interarrival jitter calculation in RFC 3550 section 6.4.1. * * Default: 0 (no). */ #ifndef PJMEDIA_RTCP_STAT_HAS_RAW_JITTER # define PJMEDIA_RTCP_STAT_HAS_RAW_JITTER 0 #endif /** * Specify the factor with wich RTCP RTT statistics should be normalized * if exceptionally high. For e.g. mobile networks with potentially large * fluctuations, this might be unwanted. * * Use (0) to disable this feature. * * Default: 3. */ #ifndef PJMEDIA_RTCP_NORMALIZE_FACTOR # define PJMEDIA_RTCP_NORMALIZE_FACTOR 3 #endif /** * Specify whether RTCP statistics includes IP Delay Variation statistics. * IPDV is defined as network transit time difference of two consecutive * packets. The IPDV statistic can be useful to inspect clock skew existance * and level, e.g: when the IPDV mean values were stable in positive numbers, * then the remote clock (used in sending RTP packets) is faster than local * system clock. Ideally, the IPDV mean values are always equal to 0. * * Default: 0 (no). */ #ifndef PJMEDIA_RTCP_STAT_HAS_IPDV # define PJMEDIA_RTCP_STAT_HAS_IPDV 0 #endif /** * Specify whether RTCP XR support should be built into PJMEDIA. Disabling * this feature will reduce footprint slightly. Note that even when this * setting is enabled, RTCP XR processing will only be performed in stream * if it is enabled on run-time on per stream basis. See * PJMEDIA_STREAM_ENABLE_XR setting for more info. * * Default: 0 (no). */ #ifndef PJMEDIA_HAS_RTCP_XR # define PJMEDIA_HAS_RTCP_XR 0 #endif /** * The RTCP XR feature is activated and used by stream if \a enable_rtcp_xr * field of \a pjmedia_stream_info structure is non-zero. This setting * controls the default value of this field. * * Default: 0 (disabled) */ #ifndef PJMEDIA_STREAM_ENABLE_XR # define PJMEDIA_STREAM_ENABLE_XR 0 #endif /** * Specify the buffer length for storing any received RTCP SDES text * in a stream session. Usually RTCP contains only the mandatory SDES * field, i.e: CNAME. * * Default: 64 bytes. */ #ifndef PJMEDIA_RTCP_RX_SDES_BUF_LEN # define PJMEDIA_RTCP_RX_SDES_BUF_LEN 64 #endif /** * Specify how long (in miliseconds) the stream should suspend the * silence detector/voice activity detector (VAD) during the initial * period of the session. This feature is useful to open bindings in * all NAT routers between local and remote endpoint since most NATs * do not allow incoming packet to get in before local endpoint sends * outgoing packets. * * Specify zero to disable this feature. * * Default: 600 msec (which gives good probability that some RTP * packets will reach the destination, but without * filling up the jitter buffer on the remote end). */ #ifndef PJMEDIA_STREAM_VAD_SUSPEND_MSEC # define PJMEDIA_STREAM_VAD_SUSPEND_MSEC 600 #endif /** * Perform RTP payload type checking in the stream. Normally the peer * MUST send RTP with payload type as we specified in our SDP. Certain * agents may not be able to follow this hence the only way to have * communication is to disable this check. * * Default: 1 */ #ifndef PJMEDIA_STREAM_CHECK_RTP_PT # define PJMEDIA_STREAM_CHECK_RTP_PT 1 #endif /** * Reserve some space for application extra data, e.g: SRTP auth tag, * in RTP payload, so the total payload length will not exceed the MTU. */ #ifndef PJMEDIA_STREAM_RESV_PAYLOAD_LEN # define PJMEDIA_STREAM_RESV_PAYLOAD_LEN 20 #endif /** * Specify the maximum duration of silence period in the codec, in msec. * This is useful for example to keep NAT binding open in the firewall * and to prevent server from disconnecting the call because no * RTP packet is received. * * This only applies to codecs that use PJMEDIA's VAD (pretty much * everything including iLBC, except Speex, which has its own DTX * mechanism). * * Use (-1) to disable this feature. * * Default: 5000 ms * */ #ifndef PJMEDIA_CODEC_MAX_SILENCE_PERIOD # define PJMEDIA_CODEC_MAX_SILENCE_PERIOD 5000 #endif /** * Suggested or default threshold to be set for fixed silence detection * or as starting threshold for adaptive silence detection. The threshold * has the range from zero to 0xFFFF. */ #ifndef PJMEDIA_SILENCE_DET_THRESHOLD # define PJMEDIA_SILENCE_DET_THRESHOLD 4 #endif /** * Maximum silence threshold in the silence detector. The silence detector * will not cut the audio transmission if the audio level is above this * level. * * Use 0x10000 (or greater) to disable this feature. * * Default: 0x10000 (disabled) */ #ifndef PJMEDIA_SILENCE_DET_MAX_THRESHOLD # define PJMEDIA_SILENCE_DET_MAX_THRESHOLD 0x10000 #endif /** * Speex Accoustic Echo Cancellation (AEC). * By default is enabled. */ #ifndef PJMEDIA_HAS_SPEEX_AEC # define PJMEDIA_HAS_SPEEX_AEC 1 #endif /** * Specify whether Automatic Gain Control (AGC) should also be enabled in * Speex AEC. * * Default: 1 (yes) */ #ifndef PJMEDIA_SPEEX_AEC_USE_AGC # define PJMEDIA_SPEEX_AEC_USE_AGC 1 #endif /** * Specify whether denoise should also be enabled in Speex AEC. * * Default: 1 (yes) */ #ifndef PJMEDIA_SPEEX_AEC_USE_DENOISE # define PJMEDIA_SPEEX_AEC_USE_DENOISE 1 #endif /** * Maximum number of parameters in SDP fmtp attribute. * * Default: 16 */ #ifndef PJMEDIA_CODEC_MAX_FMTP_CNT # define PJMEDIA_CODEC_MAX_FMTP_CNT 16 #endif /** * This specifies the behavior of the SDP negotiator when responding to an * offer, whether it should rather use the codec preference as set by * remote, or should it rather use the codec preference as specified by * local endpoint. * * For example, suppose incoming call has codec order "8 0 3", while * local codec order is "3 0 8". If remote codec order is preferable, * the selected codec will be 8, while if local codec order is preferable, * the selected codec will be 3. * * If set to non-zero, the negotiator will use the codec order as specified * by remote in the offer. * * Note that this behavior can be changed during run-time by calling * pjmedia_sdp_neg_set_prefer_remote_codec_order(). * * Default is 1 (to maintain backward compatibility) */ #ifndef PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER # define PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER 1 #endif /** * This specifies the behavior of the SDP negotiator when responding to an * offer, whether it should answer with multiple formats or not. * * Note that this behavior can be changed during run-time by calling * pjmedia_sdp_neg_set_allow_multiple_codecs(). * * Default is 0 (to maintain backward compatibility) */ #ifndef PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS # define PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS 0 #endif /** * This specifies the maximum number of the customized SDP format * negotiation callbacks. */ #ifndef PJMEDIA_SDP_NEG_MAX_CUSTOM_FMT_NEG_CB # define PJMEDIA_SDP_NEG_MAX_CUSTOM_FMT_NEG_CB 8 #endif /** * This specifies if the SDP negotiator should rewrite answer payload * type numbers to use the same payload type numbers as the remote offer * for all matched codecs. * * Default is 1 (yes) */ #ifndef PJMEDIA_SDP_NEG_ANSWER_SYMMETRIC_PT # define PJMEDIA_SDP_NEG_ANSWER_SYMMETRIC_PT 1 #endif /** * Support for sending and decoding RTCP port in SDP (RFC 3605). * Default is equal to PJMEDIA_ADVERTISE_RTCP setting. */ #ifndef PJMEDIA_HAS_RTCP_IN_SDP # define PJMEDIA_HAS_RTCP_IN_SDP (PJMEDIA_ADVERTISE_RTCP) #endif /** * This macro controls whether pjmedia should include SDP * bandwidth modifier "TIAS" (RFC3890). * * Note that there is also a run-time variable to turn this setting * on or off, defined in endpoint.c. To access this variable, use * the following construct * \verbatim extern pj_bool_t pjmedia_add_bandwidth_tias_in_sdp; // Do not enable bandwidth information inclusion in sdp pjmedia_add_bandwidth_tias_in_sdp = PJ_FALSE; \endverbatim * * Default: 1 (yes) */ #ifndef PJMEDIA_ADD_BANDWIDTH_TIAS_IN_SDP # define PJMEDIA_ADD_BANDWIDTH_TIAS_IN_SDP 1 #endif /** * This macro controls whether pjmedia should include SDP rtpmap * attribute for static payload types. SDP rtpmap for static * payload types are optional, although they are normally included * for interoperability reason. * * Note that there is also a run-time variable to turn this setting * on or off, defined in endpoint.c. To access this variable, use * the following construct * \verbatim extern pj_bool_t pjmedia_add_rtpmap_for_static_pt; // Do not include rtpmap for static payload types (<96) pjmedia_add_rtpmap_for_static_pt = PJ_FALSE; \endverbatim * * Default: 1 (yes) */ #ifndef PJMEDIA_ADD_RTPMAP_FOR_STATIC_PT # define PJMEDIA_ADD_RTPMAP_FOR_STATIC_PT 1 #endif /** * This macro declares the payload type for telephone-event * that is advertised by PJMEDIA for outgoing SDP. If this macro * is set to zero, telephone events would not be advertised nor * supported. * * If this value is changed to other number, please update the * PJMEDIA_RTP_PT_TELEPHONE_EVENTS_STR too. */ #ifndef PJMEDIA_RTP_PT_TELEPHONE_EVENTS # define PJMEDIA_RTP_PT_TELEPHONE_EVENTS 96 #endif /** * Macro to get the string representation of the telephone-event * payload type. */ #ifndef PJMEDIA_RTP_PT_TELEPHONE_EVENTS_STR # define PJMEDIA_RTP_PT_TELEPHONE_EVENTS_STR "96" #endif /** * Maximum tones/digits that can be enqueued in the tone generator. */ #ifndef PJMEDIA_TONEGEN_MAX_DIGITS # define PJMEDIA_TONEGEN_MAX_DIGITS 32 #endif /* * Below specifies the various tone generator backend algorithm. */ /** * The math's sine(), floating point. This has very good precision * but it's the slowest and requires floating point support and * linking with the math library. */ #define PJMEDIA_TONEGEN_SINE 1 /** * Floating point approximation of sine(). This has relatively good * precision and much faster than plain sine(), but it requires floating- * point support and linking with the math library. */ #define PJMEDIA_TONEGEN_FLOATING_POINT 2 /** * Fixed point using sine signal generated by Cordic algorithm. This * algorithm can be tuned to provide balance between precision and * performance by tuning the PJMEDIA_TONEGEN_FIXED_POINT_CORDIC_LOOP * setting, and may be suitable for platforms that lack floating-point * support. */ #define PJMEDIA_TONEGEN_FIXED_POINT_CORDIC 3 /** * Fast fixed point using some approximation to generate sine waves. * The tone generated by this algorithm is not very precise, however * the algorithm is very fast. */ #define PJMEDIA_TONEGEN_FAST_FIXED_POINT 4 /** * Specify the tone generator algorithm to be used. Please see * http://trac.pjsip.org/repos/wiki/Tone_Generator for the performance * analysis results of the various tone generator algorithms. * * Default value: * - PJMEDIA_TONEGEN_FLOATING_POINT when PJ_HAS_FLOATING_POINT is set * - PJMEDIA_TONEGEN_FIXED_POINT_CORDIC when PJ_HAS_FLOATING_POINT is not set */ #ifndef PJMEDIA_TONEGEN_ALG # if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT # define PJMEDIA_TONEGEN_ALG PJMEDIA_TONEGEN_FLOATING_POINT # else # define PJMEDIA_TONEGEN_ALG PJMEDIA_TONEGEN_FIXED_POINT_CORDIC # endif #endif /** * Specify the number of calculation loops to generate the tone, when * PJMEDIA_TONEGEN_FIXED_POINT_CORDIC algorithm is used. With more calculation * loops, the tone signal gets more precise, but this will add more * processing. * * Valid values are 1 to 28. * * Default value: 10 */ #ifndef PJMEDIA_TONEGEN_FIXED_POINT_CORDIC_LOOP # define PJMEDIA_TONEGEN_FIXED_POINT_CORDIC_LOOP 10 #endif /** * Enable high quality of tone generation, the better quality will cost * more CPU load. This is only applied to floating point enabled machines. * * By default it is enabled when PJ_HAS_FLOATING_POINT is set. * * This macro has been deprecated in version 1.0-rc3. */ #ifdef PJMEDIA_USE_HIGH_QUALITY_TONEGEN # error "The PJMEDIA_USE_HIGH_QUALITY_TONEGEN macro is obsolete" #endif /** * Fade-in duration for the tone, in milliseconds. Set to zero to disable * this feature. * * Default: 1 (msec) */ #ifndef PJMEDIA_TONEGEN_FADE_IN_TIME # define PJMEDIA_TONEGEN_FADE_IN_TIME 1 #endif /** * Fade-out duration for the tone, in milliseconds. Set to zero to disable * this feature. * * Default: 2 (msec) */ #ifndef PJMEDIA_TONEGEN_FADE_OUT_TIME # define PJMEDIA_TONEGEN_FADE_OUT_TIME 2 #endif /** * The default tone generator amplitude (1-32767). * * Default value: 12288 */ #ifndef PJMEDIA_TONEGEN_VOLUME # define PJMEDIA_TONEGEN_VOLUME 12288 #endif /** * Enable support for SRTP media transport. This will require linking * with libsrtp from the third_party directory. * * By default it is enabled. */ #ifndef PJMEDIA_HAS_SRTP # define PJMEDIA_HAS_SRTP 1 #endif /** * Let the library handle libsrtp initialization and deinitialization. * Application may want to disable this and manually perform libsrtp * initialization and deinitialization when it needs to use libsrtp * before the library is initialized or after the library is shutdown. * * By default it is enabled. */ #ifndef PJMEDIA_LIBSRTP_AUTO_INIT_DEINIT # define PJMEDIA_LIBSRTP_AUTO_INIT_DEINIT 1 #endif /** * Enable support to handle codecs with inconsistent clock rate * between clock rate in SDP/RTP & the clock rate that is actually used. * This happens for example with G.722 and MPEG audio codecs. * See: * - G.722 : RFC 3551 4.5.2 * - MPEG audio : RFC 3551 4.5.13 & RFC 3119 * * Also when this feature is enabled, some handling will be performed * to deal with clock rate incompatibilities of some phones. * * By default it is enabled. */ #ifndef PJMEDIA_HANDLE_G722_MPEG_BUG # define PJMEDIA_HANDLE_G722_MPEG_BUG 1 #endif /** * Transport info (pjmedia_transport_info) contains a socket info and list * of transport specific info, since transports can be chained together * (for example, SRTP transport uses UDP transport as the underlying * transport). This constant specifies maximum number of transport specific * infos that can be held in a transport info. */ #ifndef PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXCNT # define PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXCNT 4 #endif /** * Maximum size in bytes of storage buffer of a transport specific info. */ #ifndef PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXSIZE # define PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXSIZE (36*sizeof(long)) #endif /** * Value to be specified in PJMEDIA_STREAM_ENABLE_KA setting. * This indicates that an empty RTP packet should be used as * the keep-alive packet. */ #define PJMEDIA_STREAM_KA_EMPTY_RTP 1 /** * Value to be specified in PJMEDIA_STREAM_ENABLE_KA setting. * This indicates that a user defined packet should be used * as the keep-alive packet. The content of the user-defined * packet is specified by PJMEDIA_STREAM_KA_USER_PKT. Default * content is a CR-LF packet. */ #define PJMEDIA_STREAM_KA_USER 2 /** * The content of the user defined keep-alive packet. The format * of the packet is initializer to pj_str_t structure. Note that * the content may contain NULL character. */ #ifndef PJMEDIA_STREAM_KA_USER_PKT # define PJMEDIA_STREAM_KA_USER_PKT { "\r\n", 2 } #endif /** * Specify another type of keep-alive and NAT hole punching * mechanism (the other type is PJMEDIA_STREAM_VAD_SUSPEND_MSEC * and PJMEDIA_CODEC_MAX_SILENCE_PERIOD) to be used by stream. * When this feature is enabled, the stream will initially * transmit one packet to punch a hole in NAT, and periodically * transmit keep-alive packets. * * When this alternative keep-alive mechanism is used, application * may disable the other keep-alive mechanisms, i.e: by setting * PJMEDIA_STREAM_VAD_SUSPEND_MSEC to zero and * PJMEDIA_CODEC_MAX_SILENCE_PERIOD to -1. * * The value of this macro specifies the type of packet used * for the keep-alive mechanism. Valid values are * PJMEDIA_STREAM_KA_EMPTY_RTP and PJMEDIA_STREAM_KA_USER. * * The duration of the keep-alive interval further can be set * with PJMEDIA_STREAM_KA_INTERVAL setting. * * Default: 0 (disabled) */ #ifndef PJMEDIA_STREAM_ENABLE_KA # define PJMEDIA_STREAM_ENABLE_KA 0 #endif /** * Specify the keep-alive interval of PJMEDIA_STREAM_ENABLE_KA * mechanism, in seconds. * * Default: 5 seconds */ #ifndef PJMEDIA_STREAM_KA_INTERVAL # define PJMEDIA_STREAM_KA_INTERVAL 5 #endif /* * .... new stuffs ... */ /* * Video */ /** * Top level option to enable/disable video features. * * Default: 1 (enabled) */ #ifndef PJMEDIA_HAS_VIDEO # define PJMEDIA_HAS_VIDEO 1 #endif /** * 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 libavcore is available. * * Default: PJMEDIA_HAS_FFMPEG (or detected by configure) */ #ifndef PJMEDIA_HAS_LIBAVCORE # define PJMEDIA_HAS_LIBAVCORE PJMEDIA_HAS_FFMPEG #endif /** * Specify if libvpx is available. * * Default: 0 (or detected by configure) */ #ifndef PJMEDIA_HAS_LIBVPX # define PJMEDIA_HAS_LIBVPX 0 #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 /** * Specify the maximum time difference (in ms) for synchronization between * two medias. If the synchronization media source is ahead of time * greater than this duration, it is considered to make a very large jump * and the synchronization will be reset. * * Default: 20000 */ #ifndef PJMEDIA_CLOCK_SYNC_MAX_SYNC_MSEC # define PJMEDIA_CLOCK_SYNC_MAX_SYNC_MSEC 20000 #endif /** * Maximum video frame size. * Default: 128kB */ #ifndef PJMEDIA_MAX_VIDEO_ENC_FRAME_SIZE # define PJMEDIA_MAX_VIDEO_ENC_FRAME_SIZE (1<<17) #endif /** * Specify the maximum duration (in ms) for resynchronization. When a media * is late to another media it is supposed to be synchronized to, it is * guaranteed to be synchronized again after this duration. While if the * media is ahead/early by t ms, it is guaranteed to be synchronized after * t + this duration. This timing only applies if there is no additional * resynchronization required during the specified duration. * * Default: 2000 */ #ifndef PJMEDIA_CLOCK_SYNC_MAX_RESYNC_DURATION # define PJMEDIA_CLOCK_SYNC_MAX_RESYNC_DURATION 2000 #endif /** * Minimum gap between two consecutive discards in jitter buffer, * in milliseconds. * * Default: 200 ms */ #ifndef PJMEDIA_JBUF_DISC_MIN_GAP # define PJMEDIA_JBUF_DISC_MIN_GAP 200 #endif /** * Minimum burst level reference used for calculating discard duration * in jitter buffer progressive discard algorithm, in frames. * * Default: 1 frame */ #ifndef PJMEDIA_JBUF_PRO_DISC_MIN_BURST # define PJMEDIA_JBUF_PRO_DISC_MIN_BURST 1 #endif /** * Maximum burst level reference used for calculating discard duration * in jitter buffer progressive discard algorithm, in frames. * * Default: 200 frames */ #ifndef PJMEDIA_JBUF_PRO_DISC_MAX_BURST # define PJMEDIA_JBUF_PRO_DISC_MAX_BURST 100 #endif /** * Duration for progressive discard algotithm in jitter buffer to discard * an excessive frame when burst is equal to or lower than * PJMEDIA_JBUF_PRO_DISC_MIN_BURST, in milliseconds. * * Default: 2000 ms */ #ifndef PJMEDIA_JBUF_PRO_DISC_T1 # define PJMEDIA_JBUF_PRO_DISC_T1 2000 #endif /** * Duration for progressive discard algotithm in jitter buffer to discard * an excessive frame when burst is equal to or greater than * PJMEDIA_JBUF_PRO_DISC_MAX_BURST, in milliseconds. * * Default: 10000 ms */ #ifndef PJMEDIA_JBUF_PRO_DISC_T2 # define PJMEDIA_JBUF_PRO_DISC_T2 10000 #endif /** * Video stream will discard old picture from the jitter buffer as soon as * new picture is received, to reduce latency. * * Default: 0 */ #ifndef PJMEDIA_VID_STREAM_SKIP_PACKETS_TO_REDUCE_LATENCY # define PJMEDIA_VID_STREAM_SKIP_PACKETS_TO_REDUCE_LATENCY 0 #endif /** * Maximum video payload size. Note that this must not be greater than * PJMEDIA_MAX_MTU. * * Default: (PJMEDIA_MAX_MTU - 100) */ #ifndef PJMEDIA_MAX_VID_PAYLOAD_SIZE # define PJMEDIA_MAX_VID_PAYLOAD_SIZE (PJMEDIA_MAX_MTU - 100) #endif /** * Specify target value for socket receive buffer size. It will be * applied to RTP socket of media transport using setsockopt(). When * transport failed to set the specified size, it will try with lower * value until the highest possible is successfully set. * * Setting this to zero will leave the socket receive buffer size to * OS default (e.g: usually 8 KB on desktop platforms). * * Default: 64 KB when video is enabled, otherwise zero (OS default) */ #ifndef PJMEDIA_TRANSPORT_SO_RCVBUF_SIZE # if PJMEDIA_HAS_VIDEO # define PJMEDIA_TRANSPORT_SO_RCVBUF_SIZE (64*1024) # else # define PJMEDIA_TRANSPORT_SO_RCVBUF_SIZE 0 # endif #endif /** * Specify target value for socket send buffer size. It will be * applied to RTP socket of media transport using setsockopt(). When * transport failed to set the specified size, it will try with lower * value until the highest possible is successfully set. * * Setting this to zero will leave the socket send buffer size to * OS default (e.g: usually 8 KB on desktop platforms). * * Default: 64 KB when video is enabled, otherwise zero (OS default) */ #ifndef PJMEDIA_TRANSPORT_SO_SNDBUF_SIZE # if PJMEDIA_HAS_VIDEO # define PJMEDIA_TRANSPORT_SO_SNDBUF_SIZE (64*1024) # else # define PJMEDIA_TRANSPORT_SO_SNDBUF_SIZE 0 # endif #endif /** * Specify if libyuv is available. * * Default: 0 (disable) */ #ifndef PJMEDIA_HAS_LIBYUV # define PJMEDIA_HAS_LIBYUV 0 #endif /** * Specify if dtmf flash in RFC 2833 is available. */ #ifndef PJMEDIA_HAS_DTMF_FLASH # define PJMEDIA_HAS_DTMF_FLASH 1 #endif /** * @} */ #endif /* __PJMEDIA_CONFIG_H__ */ diff --git a/deps/pjsip/pjmedia/include/pjmedia/echo.h b/deps/pjsip/pjmedia/include/pjmedia/echo.h index 6dce104e..a69e8095 100644 --- a/deps/pjsip/pjmedia/include/pjmedia/echo.h +++ b/deps/pjsip/pjmedia/include/pjmedia/echo.h @@ -1,266 +1,266 @@ -/* $Id: echo.h 4082 2012-04-24 13:09:14Z bennylp $ */ +/* $Id: echo.h 5186 2015-10-06 05:57:51Z ming $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef __PJMEDIA_ECHO_H__ #define __PJMEDIA_ECHO_H__ /** * @file echo.h * @brief Echo Cancellation API. */ #include /** * @defgroup PJMEDIA_Echo_Cancel Accoustic Echo Cancellation API * @ingroup PJMEDIA_PORT * @brief Echo Cancellation API. * @{ * * This section describes API to perform echo cancellation to audio signal. * There may be multiple echo canceller implementation in PJMEDIA, ranging * from simple echo suppressor to a full Accoustic Echo Canceller/AEC. By * using this API, application should be able to use which EC backend to * use base on the requirement and capability of the platform. */ PJ_BEGIN_DECL /** * Opaque type for PJMEDIA Echo Canceller state. */ typedef struct pjmedia_echo_state pjmedia_echo_state; /** * Echo cancellation options. */ typedef enum pjmedia_echo_flag { /** * Use any available backend echo canceller algorithm. This is * the default settings. This setting is mutually exclusive with * PJMEDIA_ECHO_SIMPLE and PJMEDIA_ECHO_SPEEX. */ PJMEDIA_ECHO_DEFAULT= 0, /** * Force to use Speex AEC as the backend echo canceller algorithm. * This setting is mutually exclusive with PJMEDIA_ECHO_SIMPLE. */ PJMEDIA_ECHO_SPEEX = 1, /** * If PJMEDIA_ECHO_SIMPLE flag is specified during echo canceller * creation, then a simple echo suppressor will be used instead of * an accoustic echo cancellation. This setting is mutually exclusive * with PJMEDIA_ECHO_SPEEX. */ PJMEDIA_ECHO_SIMPLE = 2, /** * Force to use WebRTC AEC as the backend echo canceller algorithm. * This setting is mutually exclusive with PJMEDIA_ECHO_SIMPLE & PJMEDIA_ECHO_SPEEX. */ PJMEDIA_ECHO_WEBRTC = 3, /** * For internal use. */ PJMEDIA_ECHO_ALGO_MASK = 15, /** * If PJMEDIA_ECHO_NO_LOCK flag is specified, no mutex will be created * for the echo canceller, but application will guarantee that echo * canceller will not be called by different threads at the same time. */ PJMEDIA_ECHO_NO_LOCK = 16, /** * If PJMEDIA_ECHO_USE_SIMPLE_FIFO flag is specified, the delay buffer * created for the echo canceller will use simple FIFO mechanism, i.e. * without using WSOLA to expand and shrink audio samples. */ PJMEDIA_ECHO_USE_SIMPLE_FIFO = 32, /** * If PJMEDIA_ECHO_USE_SW_ECHO flag is specified, software echo canceller * will be used instead of device EC. */ PJMEDIA_ECHO_USE_SW_ECHO = 64 } pjmedia_echo_flag; /** * Create the echo canceller. * * @param pool Pool to allocate memory. * @param clock_rate Media clock rate/sampling rate. * @param samples_per_frame Number of samples per frame. * @param tail_ms Tail length, miliseconds. * @param latency_ms Total lacency introduced by playback and * recording device. Set to zero if the latency * is not known. * @param options Options. If PJMEDIA_ECHO_SIMPLE is specified, * then a simple echo suppressor implementation * will be used instead of an accoustic echo * cancellation. * See #pjmedia_echo_flag for other options. * @param p_echo Pointer to receive the Echo Canceller state. * * @return PJ_SUCCESS on success, or the appropriate status. */ PJ_DECL(pj_status_t) pjmedia_echo_create(pj_pool_t *pool, unsigned clock_rate, unsigned samples_per_frame, unsigned tail_ms, unsigned latency_ms, unsigned options, pjmedia_echo_state **p_echo ); /** * Create multi-channel the echo canceller. * * @param pool Pool to allocate memory. * @param clock_rate Media clock rate/sampling rate. * @param channel_count Number of channels. * @param samples_per_frame Number of samples per frame. * @param tail_ms Tail length, miliseconds. * @param latency_ms Total lacency introduced by playback and * recording device. Set to zero if the latency * is not known. * @param options Options. If PJMEDIA_ECHO_SIMPLE is specified, * then a simple echo suppressor implementation * will be used instead of an accoustic echo * cancellation. * See #pjmedia_echo_flag for other options. * @param p_echo Pointer to receive the Echo Canceller state. * * @return PJ_SUCCESS on success, or the appropriate status. */ PJ_DECL(pj_status_t) pjmedia_echo_create2(pj_pool_t *pool, unsigned clock_rate, unsigned channel_count, unsigned samples_per_frame, unsigned tail_ms, unsigned latency_ms, unsigned options, pjmedia_echo_state **p_echo ); /** * Destroy the Echo Canceller. * * @param echo The Echo Canceller. * * @return PJ_SUCCESS on success. */ PJ_DECL(pj_status_t) pjmedia_echo_destroy(pjmedia_echo_state *echo ); /** * Reset the echo canceller. * * @param echo The Echo Canceller. * * @return PJ_SUCCESS on success. */ PJ_DECL(pj_status_t) pjmedia_echo_reset(pjmedia_echo_state *echo ); /** * Let the Echo Canceller know that a frame has been played to the speaker. * The Echo Canceller will keep the frame in its internal buffer, to be used * when cancelling the echo with #pjmedia_echo_capture(). * * @param echo The Echo Canceller. * @param play_frm Sample buffer containing frame to be played * (or has been played) to the playback device. * The frame must contain exactly samples_per_frame * number of samples. * * @return PJ_SUCCESS on success. */ PJ_DECL(pj_status_t) pjmedia_echo_playback(pjmedia_echo_state *echo, pj_int16_t *play_frm ); /** * Let the Echo Canceller know that a frame has been captured from the * microphone. The Echo Canceller will cancel the echo from the captured * signal, using the internal buffer (supplied by #pjmedia_echo_playback()) * as the FES (Far End Speech) reference. * * @param echo The Echo Canceller. * @param rec_frm On input, it contains the input signal (captured * from microphone) which echo is to be removed. * Upon returning this function, this buffer contain * the processed signal with the echo removed. * The frame must contain exactly samples_per_frame * number of samples. * @param options Echo cancellation options, reserved for future use. * Put zero for now. * * @return PJ_SUCCESS on success. */ PJ_DECL(pj_status_t) pjmedia_echo_capture(pjmedia_echo_state *echo, pj_int16_t *rec_frm, unsigned options ); /** * Perform echo cancellation. * * @param echo The Echo Canceller. * @param rec_frm On input, it contains the input signal (captured * from microphone) which echo is to be removed. * Upon returning this function, this buffer contain * the processed signal with the echo removed. * @param play_frm Sample buffer containing frame to be played * (or has been played) to the playback device. * The frame must contain exactly samples_per_frame * number of samples. * @param options Echo cancellation options, reserved for future use. * Put zero for now. * @param reserved Reserved for future use, put NULL for now. * * @return PJ_SUCCESS on success. */ PJ_DECL(pj_status_t) pjmedia_echo_cancel( pjmedia_echo_state *echo, pj_int16_t *rec_frm, const pj_int16_t *play_frm, unsigned options, void *reserved ); PJ_END_DECL /** * @} */ #endif /* __PJMEDIA_ECHO_H__ */ diff --git a/deps/pjsip/pjmedia/include/pjmedia/event.h b/deps/pjsip/pjmedia/include/pjmedia/event.h index d5f20618..5aaa69af 100644 --- a/deps/pjsip/pjmedia/include/pjmedia/event.h +++ b/deps/pjsip/pjmedia/include/pjmedia/event.h @@ -1,405 +1,405 @@ -/* $Id: event.h 3905 2011-12-09 05:15:39Z ming $ */ +/* $Id: event.h 4815 2014-04-10 10:01:07Z bennylp $ */ /* * Copyright (C) 2011-2011 Teluu Inc. (http://www.teluu.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef __PJMEDIA_EVENT_H__ #define __PJMEDIA_EVENT_H__ /** * @file pjmedia/event.h * @brief Event framework */ #include #include PJ_BEGIN_DECL /** * @defgroup PJMEDIA_EVENT Event Framework * @brief PJMEDIA event framework * @{ */ /** * This enumeration describes list of media events. */ typedef enum pjmedia_event_type { /** * No event. */ PJMEDIA_EVENT_NONE, /** * Media format has changed event. */ PJMEDIA_EVENT_FMT_CHANGED = PJMEDIA_FOURCC('F', 'M', 'C', 'H'), /** * Video window is being closed. */ PJMEDIA_EVENT_WND_CLOSING = PJMEDIA_FOURCC('W', 'N', 'C', 'L'), /** * Video window has been closed event. */ PJMEDIA_EVENT_WND_CLOSED = PJMEDIA_FOURCC('W', 'N', 'C', 'O'), /** * Video window has been resized event. */ PJMEDIA_EVENT_WND_RESIZED = PJMEDIA_FOURCC('W', 'N', 'R', 'Z'), /** * Mouse button has been pressed event. */ PJMEDIA_EVENT_MOUSE_BTN_DOWN = PJMEDIA_FOURCC('M', 'S', 'D', 'N'), /** * Video keyframe has just been decoded event. */ PJMEDIA_EVENT_KEYFRAME_FOUND = PJMEDIA_FOURCC('I', 'F', 'R', 'F'), /** * Video decoding error due to missing keyframe event. */ PJMEDIA_EVENT_KEYFRAME_MISSING = PJMEDIA_FOURCC('I', 'F', 'R', 'M'), /** * Remote video decoder asked for a keyframe. */ PJMEDIA_EVENT_KEYFRAME_REQUESTED = PJMEDIA_FOURCC('I', 'F', 'R', 'R'), /** * Video orientation has been changed event. */ PJMEDIA_EVENT_ORIENT_CHANGED = PJMEDIA_FOURCC('O', 'R', 'N', 'T') } pjmedia_event_type; /** * Additional data/parameters for media format changed event * (PJMEDIA_EVENT_FMT_CHANGED). */ typedef struct pjmedia_event_fmt_changed_data { /** The media flow direction */ pjmedia_dir dir; /** The new media format. */ pjmedia_format new_fmt; } pjmedia_event_fmt_changed_data; /** * Additional data/parameters are not needed. */ typedef struct pjmedia_event_dummy_data { /** Dummy data */ int dummy; } pjmedia_event_dummy_data; /** * Additional data/parameters for window resized event * (PJMEDIA_EVENT_WND_RESIZED). */ typedef struct pjmedia_event_wnd_resized_data { /** * The new window size. */ pjmedia_rect_size new_size; } pjmedia_event_wnd_resized_data; /** * Additional data/parameters for window closing event. */ typedef struct pjmedia_event_wnd_closing_data { /** Consumer may set this field to PJ_TRUE to cancel the closing */ pj_bool_t cancel; } pjmedia_event_wnd_closing_data; /** Additional parameters for window changed event. */ typedef pjmedia_event_dummy_data pjmedia_event_wnd_closed_data; /** Additional parameters for mouse button down event */ typedef pjmedia_event_dummy_data pjmedia_event_mouse_btn_down_data; /** Additional parameters for keyframe found event */ typedef pjmedia_event_dummy_data pjmedia_event_keyframe_found_data; /** Additional parameters for keyframe missing event */ typedef pjmedia_event_dummy_data pjmedia_event_keyframe_missing_data; /** * Maximum size of additional parameters section in pjmedia_event structure */ #define PJMEDIA_EVENT_DATA_MAX_SIZE sizeof(pjmedia_event_fmt_changed_data) /** Type of storage to hold user data in pjmedia_event structure */ typedef char pjmedia_event_user_data[PJMEDIA_EVENT_DATA_MAX_SIZE]; /** * This structure describes a media event. It consists mainly of the event * type and additional data/parameters for the event. Applications can * use #pjmedia_event_init() to initialize this event structure with * basic information about the event. */ typedef struct pjmedia_event { /** * The event type. */ pjmedia_event_type type; /** * The media timestamp when the event occurs. */ pj_timestamp timestamp; /** * Pointer information about the source of this event. This field * is provided mainly for comparison purpose so that event subscribers * can check which source the event originated from. Usage of this * pointer for other purpose may require special care such as mutex * locking or checking whether the object is already destroyed. */ const void *src; /** * Pointer information about the publisher of this event. This field * is provided mainly for comparison purpose so that event subscribers * can check which object published the event. Usage of this * pointer for other purpose may require special care such as mutex * locking or checking whether the object is already destroyed. */ const void *epub; /** * Additional data/parameters about the event. The type of data * will be specific to the event type being reported. */ union { /** Media format changed event data. */ pjmedia_event_fmt_changed_data fmt_changed; /** Window resized event data */ pjmedia_event_wnd_resized_data wnd_resized; /** Window closing event data. */ pjmedia_event_wnd_closing_data wnd_closing; /** Window closed event data */ pjmedia_event_wnd_closed_data wnd_closed; /** Mouse button down event data */ pjmedia_event_mouse_btn_down_data mouse_btn_down; /** Keyframe found event data */ pjmedia_event_keyframe_found_data keyframe_found; /** Keyframe missing event data */ pjmedia_event_keyframe_missing_data keyframe_missing; /** Storage for user event data */ pjmedia_event_user_data user; /** Pointer to storage to user event data, if it's outside * this struct */ void *ptr; } data; } pjmedia_event; /** * The callback to receive media events. * * @param event The media event. * @param user_data The user data associated with the callback. * * @return If the callback returns non-PJ_SUCCESS, this return * code may be propagated back to the caller. */ typedef pj_status_t pjmedia_event_cb(pjmedia_event *event, void *user_data); /** * This enumeration describes flags for event publication via * #pjmedia_event_publish(). */ typedef enum pjmedia_event_publish_flag { /** * Default flag. */ PJMEDIA_EVENT_PUBLISH_DEFAULT, /** * Publisher will only post the event to the event manager. It is the * event manager that will later notify all the publisher's subscribers. */ PJMEDIA_EVENT_PUBLISH_POST_EVENT = 1 } pjmedia_event_publish_flag; /** * Event manager flag. */ typedef enum pjmedia_event_mgr_flag { /** * Tell the event manager not to create any event worker thread. */ PJMEDIA_EVENT_MGR_NO_THREAD = 1 } pjmedia_event_mgr_flag; /** * Opaque data type for event manager. Typically, the event manager * is a singleton instance, although application may instantiate more than one * instances of this if required. */ typedef struct pjmedia_event_mgr pjmedia_event_mgr; /** * Create a new event 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 options Options. Bitmask flags from #pjmedia_event_mgr_flag * @param mgr Pointer to hold the created instance of the * event manager. * * @return PJ_SUCCESS on success or the appropriate error code. */ PJ_DECL(pj_status_t) pjmedia_event_mgr_create(pj_pool_t *pool, unsigned options, pjmedia_event_mgr **mgr); /** * Get the singleton instance of the event manager. * * @return The instance. */ PJ_DECL(pjmedia_event_mgr*) pjmedia_event_mgr_instance(void); /** * Manually assign a specific event 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_event_mgr_set_instance(pjmedia_event_mgr *mgr); /** * Destroy an event manager. If the manager happens to be the singleton * instance, the singleton instance will be set to NULL. * * @param mgr The eventmanager. Specify NULL to use * the singleton instance. */ PJ_DECL(void) pjmedia_event_mgr_destroy(pjmedia_event_mgr *mgr); /** * Initialize event structure with basic data about the event. * * @param event The event to be initialized. * @param type The event type to be set for this event. * @param ts Event timestamp. May be set to NULL to set the event * timestamp to zero. * @param src Event source. */ PJ_DECL(void) pjmedia_event_init(pjmedia_event *event, pjmedia_event_type type, const pj_timestamp *ts, const void *src); /** * Subscribe a callback function to events published by the specified * publisher. Note that the subscriber may receive not only events emitted by * the specific publisher specified in the argument, but also from other * publishers contained by the publisher, if the publisher is republishing * events from other publishers. * * @param mgr The event manager. * @param cb The callback function to receive the event. * @param user_data The user data to be associated with the callback * function. * @param epub The event publisher. * * @return PJ_SUCCESS on success or the appropriate error code. */ PJ_DECL(pj_status_t) pjmedia_event_subscribe(pjmedia_event_mgr *mgr, pjmedia_event_cb *cb, void *user_data, void *epub); /** * Unsubscribe the callback associated with the user data from a publisher. * If the user data is not specified, this function will do the * unsubscription for all user data. If the publisher, epub, is not * specified, this function will do the unsubscription from all publishers. * * @param mgr The event manager. * @param cb The callback function. * @param user_data The user data associated with the callback * function, can be NULL. * @param epub The event publisher, can be NULL. * * @return PJ_SUCCESS on success or the appropriate error code. */ PJ_DECL(pj_status_t) pjmedia_event_unsubscribe(pjmedia_event_mgr *mgr, pjmedia_event_cb *cb, void *user_data, void *epub); /** * Publish the specified event to all subscribers of the specified event * publisher. By default, the function will call all the subcribers' * callbacks immediately. If the publisher uses the flag * PJMEDIA_EVENT_PUBLISH_POST_EVENT, publisher will only post the event * to the event manager and return immediately. It is the event manager * that will later notify all the publisher's subscribers. * * @param mgr The event manager. * @param epub The event publisher. * @param event The event to be published. * @param flag Publication flag. * * @return PJ_SUCCESS only if all subscription callbacks returned * PJ_SUCCESS. */ PJ_DECL(pj_status_t) pjmedia_event_publish(pjmedia_event_mgr *mgr, void *epub, pjmedia_event *event, pjmedia_event_publish_flag flag); /** * @} PJMEDIA_EVENT */ PJ_END_DECL #endif /* __PJMEDIA_EVENT_H__ */ diff --git a/deps/pjsip/pjmedia/include/pjmedia/format.h b/deps/pjsip/pjmedia/include/pjmedia/format.h index 3b6fb036..b776f8fd 100644 --- a/deps/pjsip/pjmedia/include/pjmedia/format.h +++ b/deps/pjsip/pjmedia/include/pjmedia/format.h @@ -1,794 +1,794 @@ -/* $Id: format.h 4470 2013-04-15 10:40:26Z bennylp $ */ +/* $Id: format.h 4994 2015-03-17 04:02:44Z nanang $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef __PJMEDIA_FORMAT_H__ #define __PJMEDIA_FORMAT_H__ /** * @file pjmedia/format.h Media format * @brief Media format */ #include /** * @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) PJMEDIA_FOURCC(C1, C2, C3, C4) /** * 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_ARGB = PJMEDIA_FORMAT_PACK('A', 'R', 'G', 'B'), 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 planar 4:4:4/24bpp RGB format, the data can be treated as * three planes of color components, where the first plane contains * only the G samples, the second plane contains only the B samples, * and the third plane contains only the R samples. */ PJMEDIA_FORMAT_GBRP = PJMEDIA_FORMAT_PACK('G', 'B', 'R', 'P'), /** * 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:0/12bpp YUV format, similar to I420 or IYUV but * the U (Cb) and V (Cr) planes order is switched, i.e: the second plane * contains the V (Cb) samples and the third plane contains the V (Cr) * samples. */ PJMEDIA_FORMAT_YV12 = PJMEDIA_FORMAT_PACK('Y', 'V', '1', '2'), /** * This is planar 4:2:0/12bpp YUV format, the data can be treated as * two planes of color components, where the first plane contains * only the Y samples, the second plane contains interleaved * V (Cr) - U (Cb) samples. */ PJMEDIA_FORMAT_NV21 = PJMEDIA_FORMAT_PACK('N', 'V', '2', '1'), /** * This is planar 4:2:2/16bpp 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_I422 = PJMEDIA_FORMAT_PACK('I', '4', '2', '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_H263P = PJMEDIA_FORMAT_PACK('P', '2', '6', '3'), PJMEDIA_FORMAT_H264 = PJMEDIA_FORMAT_PACK('H', '2', '6', '4'), 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_VP8 = PJMEDIA_FORMAT_PACK('L', 'V', 'P', '8'), } 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)(1.0*usec_ptime * clock_rate * channel_count / 1000000)); #else return ((unsigned)(usec_ptime / 1000L * clock_rate * \ channel_count / 1000)); #endif } /** * Variant of #PJMEDIA_SPF() which takes frame rate instead of ptime. */ PJ_INLINE(unsigned) PJMEDIA_SPF2(unsigned clock_rate, const pjmedia_ratio *fr, unsigned channel_count) { #if PJ_HAS_INT64 return ((unsigned)((pj_uint64_t)clock_rate * fr->denum \ / fr->num / channel_count)); #elif PJ_HAS_FLOATING_POINT return ((unsigned)(1.0* clock_rate * fr->denum / fr->num /channel_count)); #else return ((unsigned)(1L * clock_rate * fr->denum / fr->num / channel_count)); #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)((pj_uint64_t)bps * usec_ptime / PJ_UINT64(8000000))); #elif PJ_HAS_FLOATING_POINT return ((unsigned)(1.0 * bps * usec_ptime / 8000000.0)); #else return ((unsigned)(bps / 8L * usec_ptime / 1000000)); #endif } /** * General utility routine to calculate ptime value from frame rate. * Application should use this macro whenever possible due to possible * overflow in the math calculation. * * @param frame_rate Frame rate * * @return The ptime value (in usec). */ PJ_INLINE(unsigned) PJMEDIA_PTIME(const pjmedia_ratio *frame_rate) { #if PJ_HAS_INT64 return ((unsigned)((pj_uint64_t)1000000 * \ frame_rate->denum / frame_rate->num)); #elif PJ_HAS_FLOATING_POINT return ((unsigned)(1000000.0 * frame_rate->denum / frame_rate->num)); #else return ((unsigned)((1000L * frame_rate->denum / frame_rate->num) * 1000)); #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_INLINE(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) { /* This function is inlined to avoid build problem due to circular * dependency, i.e: this function is part of pjmedia and is needed * by pjmedia-audiodev, while pjmedia depends on pjmedia-audiodev. */ 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; } /** * 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/deps/pjsip/pjmedia/include/pjmedia/sound_port.h b/deps/pjsip/pjmedia/include/pjmedia/sound_port.h index c68c9ba2..6e3c3cd1 100644 --- a/deps/pjsip/pjmedia/include/pjmedia/sound_port.h +++ b/deps/pjsip/pjmedia/include/pjmedia/sound_port.h @@ -1,394 +1,394 @@ -/* $Id: sound_port.h 4082 2012-04-24 13:09:14Z bennylp $ */ +/* $Id: sound_port.h 4982 2015-02-11 05:15:29Z nanang $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef __PJMEDIA_SOUND_PORT_H__ #define __PJMEDIA_SOUND_PORT_H__ /** * @file sound_port.h * @brief Media port connection abstraction to sound device. */ #include #include #include PJ_BEGIN_DECL /** * @defgroup PJMED_SND_PORT Sound Device Port * @ingroup PJMEDIA_PORT_CLOCK * @brief Media Port Connection Abstraction to the Sound Device @{ As explained in @ref PJMED_SND, the sound hardware abstraction provides some callbacks for its user: - it calls rec_cb callback when it has finished capturing one media frame, and - it calls play_cb when it needs media frame to be played to the sound playback hardware. The @ref PJMED_SND_PORT (the object being explained here) add a thin wrapper to the hardware abstraction: - it will call downstream port's put_frame() when rec_cb() is called (i.e. when the sound hardware has finished capturing frame), and - it will call downstream port's get_frame() when play_cb() is called (i.e. every time the sound hardware needs more frames to be played to the playback hardware). This simple abstraction enables media to flow automatically (and in timely manner) from the downstream media port to the sound device. In other words, the sound device port supplies media clock to the ports. The media clock concept is explained in @ref PJMEDIA_PORT_CLOCK section. Application registers downstream port to the sound device port by calling #pjmedia_snd_port_connect(); */ /** * Sound port options. */ enum pjmedia_snd_port_option { /** * Don't start the audio device when creating a sound port. */ PJMEDIA_SND_PORT_NO_AUTO_START = 1 }; /** * This structure specifies the parameters to create the sound port. * Use pjmedia_snd_port_param_default() to initialize this structure with * default values (mostly zeroes) */ typedef struct pjmedia_snd_port_param { /** * Base structure. */ pjmedia_aud_param base; /** * Sound port creation options. */ unsigned options; /** * Echo cancellation options/flags. */ unsigned ec_options; /** * Arbitrary user data for playback and record preview callbacks below. */ void *user_data; /** * Optional callback for audio frame preview right before queued to * the speaker. * Notes: * - application MUST NOT block or perform long operation in the callback * as the callback may be executed in sound device thread * - when using software echo cancellation, application MUST NOT modify * the audio data from within the callback, otherwise the echo canceller * will not work properly. * - the return value of the callback will be ignored */ pjmedia_aud_play_cb on_play_frame; /** * Optional callback for audio frame preview recorded from the microphone * before being processed by any media component such as software echo * canceller. * Notes: * - application MUST NOT block or perform long operation in the callback * as the callback may be executed in sound device thread * - when using software echo cancellation, application MUST NOT modify * the audio data from within the callback, otherwise the echo canceller * will not work properly. * - the return value of the callback will be ignored */ pjmedia_aud_rec_cb on_rec_frame; } pjmedia_snd_port_param; /** * Initialize pjmedia_snd_port_param with default values. * * @param prm The parameter. */ PJ_DECL(void) pjmedia_snd_port_param_default(pjmedia_snd_port_param *prm); /** * This opaque type describes sound device port connection. * Sound device port is not a media port, but it is used to connect media * port to the sound device. */ typedef struct pjmedia_snd_port pjmedia_snd_port; /** * Create bidirectional sound port for both capturing and playback of * audio samples. * * @param pool Pool to allocate sound port structure. * @param rec_id Device index for recorder/capture stream, or * -1 to use the first capable device. * @param play_id Device index for playback stream, or -1 to use * the first capable device. * @param clock_rate Sound device's clock rate to set. * @param channel_count Set number of channels, 1 for mono, or 2 for * stereo. The channel count determines the format * of the frame. * @param samples_per_frame Number of samples per frame. * @param bits_per_sample Set the number of bits per sample. The normal * value for this parameter is 16 bits per sample. * @param options Options flag. * @param p_port Pointer to receive the sound device port instance. * * @return PJ_SUCCESS on success, or the appropriate error * code. */ PJ_DECL(pj_status_t) pjmedia_snd_port_create( pj_pool_t *pool, int rec_id, int play_id, unsigned clock_rate, unsigned channel_count, unsigned samples_per_frame, unsigned bits_per_sample, unsigned options, pjmedia_snd_port **p_port); /** * Create unidirectional sound device port for capturing audio streams from * the sound device with the specified parameters. * * @param pool Pool to allocate sound port structure. * @param index Device index, or -1 to let the library choose the * first available device. * @param clock_rate Sound device's clock rate to set. * @param channel_count Set number of channels, 1 for mono, or 2 for * stereo. The channel count determines the format * of the frame. * @param samples_per_frame Number of samples per frame. * @param bits_per_sample Set the number of bits per sample. The normal * value for this parameter is 16 bits per sample. * @param options Options flag. * @param p_port Pointer to receive the sound device port instance. * * @return PJ_SUCCESS on success, or the appropriate error * code. */ PJ_DECL(pj_status_t) pjmedia_snd_port_create_rec(pj_pool_t *pool, int index, unsigned clock_rate, unsigned channel_count, unsigned samples_per_frame, unsigned bits_per_sample, unsigned options, pjmedia_snd_port **p_port); /** * Create unidirectional sound device port for playing audio streams with the * specified parameters. * * @param pool Pool to allocate sound port structure. * @param index Device index, or -1 to let the library choose the * first available device. * @param clock_rate Sound device's clock rate to set. * @param channel_count Set number of channels, 1 for mono, or 2 for * stereo. The channel count determines the format * of the frame. * @param samples_per_frame Number of samples per frame. * @param bits_per_sample Set the number of bits per sample. The normal * value for this parameter is 16 bits per sample. * @param options Options flag. * @param p_port Pointer to receive the sound device port instance. * * @return PJ_SUCCESS on success, or the appropriate error * code. */ PJ_DECL(pj_status_t) pjmedia_snd_port_create_player(pj_pool_t *pool, int index, unsigned clock_rate, unsigned channel_count, unsigned samples_per_frame, unsigned bits_per_sample, unsigned options, pjmedia_snd_port **p_port); /** * Create sound device port according to the specified parameters. * * @param pool Pool to allocate sound port structure. * @param prm Sound port parameter. * @param p_port Pointer to receive the sound device port instance. * * @return PJ_SUCCESS on success, or the appropriate error * code. */ PJ_DECL(pj_status_t) pjmedia_snd_port_create2(pj_pool_t *pool, const pjmedia_snd_port_param *prm, pjmedia_snd_port **p_port); /** * Destroy sound device port. * * @param snd_port The sound device port. * * @return PJ_SUCCESS on success, or the appropriate error * code. */ PJ_DECL(pj_status_t) pjmedia_snd_port_destroy(pjmedia_snd_port *snd_port); /** * Retrieve the sound stream associated by this sound device port. * * @param snd_port The sound device port. * * @return The sound stream instance. */ PJ_DECL(pjmedia_aud_stream*) pjmedia_snd_port_get_snd_stream( pjmedia_snd_port *snd_port); /** * Change the echo cancellation settings. The echo cancellation settings * should have been specified when this sound port was created, by setting * the appropriate fields in the pjmedia_aud_param, because not all sound * device implementation supports changing the EC setting once the device * has been opened. * * The behavior of this function depends on whether device or software AEC * is being used. If the device supports AEC, this function will forward * the change request to the device and it will be up to the device whether * to support the request. If software AEC is being used (the software EC * will be used if the device does not support AEC), this function will * change the software EC settings. * * @param snd_port The sound device port. * @param pool Pool to re-create the echo canceller if necessary. * @param tail_ms Maximum echo tail length to be supported, in * miliseconds. If zero is specified, the EC would * be disabled. * @param options The options to be passed to #pjmedia_echo_create(). * This is only used if software EC is being used. * * @return PJ_SUCCESS on success. */ PJ_DECL(pj_status_t) pjmedia_snd_port_set_ec( pjmedia_snd_port *snd_port, pj_pool_t *pool, unsigned tail_ms, unsigned options); /** * Get current echo canceller tail length, in miliseconds. The tail length * will be zero if EC is not enabled. * * @param snd_port The sound device port. * @param p_length Pointer to receive the tail length. * * @return PJ_SUCCESS on success. */ PJ_DECL(pj_status_t) pjmedia_snd_port_get_ec_tail(pjmedia_snd_port *snd_port, unsigned *p_length); /** * Get a clock source from the sound port. * * @param snd_port The sound port. * @param dir Sound port's direction. * * @return The clock source. */ PJ_DECL(pjmedia_clock_src *) pjmedia_snd_port_get_clock_src( pjmedia_snd_port *snd_port, pjmedia_dir dir ); /** * Reset the EC state in the sound port. * * @param snd_port The sound device port. * * @return PJ_SUCCESS on success. */ PJ_DECL(pj_status_t) pjmedia_snd_port_reset_ec_state(pjmedia_snd_port *snd_port); /** * Connect a port to the sound device port. If the sound device port has a * sound recorder device, then this will start periodic function call to * the port's put_frame() function. If the sound device has a sound player * device, then this will start periodic function call to the port's * get_frame() function. * * For this version of PJMEDIA, the media port MUST have the same audio * settings as the sound device port, or otherwise the connection will * fail. This means the port MUST have the same clock_rate, channel count, * samples per frame, and bits per sample as the sound device port. * * @param snd_port The sound device port. * @param port The media port to be connected. * * @return PJ_SUCCESS on success, or the appropriate error * code. */ PJ_DECL(pj_status_t) pjmedia_snd_port_connect(pjmedia_snd_port *snd_port, pjmedia_port *port); /** * Retrieve the port instance currently attached to the sound port, if any. * * @param snd_port The sound device port. * * @return The port instance currently attached to the * sound device port, or NULL if there is no port * currently attached to the sound device port. */ PJ_DECL(pjmedia_port*) pjmedia_snd_port_get_port(pjmedia_snd_port *snd_port); /** * Disconnect currently attached port from the sound device port. * * @param snd_port The sound device port. * * @return PJ_SUCCESS on success. */ PJ_DECL(pj_status_t) pjmedia_snd_port_disconnect(pjmedia_snd_port *snd_port); /** * @} */ PJ_END_DECL #endif /* __PJMEDIA_SOUND_PORT_H__ */ diff --git a/deps/pjsip/pjmedia/include/pjmedia/transport_ice.h b/deps/pjsip/pjmedia/include/pjmedia/transport_ice.h index b98e2d61..cc8268c0 100644 --- a/deps/pjsip/pjmedia/include/pjmedia/transport_ice.h +++ b/deps/pjsip/pjmedia/include/pjmedia/transport_ice.h @@ -1,275 +1,275 @@ -/* $Id: transport_ice.h 4350 2013-02-15 03:57:31Z nanang $ */ +/* $Id: transport_ice.h 4606 2013-10-01 05:00:57Z ming $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef __PJMEDIA_TRANSPORT_ICE_H__ #define __PJMEDIA_TRANSPORT_ICE_H__ /** * @file transport_ice.h * @brief ICE capable media transport. */ #include #include /** * @defgroup PJMEDIA_TRANSPORT_ICE ICE Media Transport * @ingroup PJMEDIA_TRANSPORT * @brief Interactive Connectivity Establishment (ICE) transport * @{ * * This describes the implementation of media transport using * Interactive Connectivity Establishment (ICE) protocol. */ PJ_BEGIN_DECL /** * Structure containing callbacks to receive ICE notifications. */ typedef struct pjmedia_ice_cb { /** * This callback will be called when ICE negotiation completes. * * @param tp PJMEDIA ICE transport. * @param op The operation * @param status Operation status. */ void (*on_ice_complete)(pjmedia_transport *tp, pj_ice_strans_op op, pj_status_t status); /** * This callback will be called when ICE state changes. * * @param tp PJMEDIA ICE transport. * @param prev Previous state. * @param curr Current state. */ void (*on_ice_state)(pjmedia_transport *tp, pj_ice_strans_state prev, pj_ice_strans_state curr); /** * This callback will be called when ICE is stopped. * * @param tp PJMEDIA ICE transport. * @param reason Reason for stopping ICE. * @param err Error code */ void (*on_ice_stop)(pjmedia_transport *tp, char *reason, pj_status_t err); } pjmedia_ice_cb; /** * This structure specifies ICE transport specific info. This structure * will be filled in media transport specific info. */ typedef struct pjmedia_ice_transport_info { /** * Specifies whether ICE is used, i.e. SDP offer and answer indicates * that both parties support ICE and ICE should be used for the session. */ pj_bool_t active; /** * ICE sesion state. */ pj_ice_strans_state sess_state; /** * Session role. */ pj_ice_sess_role role; /** * Number of components in the component array. Before ICE negotiation * is complete, the number represents the number of components of the * local agent. After ICE negotiation has been completed successfully, * the number represents the number of common components between local * and remote agents. */ unsigned comp_cnt; /** * Array of ICE components. Typically the first element denotes RTP and * second element denotes RTCP. */ struct { /** * Local candidate type. */ pj_ice_cand_type lcand_type; /** * The local address. */ pj_sockaddr lcand_addr; /** * Remote candidate type. */ pj_ice_cand_type rcand_type; /** * Remote address. */ pj_sockaddr rcand_addr; } comp[2]; } pjmedia_ice_transport_info; /** * Options that can be specified when creating ICE transport. */ enum pjmedia_transport_ice_options { /** * Normally when remote doesn't use ICE, the ICE transport will * continuously check the source address of incoming packets to see * if it is different than the configured remote address, and switch * the remote address to the source address of the packet if they * are different after several packets are received. * Specifying this option will disable this feature. */ PJMEDIA_ICE_NO_SRC_ADDR_CHECKING = 1 }; /** * Create the Interactive Connectivity Establishment (ICE) media transport * using the specified configuration. When STUN or TURN (or both) is used, * the creation operation will complete asynchronously, when STUN resolution * and TURN allocation completes. When the initialization completes, the * \a on_ice_complete() complete will be called with \a op parameter equal * to PJ_ICE_STRANS_OP_INIT. * * In addition, this transport will also notify the application about the * result of ICE negotiation, also in \a on_ice_complete() callback. In this * case the callback will be called with \a op parameter equal to * PJ_ICE_STRANS_OP_NEGOTIATION. * * Other than this, application should use the \ref PJMEDIA_TRANSPORT API * to manipulate this media transport. * * @param endpt The media endpoint. * @param name Optional name to identify this ICE media transport * for logging purposes. * @param comp_cnt Number of components to be created. * @param cfg Pointer to configuration settings. * @param cb Optional structure containing ICE specific callbacks. * @param p_tp Pointer to receive the media transport instance. * * @return PJ_SUCCESS on success, or the appropriate error code. */ PJ_DECL(pj_status_t) pjmedia_ice_create(pjmedia_endpt *endpt, const char *name, unsigned comp_cnt, const pj_ice_strans_cfg *cfg, const pjmedia_ice_cb *cb, pjmedia_transport **p_tp); /** * The same as #pjmedia_ice_create() with additional \a options param. * * @param endpt The media endpoint. * @param name Optional name to identify this ICE media transport * for logging purposes. * @param comp_cnt Number of components to be created. * @param cfg Pointer to configuration settings. * @param cb Optional structure containing ICE specific callbacks. * @param options Options, see #pjmedia_transport_ice_options. * @param p_tp Pointer to receive the media transport instance. * * @return PJ_SUCCESS on success, or the appropriate error code. */ PJ_DECL(pj_status_t) pjmedia_ice_create2(pjmedia_endpt *endpt, const char *name, unsigned comp_cnt, const pj_ice_strans_cfg *cfg, const pjmedia_ice_cb *cb, unsigned options, pjmedia_transport **p_tp); /** * The same as #pjmedia_ice_create2() with additional \a user_data param. * * @param endpt The media endpoint. * @param name Optional name to identify this ICE media transport * for logging purposes. * @param comp_cnt Number of components to be created. * @param cfg Pointer to configuration settings. * @param cb Optional structure containing ICE specific callbacks. * @param options Options, see #pjmedia_transport_ice_options. * @param user_data User data to be attached to the transport. * @param p_tp Pointer to receive the media transport instance. * * @return PJ_SUCCESS on success, or the appropriate error code. */ PJ_DECL(pj_status_t) pjmedia_ice_create3(pjmedia_endpt *endpt, const char *name, unsigned comp_cnt, const pj_ice_strans_cfg *cfg, const pjmedia_ice_cb *cb, unsigned options, void *user_data, pjmedia_transport **p_tp); /** * Return the ICE stream transport associated with this PJMEDIA transport * * @param tp Media transport instance. * * @return Pointer to the pj_ice_strans instance associated with this * media transport. */ PJ_DECL(pj_ice_strans*) pjmedia_ice_get_strans(pjmedia_transport *tp); /** * Get the group lock for the ICE media transport. * * @param tp The ICE media transport. * * @return The group lock. */ PJ_DECL(pj_grp_lock_t *) pjmedia_ice_get_grp_lock(pjmedia_transport *tp); PJ_END_DECL /** * @} */ #endif /* __PJMEDIA_TRANSPORT_ICE_H__ */ diff --git a/deps/pjsip/pjmedia/src/pjmedia-videodev/util.c b/deps/pjsip/pjmedia/src/pjmedia-videodev/util.c index dafb698e..f76e296b 100644 --- a/deps/pjsip/pjmedia/src/pjmedia-videodev/util.c +++ b/deps/pjsip/pjmedia/src/pjmedia-videodev/util.c @@ -1,365 +1,365 @@ -/* $Id$ */ +/* $Id: util.c 5157 2015-08-10 09:11:39Z nanang $ */ /* * Copyright (C) 2014-2015 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 "util.h" #include #include #include #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) #if defined(PJMEDIA_HAS_LIBYUV) && PJMEDIA_HAS_LIBYUV != 0 #include #define HAS_ROTATION 1 #else #define HAS_ROTATION 0 #endif #define THIS_FILE "vid_util.c" pj_status_t pjmedia_vid_dev_conv_create_converter(pjmedia_vid_dev_conv *conv, pj_pool_t *pool, pjmedia_format *fmt, pjmedia_rect_size src_size, pjmedia_rect_size dst_size, pj_bool_t handle_rotation, pj_bool_t maintain_aspect_ratio) { pj_status_t status; pjmedia_conversion_param conv_param; const pjmedia_video_format_info *vfi; pj_assert((src_size.w == dst_size.w || src_size.h == dst_size.h) || (src_size.w == dst_size.h || src_size.h == dst_size.w)); if (conv->conv) return PJ_SUCCESS; if (fmt->id != PJMEDIA_FORMAT_I420 && fmt->id != PJMEDIA_FORMAT_BGRA) return PJ_EINVAL; /* Currently, for BGRA format, device must handle the rotation. */ if (fmt->id == PJMEDIA_FORMAT_BGRA && handle_rotation) return PJ_ENOTSUP; if (handle_rotation) { #if !HAS_ROTATION return PJ_ENOTSUP; #endif } conv->src_size = src_size; conv->dst_size = dst_size; conv->handle_rotation = handle_rotation; pjmedia_format_copy(&conv->fmt, fmt); pjmedia_format_copy(&conv_param.src, fmt); pjmedia_format_copy(&conv_param.dst, fmt); /* If we do the rotation, the conversion's source size must be the same * as the device's original size. Otherwise, frames that require conversion * are the ones of which orientation differ by 90 or 270 degrees from the * destination size. */ if (handle_rotation) { conv_param.src.det.vid.size = src_size; } else { conv_param.src.det.vid.size.w = dst_size.h; conv_param.src.det.vid.size.h = dst_size.w; } /* Maintaining aspect ratio requires filling the left&right / * top&bottom area with black color. * Currently it is only supported for I420. * TODO: support BGRA as well */ if (fmt->id != PJMEDIA_FORMAT_I420) maintain_aspect_ratio = PJ_FALSE; /* Calculate the size after rotation. * If aspect ratio doesn't need to be maintained, rot_size is simply equal * to the destination size. Otherwise, we need to fit the rotated frame * to height or to width. */ conv->maintain_aspect_ratio = maintain_aspect_ratio; if (maintain_aspect_ratio) { conv->fit_to_h = (dst_size.w >= dst_size.h? PJ_TRUE: PJ_FALSE); if (conv->fit_to_h) { /* Fit to height */ conv->rot_size.h = dst_size.h; conv->rot_size.w = dst_size.h * dst_size.h / dst_size.w; /* Make sure the width difference is divisible by four * so we can have equal padding left and right. */ conv->rot_size.w += (dst_size.w - conv->rot_size.w) % 4; conv->pad = (conv->dst_size.w - conv->rot_size.w) / 2; } else { /* Fit to width */ conv->rot_size.w = dst_size.w; conv->rot_size.h = dst_size.w * dst_size.w / dst_size.h; conv->rot_size.h += (dst_size.h - conv->rot_size.h) % 4; conv->pad = (conv->dst_size.h - conv->rot_size.h) / 2; } } else { conv->rot_size = dst_size; } /* Calculate the size after resizing. */ if (handle_rotation) { /* If we do the rotation, conversion is done before rotation. */ if (maintain_aspect_ratio) { /* Since aspect ratio is maintained, the long side after * conversion must be the same as before conversion. * For example: 352x288 will be converted to 288x236 */ pj_size_t long_s = (conv->rot_size.h > conv->rot_size.w? conv->rot_size.h: conv->rot_size.w); pj_size_t short_s = (conv->rot_size.h > conv->rot_size.w? conv->rot_size.w: conv->rot_size.h); if (src_size.w > src_size.h) { conv->res_size.w = long_s; conv->res_size.h = short_s; } else { conv->res_size.w = short_s; conv->res_size.h = long_s; } } else { /* We don't need to maintain aspect ratio, * so just swap the width and height. * For example: 352x288 will be resized to 288x352 */ conv->res_size.w = src_size.h; conv->res_size.h = src_size.w; } conv_param.dst.det.vid.size = conv->res_size; } else { conv->res_size = conv->rot_size; conv_param.dst.det.vid.size = conv->rot_size; } status = pjmedia_converter_create(NULL, pool, &conv_param, &conv->conv); if (status != PJ_SUCCESS) { PJ_LOG(3, (THIS_FILE, "Error creating converter")); return status; } vfi = pjmedia_get_video_format_info(NULL, fmt->id); pj_assert(vfi); conv->wxh = conv->dst_size.w * conv->dst_size.h; conv->src_frame_size = dst_size.w * dst_size.h * vfi->bpp / 8; conv->conv_frame_size = conv->rot_size.w * conv->rot_size.h; conv->conv_frame_size *= vfi->bpp / 8; conv->conv_buf = pj_pool_alloc(pool, conv->src_frame_size); pjmedia_vid_dev_conv_set_rotation(conv, PJMEDIA_ORIENT_NATURAL); PJ_LOG(4, (THIS_FILE, "Orientation converter created: %dx%d to %dx%d, " "maintain aspect ratio=%s", conv_param.src.det.vid.size.w, conv_param.src.det.vid.size.h, conv_param.dst.det.vid.size.w, conv_param.dst.det.vid.size.h, maintain_aspect_ratio? "yes": "no")); return PJ_SUCCESS; } void pjmedia_vid_dev_conv_set_rotation(pjmedia_vid_dev_conv *conv, pjmedia_orient rotation) { pjmedia_rect_size new_size = conv->src_size; conv->rotation = rotation; if (rotation == PJMEDIA_ORIENT_ROTATE_90DEG || rotation == PJMEDIA_ORIENT_ROTATE_270DEG) { new_size.w = conv->src_size.h; new_size.h = conv->src_size.w; } /* Check whether new size (size after rotation) and destination * are both portrait or both landscape. If yes, resize will not * be required in pjmedia_vid_dev_conv_resize_and_rotate() below. * For example, 352x288 frame rotated 270 degrees will fit into * a destination frame of 288x352 (no resize needed). */ if ((new_size.w > new_size.h && conv->dst_size.w > conv->dst_size.h) || (new_size.h > new_size.w && conv->dst_size.h > conv->dst_size.w)) { conv->match_src_dst = PJ_TRUE; } else { conv->match_src_dst = PJ_FALSE; } } pj_status_t pjmedia_vid_dev_conv_resize_and_rotate(pjmedia_vid_dev_conv *conv, void *src_buf, void **result) { #define swap(a, b) {pj_uint8_t *c = a; a = b; b = c;} pj_status_t status; pjmedia_frame src_frame, dst_frame; pjmedia_rect_size src_size = conv->src_size; pj_uint8_t *src = src_buf; pj_uint8_t *dst = conv->conv_buf; pj_assert(src_buf); if (!conv->conv) return PJ_EINVALIDOP; if (!conv->match_src_dst) { /* We need to resize. */ src_frame.buf = src; dst_frame.buf = dst; src_frame.size = conv->src_frame_size; dst_frame.size = conv->conv_frame_size; status = pjmedia_converter_convert(conv->conv, &src_frame, &dst_frame); if (status != PJ_SUCCESS) { PJ_LOG(3, (THIS_FILE, "Failed to convert frame")); return status; } src_size = conv->res_size; swap(src, dst); } if (conv->handle_rotation && conv->rotation != PJMEDIA_ORIENT_NATURAL) { /* We need to do rotation. */ if (conv->fmt.id == PJMEDIA_FORMAT_I420) { pjmedia_rect_size dst_size = src_size; pj_size_t p_len = src_size.w * src_size.h; if (conv->rotation == PJMEDIA_ORIENT_ROTATE_90DEG || conv->rotation == PJMEDIA_ORIENT_ROTATE_270DEG) { dst_size.w = src_size.h; dst_size.h = src_size.w; } #if defined(PJMEDIA_HAS_LIBYUV) && PJMEDIA_HAS_LIBYUV != 0 enum RotationMode mode; switch (conv->rotation) { case PJMEDIA_ORIENT_ROTATE_90DEG: mode = kRotate90; break; case PJMEDIA_ORIENT_ROTATE_180DEG: mode = kRotate180; break; case PJMEDIA_ORIENT_ROTATE_270DEG: mode = kRotate270; break; default: mode = kRotate0; } I420Rotate(src, src_size.w, src+p_len, src_size.w/2, src+p_len+p_len/4, src_size.w/2, dst, dst_size.w, dst+p_len, dst_size.w/2, dst+p_len+p_len/4, dst_size.w/2, src_size.w, src_size.h, mode); swap(src, dst); #else PJ_UNUSED_ARG(p_len); PJ_UNUSED_ARG(dst_size); #endif } } if (!conv->match_src_dst && conv->maintain_aspect_ratio) { /* Center the frame and fill the area with black color */ if (conv->fmt.id == PJMEDIA_FORMAT_I420) { unsigned i = 0; pj_uint8_t *pdst = dst; pj_uint8_t *psrc = src; pj_size_t p_len_src = 0, p_len_dst = conv->wxh; int pad = conv->pad; pj_bzero(pdst, p_len_dst); if (conv->fit_to_h) { /* Fill the left and right with black */ for (; i < conv->dst_size.h; ++i) { pdst += pad; pj_memcpy(pdst, psrc, conv->rot_size.w); pdst += conv->rot_size.w; psrc += conv->rot_size.w; pdst += pad; } } else { /* Fill the top and bottom with black */ p_len_src = conv->rot_size.w * conv->rot_size.h; pj_memcpy(pdst + conv->rot_size.w * pad, psrc, p_len_src); psrc += p_len_src; pdst += p_len_dst; } /* Fill the U&V components with 0x80 to make it black. * Bzero-ing will make the area look green instead. */ pj_memset(pdst, 0x80, p_len_dst/2); pad /= 2; if (conv->fit_to_h) { p_len_src = conv->rot_size.w / 2; for (i = conv->dst_size.h; i > 0; --i) { pdst += pad; pj_memcpy(pdst, psrc, p_len_src); pdst += p_len_src; psrc += p_len_src; pdst += pad; } } else { pj_uint8_t *U, *V; pj_size_t gap = conv->rot_size.w * pad / 2; p_len_src /= 4; U = pdst; V = U + p_len_dst/4; pj_memcpy(U + gap, psrc, p_len_src); psrc += p_len_src; pj_memcpy(V + gap, psrc, p_len_src); } swap(src, dst); } } *result = src; return PJ_SUCCESS; } void pjmedia_vid_dev_conv_destroy_converter(pjmedia_vid_dev_conv *conv) { if (conv->conv) { pjmedia_converter_destroy(conv->conv); conv->conv = NULL; } } #endif /* PJMEDIA_HAS_VIDEO */ diff --git a/deps/pjsip/pjmedia/src/pjmedia/format.c b/deps/pjsip/pjmedia/src/pjmedia/format.c index aee185f4..e7b012ba 100644 --- a/deps/pjsip/pjmedia/src/pjmedia/format.c +++ b/deps/pjsip/pjmedia/src/pjmedia/format.c @@ -1,393 +1,393 @@ -/* $Id: format.c 4158 2012-06-06 09:56:14Z nanang $ */ +/* $Id: format.c 4785 2014-03-10 09:01:18Z nanang $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include PJ_DEF(pjmedia_audio_format_detail*) pjmedia_format_get_audio_format_detail(const pjmedia_format *fmt, pj_bool_t assert_valid) { if (fmt->detail_type==PJMEDIA_FORMAT_DETAIL_AUDIO) { return (pjmedia_audio_format_detail*) &fmt->det.aud; } else { /* Get rid of unused var compiler warning if pj_assert() * macro does not do anything */ PJ_UNUSED_ARG(assert_valid); pj_assert(!assert_valid || !"Invalid audio format detail"); return NULL; } } PJ_DEF(pjmedia_format*) pjmedia_format_copy(pjmedia_format *dst, const pjmedia_format *src) { return (pjmedia_format*)pj_memcpy(dst, src, sizeof(*src)); } #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) static pj_status_t apply_packed_fmt(const pjmedia_video_format_info *fi, pjmedia_video_apply_fmt_param *aparam); static pj_status_t apply_planar_420(const pjmedia_video_format_info *fi, pjmedia_video_apply_fmt_param *aparam); static pj_status_t apply_planar_422(const pjmedia_video_format_info *fi, pjmedia_video_apply_fmt_param *aparam); static pj_status_t apply_planar_444(const pjmedia_video_format_info *fi, pjmedia_video_apply_fmt_param *aparam); struct pjmedia_video_format_mgr { unsigned max_info; unsigned info_cnt; pjmedia_video_format_info **infos; }; static pjmedia_video_format_mgr *video_format_mgr_instance; static pjmedia_video_format_info built_in_vid_fmt_info[] = { {PJMEDIA_FORMAT_RGB24, "RGB24", PJMEDIA_COLOR_MODEL_RGB, 24, 1, &apply_packed_fmt}, {PJMEDIA_FORMAT_ARGB, "ARGB", PJMEDIA_COLOR_MODEL_RGB, 32, 1, &apply_packed_fmt}, {PJMEDIA_FORMAT_RGBA, "RGBA", PJMEDIA_COLOR_MODEL_RGB, 32, 1, &apply_packed_fmt}, {PJMEDIA_FORMAT_BGRA, "BGRA", PJMEDIA_COLOR_MODEL_RGB, 32, 1, &apply_packed_fmt}, {PJMEDIA_FORMAT_DIB , "DIB ", PJMEDIA_COLOR_MODEL_RGB, 24, 1, &apply_packed_fmt}, {PJMEDIA_FORMAT_GBRP, "GBRP", PJMEDIA_COLOR_MODEL_RGB, 24, 3, &apply_planar_444}, {PJMEDIA_FORMAT_AYUV, "AYUV", PJMEDIA_COLOR_MODEL_YUV, 32, 1, &apply_packed_fmt}, {PJMEDIA_FORMAT_YUY2, "YUY2", PJMEDIA_COLOR_MODEL_YUV, 16, 1, &apply_packed_fmt}, {PJMEDIA_FORMAT_UYVY, "UYVY", PJMEDIA_COLOR_MODEL_YUV, 16, 1, &apply_packed_fmt}, {PJMEDIA_FORMAT_YVYU, "YVYU", PJMEDIA_COLOR_MODEL_YUV, 16, 1, &apply_packed_fmt}, {PJMEDIA_FORMAT_I420, "I420", PJMEDIA_COLOR_MODEL_YUV, 12, 3, &apply_planar_420}, {PJMEDIA_FORMAT_YV12, "YV12", PJMEDIA_COLOR_MODEL_YUV, 12, 3, &apply_planar_420}, {PJMEDIA_FORMAT_I422, "I422", PJMEDIA_COLOR_MODEL_YUV, 16, 3, &apply_planar_422}, {PJMEDIA_FORMAT_I420JPEG, "I420JPG", PJMEDIA_COLOR_MODEL_YUV, 12, 3, &apply_planar_420}, {PJMEDIA_FORMAT_I422JPEG, "I422JPG", PJMEDIA_COLOR_MODEL_YUV, 16, 3, &apply_planar_422}, }; PJ_DEF(void) pjmedia_format_init_video( pjmedia_format *fmt, pj_uint32_t fmt_id, unsigned width, unsigned height, unsigned fps_num, unsigned fps_denum) { pj_assert(fps_denum); fmt->id = fmt_id; fmt->type = PJMEDIA_TYPE_VIDEO; fmt->detail_type = PJMEDIA_FORMAT_DETAIL_VIDEO; fmt->det.vid.size.w = width; fmt->det.vid.size.h = height; fmt->det.vid.fps.num = fps_num; fmt->det.vid.fps.denum = fps_denum; fmt->det.vid.avg_bps = fmt->det.vid.max_bps = 0; if (pjmedia_video_format_mgr_instance()) { const pjmedia_video_format_info *vfi; pjmedia_video_apply_fmt_param vafp; pj_uint32_t bps; vfi = pjmedia_get_video_format_info(NULL, fmt->id); if (vfi) { pj_bzero(&vafp, sizeof(vafp)); vafp.size = fmt->det.vid.size; vfi->apply_fmt(vfi, &vafp); bps = (pj_uint32_t)vafp.framebytes * fps_num * (pj_size_t)8 / fps_denum; fmt->det.vid.avg_bps = fmt->det.vid.max_bps = bps; } } } PJ_DEF(pjmedia_video_format_detail*) pjmedia_format_get_video_format_detail(const pjmedia_format *fmt, pj_bool_t assert_valid) { if (fmt->detail_type==PJMEDIA_FORMAT_DETAIL_VIDEO) { return (pjmedia_video_format_detail*)&fmt->det.vid; } else { pj_assert(!assert_valid || !"Invalid video format detail"); return NULL; } } static pj_status_t apply_packed_fmt(const pjmedia_video_format_info *fi, pjmedia_video_apply_fmt_param *aparam) { unsigned i; pj_size_t stride; stride = (pj_size_t)((aparam->size.w*fi->bpp) >> 3); /* Calculate memsize */ aparam->framebytes = stride * aparam->size.h; /* Packed formats only use 1 plane */ aparam->planes[0] = aparam->buffer; aparam->strides[0] = (int)stride; aparam->plane_bytes[0] = aparam->framebytes; /* Zero unused planes */ for (i=1; istrides[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; istrides[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; istrides[i] = 0; aparam->planes[i] = NULL; aparam->plane_bytes[i] = 0; } return PJ_SUCCESS; } static pj_status_t apply_planar_444(const pjmedia_video_format_info *fi, pjmedia_video_apply_fmt_param *aparam) { unsigned i; pj_size_t Y_bytes; PJ_UNUSED_ARG(fi); /* Calculate memsize */ Y_bytes = (pj_size_t)(aparam->size.w * aparam->size.h); aparam->framebytes = (Y_bytes * 3); /* Planar formats use 3 plane */ aparam->strides[0] = aparam->strides[1] = aparam->strides[2] = aparam->size.w; aparam->planes[0] = aparam->buffer; aparam->planes[1] = aparam->planes[0] + Y_bytes; aparam->planes[2] = aparam->planes[1] + Y_bytes; aparam->plane_bytes[0] = aparam->plane_bytes[1] = aparam->plane_bytes[2] = Y_bytes; /* Zero unused planes */ for (i=3; istrides[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; iinfos[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; iinfo_cnt; ++i) { if (mgr->infos[i]->id >= info->id) break; } if (i < mgr->info_cnt) { if (mgr->infos[i]->id == info->id) { /* just overwrite */ mgr->infos[i] = info; return PJ_SUCCESS; } pj_memmove(&mgr->infos[i+1], &mgr->infos[i], (mgr->info_cnt - i) * sizeof(pjmedia_video_format_info*)); } mgr->infos[i] = info; mgr->info_cnt++; return PJ_SUCCESS; } PJ_DEF(pjmedia_video_format_mgr*) pjmedia_video_format_mgr_instance(void) { pj_assert(video_format_mgr_instance != NULL); return video_format_mgr_instance; } PJ_DEF(void) pjmedia_video_format_mgr_set_instance(pjmedia_video_format_mgr *mgr) { video_format_mgr_instance = mgr; } PJ_DEF(void) pjmedia_video_format_mgr_destroy(pjmedia_video_format_mgr *mgr) { if (!mgr) mgr = pjmedia_video_format_mgr_instance(); PJ_ASSERT_ON_FAIL(mgr != NULL, return); mgr->info_cnt = 0; if (video_format_mgr_instance == mgr) video_format_mgr_instance = NULL; } #endif /* PJMEDIA_HAS_VIDEO */ diff --git a/deps/pjsip/pjmedia/src/pjmedia/rtcp.c b/deps/pjsip/pjmedia/src/pjmedia/rtcp.c index 3dc4ede4..4a578134 100644 --- a/deps/pjsip/pjmedia/src/pjmedia/rtcp.c +++ b/deps/pjsip/pjmedia/src/pjmedia/rtcp.c @@ -1,1150 +1,1150 @@ -/* $Id: rtcp.c 4283 2012-10-12 06:19:32Z ming $ */ +/* $Id: rtcp.c 4712 2014-01-23 08:09:29Z nanang $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #define THIS_FILE "rtcp.c" #define RTCP_SR 200 #define RTCP_RR 201 #define RTCP_SDES 202 #define RTCP_BYE 203 #define RTCP_PSFB 206 /* Payload-specific FB message (RFC 4585) */ #define RTCP_XR 207 enum { RTCP_SDES_NULL = 0, RTCP_SDES_CNAME = 1, RTCP_SDES_NAME = 2, RTCP_SDES_EMAIL = 3, RTCP_SDES_PHONE = 4, RTCP_SDES_LOC = 5, RTCP_SDES_TOOL = 6, RTCP_SDES_NOTE = 7 }; #if PJ_HAS_HIGH_RES_TIMER==0 # error "High resolution timer needs to be enabled" #endif #if 0 # define TRACE_(x) PJ_LOG(3,x) #else # define TRACE_(x) ; #endif /* * Get NTP time. */ PJ_DEF(pj_status_t) pjmedia_rtcp_get_ntp_time(const pjmedia_rtcp_session *sess, pjmedia_rtcp_ntp_rec *ntp) { /* Seconds between 1900-01-01 to 1970-01-01 */ #define JAN_1970 (2208988800UL) pj_timestamp ts; pj_status_t status; status = pj_get_timestamp(&ts); /* Fill up the high 32bit part */ ntp->hi = (pj_uint32_t)((ts.u64 - sess->ts_base.u64) / sess->ts_freq.u64) + sess->tv_base.sec + JAN_1970; /* Calculate seconds fractions */ ts.u64 = (ts.u64 - sess->ts_base.u64) % sess->ts_freq.u64; pj_assert(ts.u64 < sess->ts_freq.u64); ts.u64 = (ts.u64 << 32) / sess->ts_freq.u64; /* Fill up the low 32bit part */ ntp->lo = ts.u32.lo; #if (defined(PJ_WIN32) && PJ_WIN32!=0) || \ (defined(PJ_WIN64) && PJ_WIN64!=0) || \ (defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE!=0) /* On Win32, since we use QueryPerformanceCounter() as the backend * timestamp API, we need to protect against this bug: * Performance counter value may unexpectedly leap forward * http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q274323 */ { /* * Compare elapsed time reported by timestamp with actual elapsed * time. If the difference is too excessive, then we use system * time instead. */ /* MIN_DIFF needs to be large enough so that "normal" diff caused * by system activity or context switch doesn't trigger the time * correction. */ enum { MIN_DIFF = 400 }; pj_time_val ts_time, elapsed, diff; pj_gettimeofday(&elapsed); ts_time.sec = ntp->hi - sess->tv_base.sec - JAN_1970; ts_time.msec = (long)(ntp->lo * 1000.0 / 0xFFFFFFFF); PJ_TIME_VAL_SUB(elapsed, sess->tv_base); if (PJ_TIME_VAL_LT(ts_time, elapsed)) { diff = elapsed; PJ_TIME_VAL_SUB(diff, ts_time); } else { diff = ts_time; PJ_TIME_VAL_SUB(diff, elapsed); } if (PJ_TIME_VAL_MSEC(diff) >= MIN_DIFF) { TRACE_((sess->name, "RTCP NTP timestamp corrected by %d ms", PJ_TIME_VAL_MSEC(diff))); ntp->hi = elapsed.sec + sess->tv_base.sec + JAN_1970; ntp->lo = (elapsed.msec * 65536 / 1000) << 16; } } #endif return status; } /* * Initialize RTCP session setting. */ PJ_DEF(void) pjmedia_rtcp_session_setting_default( pjmedia_rtcp_session_setting *settings) { pj_bzero(settings, sizeof(*settings)); } /* * Initialize bidirectional RTCP statistics. * */ PJ_DEF(void) pjmedia_rtcp_init_stat(pjmedia_rtcp_stat *stat) { pj_time_val now; pj_assert(stat); pj_bzero(stat, sizeof(pjmedia_rtcp_stat)); pj_math_stat_init(&stat->rtt); pj_math_stat_init(&stat->rx.loss_period); pj_math_stat_init(&stat->rx.jitter); pj_math_stat_init(&stat->tx.loss_period); pj_math_stat_init(&stat->tx.jitter); #if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0 pj_math_stat_init(&stat->rx_ipdv); #endif #if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0 pj_math_stat_init(&stat->rx_raw_jitter); #endif pj_gettimeofday(&now); stat->start = now; } /* * Initialize RTCP session. */ PJ_DEF(void) pjmedia_rtcp_init(pjmedia_rtcp_session *sess, char *name, unsigned clock_rate, unsigned samples_per_frame, pj_uint32_t ssrc) { pjmedia_rtcp_session_setting settings; pjmedia_rtcp_session_setting_default(&settings); settings.name = name; settings.clock_rate = clock_rate; settings.samples_per_frame = samples_per_frame; settings.ssrc = ssrc; pjmedia_rtcp_init2(sess, &settings); } /* * Initialize RTCP session. */ PJ_DEF(void) pjmedia_rtcp_init2( pjmedia_rtcp_session *sess, const pjmedia_rtcp_session_setting *settings) { pjmedia_rtcp_sr_pkt *sr_pkt = &sess->rtcp_sr_pkt; pj_time_val now; /* Memset everything */ pj_bzero(sess, sizeof(pjmedia_rtcp_session)); /* Last RX timestamp in RTP packet */ sess->rtp_last_ts = (unsigned)-1; /* Name */ sess->name = settings->name ? settings->name : (char*)THIS_FILE; /* Set clock rate */ sess->clock_rate = settings->clock_rate; sess->pkt_size = settings->samples_per_frame; /* Init common RTCP SR header */ sr_pkt->common.version = 2; sr_pkt->common.count = 1; sr_pkt->common.pt = RTCP_SR; sr_pkt->common.length = pj_htons(12); sr_pkt->common.ssrc = pj_htonl(settings->ssrc); /* Copy to RTCP RR header */ pj_memcpy(&sess->rtcp_rr_pkt.common, &sr_pkt->common, sizeof(pjmedia_rtcp_common)); sess->rtcp_rr_pkt.common.pt = RTCP_RR; sess->rtcp_rr_pkt.common.length = pj_htons(7); /* Get time and timestamp base and frequency */ pj_gettimeofday(&now); sess->tv_base = now; pj_get_timestamp(&sess->ts_base); pj_get_timestamp_freq(&sess->ts_freq); sess->rtp_ts_base = settings->rtp_ts_base; /* Initialize statistics states */ pjmedia_rtcp_init_stat(&sess->stat); /* RR will be initialized on receipt of the first RTP packet. */ } PJ_DEF(void) pjmedia_rtcp_fini(pjmedia_rtcp_session *sess) { #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) pjmedia_rtcp_xr_fini(&sess->xr_session); #else /* Nothing to do. */ PJ_UNUSED_ARG(sess); #endif } static void rtcp_init_seq(pjmedia_rtcp_session *sess) { sess->received = 0; sess->exp_prior = 0; sess->rx_prior = 0; sess->transit = 0; sess->jitter = 0; } PJ_DEF(void) pjmedia_rtcp_rx_rtp( pjmedia_rtcp_session *sess, unsigned seq, unsigned rtp_ts, unsigned payload) { pjmedia_rtcp_rx_rtp2(sess, seq, rtp_ts, payload, PJ_FALSE); } PJ_DEF(void) pjmedia_rtcp_rx_rtp2(pjmedia_rtcp_session *sess, unsigned seq, unsigned rtp_ts, unsigned payload, pj_bool_t discarded) { pj_timestamp ts; pj_uint32_t arrival; pj_int32_t transit; pjmedia_rtp_status seq_st; #if !defined(PJMEDIA_HAS_RTCP_XR) || (PJMEDIA_HAS_RTCP_XR == 0) PJ_UNUSED_ARG(discarded); #endif if (sess->stat.rx.pkt == 0) { /* Init sequence for the first time. */ pjmedia_rtp_seq_init(&sess->seq_ctrl, (pj_uint16_t)seq); } sess->stat.rx.pkt++; sess->stat.rx.bytes += payload; /* Process the RTP packet. */ pjmedia_rtp_seq_update(&sess->seq_ctrl, (pj_uint16_t)seq, &seq_st); if (seq_st.status.flag.restart) { rtcp_init_seq(sess); } if (seq_st.status.flag.dup) { sess->stat.rx.dup++; TRACE_((sess->name, "Duplicate packet detected")); } if (seq_st.status.flag.outorder && !seq_st.status.flag.probation) { sess->stat.rx.reorder++; TRACE_((sess->name, "Out-of-order packet detected")); } if (seq_st.status.flag.bad) { sess->stat.rx.discard++; #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) pjmedia_rtcp_xr_rx_rtp(&sess->xr_session, seq, -1, /* lost */ (seq_st.status.flag.dup? 1:0), /* dup */ (!seq_st.status.flag.dup? 1:-1), /* discard */ -1, /* jitter */ -1, 0); /* toh */ #endif TRACE_((sess->name, "Bad packet discarded")); return; } /* Only mark "good" packets */ ++sess->received; /* Calculate loss periods. */ if (seq_st.diff > 1) { unsigned count = seq_st.diff - 1; unsigned period; period = count * sess->pkt_size * 1000 / sess->clock_rate; period *= 1000; /* Update packet lost. * The packet lost number will also be updated when we're sending * outbound RTCP RR. */ sess->stat.rx.loss += (seq_st.diff - 1); TRACE_((sess->name, "%d packet(s) lost", seq_st.diff - 1)); /* Update loss period stat */ pj_math_stat_update(&sess->stat.rx.loss_period, period); } /* * Calculate jitter only when sequence is good (see RFC 3550 section A.8), * AND only when the timestamp is different than the last packet * (see RTP FAQ). */ if (seq_st.diff == 1 && rtp_ts != sess->rtp_last_ts) { /* Get arrival time and convert timestamp to samples */ pj_get_timestamp(&ts); ts.u64 = ts.u64 * sess->clock_rate / sess->ts_freq.u64; arrival = ts.u32.lo; transit = arrival - rtp_ts; /* Ignore the first N packets as they normally have bad jitter * due to other threads working to establish the call */ if (sess->transit == 0 || sess->received < PJMEDIA_RTCP_IGNORE_FIRST_PACKETS) { sess->transit = transit; sess->stat.rx.jitter.min = (unsigned)-1; } else { pj_int32_t d; pj_uint32_t jitter; d = transit - sess->transit; if (d < 0) d = -d; sess->jitter += d - ((sess->jitter + 8) >> 4); /* Update jitter stat */ jitter = sess->jitter >> 4; /* Convert jitter unit from samples to usec */ if (jitter < 4294) jitter = jitter * 1000000 / sess->clock_rate; else { jitter = jitter * 1000 / sess->clock_rate; jitter *= 1000; } pj_math_stat_update(&sess->stat.rx.jitter, jitter); #if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0 { pj_uint32_t raw_jitter; /* Convert raw jitter unit from samples to usec */ if (d < 4294) raw_jitter = d * 1000000 / sess->clock_rate; else { raw_jitter = d * 1000 / sess->clock_rate; raw_jitter *= 1000; } /* Update jitter stat */ pj_math_stat_update(&sess->stat.rx_raw_jitter, raw_jitter); } #endif #if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0 { pj_int32_t ipdv; ipdv = transit - sess->transit; /* Convert IPDV unit from samples to usec */ if (ipdv > -2147 && ipdv < 2147) ipdv = ipdv * 1000000 / (int)sess->clock_rate; else { ipdv = ipdv * 1000 / (int)sess->clock_rate; ipdv *= 1000; } /* Update jitter stat */ pj_math_stat_update(&sess->stat.rx_ipdv, ipdv); } #endif #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) pjmedia_rtcp_xr_rx_rtp(&sess->xr_session, seq, 0, /* lost */ 0, /* dup */ discarded, /* discard */ (sess->jitter >> 4), /* jitter */ -1, 0); /* toh */ #endif /* Update session transit */ sess->transit = transit; } #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) } else if (seq_st.diff > 1) { int i; /* Report RTCP XR about packet losses */ for (i=seq_st.diff-1; i>0; --i) { pjmedia_rtcp_xr_rx_rtp(&sess->xr_session, seq - i, 1, /* lost */ 0, /* dup */ 0, /* discard */ -1, /* jitter */ -1, 0); /* toh */ } /* Report RTCP XR this packet */ pjmedia_rtcp_xr_rx_rtp(&sess->xr_session, seq, 0, /* lost */ 0, /* dup */ discarded, /* discard */ -1, /* jitter */ -1, 0); /* toh */ #endif } /* Update timestamp of last RX RTP packet */ sess->rtp_last_ts = rtp_ts; } PJ_DEF(void) pjmedia_rtcp_tx_rtp(pjmedia_rtcp_session *sess, unsigned bytes_payload_size) { /* Update statistics */ sess->stat.tx.pkt++; sess->stat.tx.bytes += bytes_payload_size; } static void parse_rtcp_report( pjmedia_rtcp_session *sess, const void *pkt, pj_size_t size) { pjmedia_rtcp_common *common = (pjmedia_rtcp_common*) pkt; const pjmedia_rtcp_rr *rr = NULL; const pjmedia_rtcp_sr *sr = NULL; pj_uint32_t last_loss, jitter_samp, jitter; /* Parse RTCP */ if (common->pt == RTCP_SR) { sr = (pjmedia_rtcp_sr*) (((char*)pkt) + sizeof(pjmedia_rtcp_common)); if (common->count > 0 && size >= (sizeof(pjmedia_rtcp_sr_pkt))) { rr = (pjmedia_rtcp_rr*)(((char*)pkt) + (sizeof(pjmedia_rtcp_common) + sizeof(pjmedia_rtcp_sr))); } } else if (common->pt == RTCP_RR && common->count > 0) { rr = (pjmedia_rtcp_rr*)(((char*)pkt) + sizeof(pjmedia_rtcp_common)); #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) } else if (common->pt == RTCP_XR) { if (sess->xr_enabled) pjmedia_rtcp_xr_rx_rtcp_xr(&sess->xr_session, pkt, size); return; #endif } if (sr) { /* Save LSR from NTP timestamp of RTCP packet */ sess->rx_lsr = ((pj_ntohl(sr->ntp_sec) & 0x0000FFFF) << 16) | ((pj_ntohl(sr->ntp_frac) >> 16) & 0xFFFF); /* Calculate SR arrival time for DLSR */ pj_get_timestamp(&sess->rx_lsr_time); TRACE_((sess->name, "Rx RTCP SR: ntp_ts=%p", sess->rx_lsr, (pj_uint32_t)(sess->rx_lsr_time.u64*65536/sess->ts_freq.u64))); } /* Nothing more to do if there's no RR packet */ if (rr == NULL) return; last_loss = sess->stat.tx.loss; /* Get packet loss */ sess->stat.tx.loss = (rr->total_lost_2 << 16) + (rr->total_lost_1 << 8) + rr->total_lost_0; TRACE_((sess->name, "Rx RTCP RR: total_lost_2=%x, 1=%x, 0=%x, lost=%d", (int)rr->total_lost_2, (int)rr->total_lost_1, (int)rr->total_lost_0, sess->stat.tx.loss)); /* We can't calculate the exact loss period for TX, so just give the * best estimation. */ if (sess->stat.tx.loss > last_loss) { unsigned period; /* Loss period in msec */ period = (sess->stat.tx.loss - last_loss) * sess->pkt_size * 1000 / sess->clock_rate; /* Loss period in usec */ period *= 1000; /* Update loss period stat */ pj_math_stat_update(&sess->stat.tx.loss_period, period); } /* Get jitter value in usec */ jitter_samp = pj_ntohl(rr->jitter); /* Calculate jitter in usec, avoiding overflows */ if (jitter_samp <= 4294) jitter = jitter_samp * 1000000 / sess->clock_rate; else { jitter = jitter_samp * 1000 / sess->clock_rate; jitter *= 1000; } /* Update jitter statistics */ pj_math_stat_update(&sess->stat.tx.jitter, jitter); /* Can only calculate if LSR and DLSR is present in RR */ if (rr->lsr && rr->dlsr) { pj_uint32_t lsr, now, dlsr; pj_uint64_t eedelay; pjmedia_rtcp_ntp_rec ntp; /* LSR is the middle 32bit of NTP. It has 1/65536 second * resolution */ lsr = pj_ntohl(rr->lsr); /* DLSR is delay since LSR, also in 1/65536 resolution */ dlsr = pj_ntohl(rr->dlsr); /* Get current time, and convert to 1/65536 resolution */ pjmedia_rtcp_get_ntp_time(sess, &ntp); now = ((ntp.hi & 0xFFFF) << 16) + (ntp.lo >> 16); /* End-to-end delay is (now-lsr-dlsr) */ eedelay = now - lsr - dlsr; /* Convert end to end delay to usec (keeping the calculation in * 64bit space):: * sess->ee_delay = (eedelay * 1000) / 65536; */ if (eedelay < 4294) { eedelay = (eedelay * 1000000) >> 16; } else { eedelay = (eedelay * 1000) >> 16; eedelay *= 1000; } TRACE_((sess->name, "Rx RTCP RR: lsr=%p, dlsr=%p (%d:%03dms), " "now=%p, rtt=%p", lsr, dlsr, dlsr/65536, (dlsr%65536)*1000/65536, now, (pj_uint32_t)eedelay)); /* Only save calculation if "now" is greater than lsr, or * otherwise rtt will be invalid */ if (now-dlsr >= lsr) { unsigned rtt = (pj_uint32_t)eedelay; /* Check that eedelay value really makes sense. * We allow up to 30 seconds RTT! */ if (eedelay > 30 * 1000 * 1000UL) { TRACE_((sess->name, "RTT not making any sense, ignored..")); goto end_rtt_calc; } #if defined(PJMEDIA_RTCP_NORMALIZE_FACTOR) && PJMEDIA_RTCP_NORMALIZE_FACTOR!=0 /* "Normalize" rtt value that is exceptionally high. For such * values, "normalize" the rtt to be PJMEDIA_RTCP_NORMALIZE_FACTOR * times the average value. */ if (rtt > ((unsigned)sess->stat.rtt.mean * PJMEDIA_RTCP_NORMALIZE_FACTOR) && sess->stat.rtt.n!=0) { unsigned orig_rtt = rtt; rtt = sess->stat.rtt.mean * PJMEDIA_RTCP_NORMALIZE_FACTOR; PJ_LOG(5,(sess->name, "RTT value %d usec is normalized to %d usec", orig_rtt, rtt)); } #endif TRACE_((sess->name, "RTCP RTT is set to %d usec", rtt)); /* Update RTT stat */ pj_math_stat_update(&sess->stat.rtt, rtt); } else { PJ_LOG(5, (sess->name, "Internal RTCP NTP clock skew detected: " "lsr=%p, now=%p, dlsr=%p (%d:%03dms), " "diff=%d", lsr, now, dlsr, dlsr/65536, (dlsr%65536)*1000/65536, dlsr-(now-lsr))); } } end_rtt_calc: pj_gettimeofday(&sess->stat.tx.update); sess->stat.tx.update_cnt++; } static void parse_rtcp_sdes(pjmedia_rtcp_session *sess, const void *pkt, pj_size_t size) { pjmedia_rtcp_sdes *sdes = &sess->stat.peer_sdes; char *p, *p_end; char *b, *b_end; p = (char*)pkt + 8; p_end = (char*)pkt + size; pj_bzero(sdes, sizeof(*sdes)); b = sess->stat.peer_sdes_buf_; b_end = b + sizeof(sess->stat.peer_sdes_buf_); while (p < p_end) { pj_uint8_t sdes_type, sdes_len; pj_str_t sdes_value = {NULL, 0}; sdes_type = *p++; /* Check for end of SDES item list */ if (sdes_type == RTCP_SDES_NULL || p == p_end) break; sdes_len = *p++; /* Check for corrupted SDES packet */ if (p + sdes_len > p_end) break; /* Get SDES item */ if (b + sdes_len < b_end) { pj_memcpy(b, p, sdes_len); sdes_value.ptr = b; sdes_value.slen = sdes_len; b += sdes_len; } else { /* Insufficient SDES buffer */ PJ_LOG(5, (sess->name, "Unsufficient buffer to save RTCP SDES type %d:%.*s", sdes_type, sdes_len, p)); p += sdes_len; continue; } switch (sdes_type) { case RTCP_SDES_CNAME: sdes->cname = sdes_value; break; case RTCP_SDES_NAME: sdes->name = sdes_value; break; case RTCP_SDES_EMAIL: sdes->email = sdes_value; break; case RTCP_SDES_PHONE: sdes->phone = sdes_value; break; case RTCP_SDES_LOC: sdes->loc = sdes_value; break; case RTCP_SDES_TOOL: sdes->tool = sdes_value; break; case RTCP_SDES_NOTE: sdes->note = sdes_value; break; default: TRACE_((sess->name, "Received unknown RTCP SDES type %d:%.*s", sdes_type, sdes_value.slen, sdes_value.ptr)); break; } p += sdes_len; } } static void parse_rtcp_bye(pjmedia_rtcp_session *sess, const void *pkt, pj_size_t size) { pj_str_t reason = {"-", 1}; /* Check and get BYE reason */ if (size > 8) { reason.slen = PJ_MIN(sizeof(sess->stat.peer_sdes_buf_), *((pj_uint8_t*)pkt+8)); pj_memcpy(sess->stat.peer_sdes_buf_, ((pj_uint8_t*)pkt+9), reason.slen); reason.ptr = sess->stat.peer_sdes_buf_; } /* Just print RTCP BYE log */ PJ_LOG(5, (sess->name, "Received RTCP BYE, reason: %.*s", reason.slen, reason.ptr)); } static void parse_rtcp_psfb(pjmedia_rtcp_session *sess, const void *pkt, pj_size_t size) { pjmedia_rtcp_common *common = (pjmedia_rtcp_common*)pkt; pj_assert(common->pt == RTCP_PSFB); if (common->count == 1) { /* It's a PLI */ PJ_LOG(5, (sess->name, "Received RTCP PLI")); sess->keyframe_requested = PJ_TRUE; } } PJ_DEF(void) pjmedia_rtcp_rx_rtcp( pjmedia_rtcp_session *sess, const void *pkt, pj_size_t size) { pj_uint8_t *p, *p_end; sess->keyframe_requested = PJ_FALSE; p = (pj_uint8_t*)pkt; p_end = p + size; while (p < p_end) { pjmedia_rtcp_common *common = (pjmedia_rtcp_common*)p; unsigned len; len = (pj_ntohs((pj_uint16_t)common->length)+1) * 4; switch(common->pt) { case RTCP_SR: case RTCP_RR: case RTCP_XR: parse_rtcp_report(sess, p, len); break; case RTCP_SDES: parse_rtcp_sdes(sess, p, len); break; case RTCP_BYE: parse_rtcp_bye(sess, p, len); break; case RTCP_PSFB: parse_rtcp_psfb(sess, p, len); break; default: /* Ignore unknown RTCP */ TRACE_((sess->name, "Received unknown RTCP packet type=%d", common->pt)); break; } p += len; } } PJ_DEF(void) pjmedia_rtcp_build_rtcp(pjmedia_rtcp_session *sess, void **ret_p_pkt, int *len) { pj_uint32_t expected, expected_interval, received_interval, lost_interval; pjmedia_rtcp_sr *sr; pjmedia_rtcp_rr *rr; pj_timestamp ts_now; pjmedia_rtcp_ntp_rec ntp; /* Get current NTP time. */ pj_get_timestamp(&ts_now); pjmedia_rtcp_get_ntp_time(sess, &ntp); /* See if we have transmitted RTP packets since last time we * sent RTCP SR. */ if (sess->stat.tx.pkt != pj_ntohl(sess->rtcp_sr_pkt.sr.sender_pcount)) { pj_time_val ts_time; pj_uint32_t rtp_ts; /* So we should send RTCP SR */ *ret_p_pkt = (void*) &sess->rtcp_sr_pkt; *len = sizeof(pjmedia_rtcp_sr_pkt); rr = &sess->rtcp_sr_pkt.rr; sr = &sess->rtcp_sr_pkt.sr; /* Update packet count */ sr->sender_pcount = pj_htonl(sess->stat.tx.pkt); /* Update octets count */ sr->sender_bcount = pj_htonl(sess->stat.tx.bytes); /* Fill in NTP timestamp in SR. */ sr->ntp_sec = pj_htonl(ntp.hi); sr->ntp_frac = pj_htonl(ntp.lo); /* Fill in RTP timestamp (corresponds to NTP timestamp) in SR. */ ts_time.sec = ntp.hi - sess->tv_base.sec - JAN_1970; ts_time.msec = (long)(ntp.lo * 1000.0 / 0xFFFFFFFF); rtp_ts = sess->rtp_ts_base + (pj_uint32_t)(sess->clock_rate*ts_time.sec) + (pj_uint32_t)(sess->clock_rate*ts_time.msec/1000); sr->rtp_ts = pj_htonl(rtp_ts); TRACE_((sess->name, "TX RTCP SR: ntp_ts=%p", ((ntp.hi & 0xFFFF) << 16) + ((ntp.lo & 0xFFFF0000) >> 16))); } else { /* We should send RTCP RR then */ *ret_p_pkt = (void*) &sess->rtcp_rr_pkt; *len = sizeof(pjmedia_rtcp_rr_pkt); rr = &sess->rtcp_rr_pkt.rr; sr = NULL; } /* SSRC and last_seq */ rr->ssrc = pj_htonl(sess->peer_ssrc); rr->last_seq = (sess->seq_ctrl.cycles & 0xFFFF0000L); /* Since this is an "+=" operation, make sure we update last_seq on * both RR and SR. */ sess->rtcp_sr_pkt.rr.last_seq += sess->seq_ctrl.max_seq; sess->rtcp_rr_pkt.rr.last_seq += sess->seq_ctrl.max_seq; rr->last_seq = pj_htonl(rr->last_seq); /* Jitter */ rr->jitter = pj_htonl(sess->jitter >> 4); /* Total lost. */ expected = pj_ntohl(rr->last_seq) - sess->seq_ctrl.base_seq; /* This is bug: total lost already calculated on each incoming RTP! if (expected >= sess->received) sess->stat.rx.loss = expected - sess->received; else sess->stat.rx.loss = 0; */ rr->total_lost_2 = (sess->stat.rx.loss >> 16) & 0xFF; rr->total_lost_1 = (sess->stat.rx.loss >> 8) & 0xFF; rr->total_lost_0 = (sess->stat.rx.loss & 0xFF); /* Fraction lost calculation */ expected_interval = expected - sess->exp_prior; sess->exp_prior = expected; received_interval = sess->received - sess->rx_prior; sess->rx_prior = sess->received; if (expected_interval >= received_interval) lost_interval = expected_interval - received_interval; else lost_interval = 0; if (expected_interval==0 || lost_interval == 0) { rr->fract_lost = 0; } else { rr->fract_lost = (lost_interval << 8) / expected_interval; } if (sess->rx_lsr_time.u64 == 0 || sess->rx_lsr == 0) { rr->lsr = 0; rr->dlsr = 0; } else { pj_timestamp ts; pj_uint32_t lsr = sess->rx_lsr; pj_uint64_t lsr_time = sess->rx_lsr_time.u64; pj_uint32_t dlsr; /* Convert LSR time to 1/65536 seconds resolution */ lsr_time = (lsr_time << 16) / sess->ts_freq.u64; /* Fill in LSR. LSR is the middle 32bit of the last SR NTP time received. */ rr->lsr = pj_htonl(lsr); /* Fill in DLSR. DLSR is Delay since Last SR, in 1/65536 seconds. */ ts.u64 = ts_now.u64; /* Convert interval to 1/65536 seconds value */ ts.u64 = (ts.u64 << 16) / sess->ts_freq.u64; /* Get DLSR */ dlsr = (pj_uint32_t)(ts.u64 - lsr_time); rr->dlsr = pj_htonl(dlsr); TRACE_((sess->name,"Tx RTCP RR: lsr=%p, lsr_time=%p, now=%p, dlsr=%p" "(%ds:%03dms)", lsr, (pj_uint32_t)lsr_time, (pj_uint32_t)ts.u64, dlsr, dlsr/65536, (dlsr%65536)*1000/65536 )); } /* Update counter */ pj_gettimeofday(&sess->stat.rx.update); sess->stat.rx.update_cnt++; } PJ_DEF(pj_status_t) pjmedia_rtcp_build_rtcp_sdes( pjmedia_rtcp_session *session, void *buf, pj_size_t *length, const pjmedia_rtcp_sdes *sdes) { pjmedia_rtcp_common *hdr; pj_uint8_t *p; pj_size_t len; PJ_ASSERT_RETURN(session && buf && length && sdes, PJ_EINVAL); /* Verify SDES item length */ if (sdes->cname.slen > 255 || sdes->name.slen > 255 || sdes->email.slen > 255 || sdes->phone.slen > 255 || sdes->loc.slen > 255 || sdes->tool.slen > 255 || sdes->note.slen > 255) { return PJ_EINVAL; } /* Verify buffer length */ len = sizeof(*hdr); if (sdes->cname.slen) len += sdes->cname.slen + 2; if (sdes->name.slen) len += sdes->name.slen + 2; if (sdes->email.slen) len += sdes->email.slen + 2; if (sdes->phone.slen) len += sdes->phone.slen + 2; if (sdes->loc.slen) len += sdes->loc.slen + 2; if (sdes->tool.slen) len += sdes->tool.slen + 2; if (sdes->note.slen) len += sdes->note.slen + 2; len++; /* null termination */ len = ((len+3)/4) * 4; if (len > *length) return PJ_ETOOSMALL; /* Build RTCP SDES header */ hdr = (pjmedia_rtcp_common*)buf; pj_memcpy(hdr, &session->rtcp_sr_pkt.common, sizeof(*hdr)); hdr->pt = RTCP_SDES; hdr->length = pj_htons((pj_uint16_t)(len/4 - 1)); /* Build RTCP SDES items */ p = (pj_uint8_t*)hdr + sizeof(*hdr); #define BUILD_SDES_ITEM(SDES_NAME, SDES_TYPE) \ if (sdes->SDES_NAME.slen) { \ *p++ = SDES_TYPE; \ *p++ = (pj_uint8_t)sdes->SDES_NAME.slen; \ pj_memcpy(p, sdes->SDES_NAME.ptr, sdes->SDES_NAME.slen); \ p += sdes->SDES_NAME.slen; \ } BUILD_SDES_ITEM(cname, RTCP_SDES_CNAME); BUILD_SDES_ITEM(name, RTCP_SDES_NAME); BUILD_SDES_ITEM(email, RTCP_SDES_EMAIL); BUILD_SDES_ITEM(phone, RTCP_SDES_PHONE); BUILD_SDES_ITEM(loc, RTCP_SDES_LOC); BUILD_SDES_ITEM(tool, RTCP_SDES_TOOL); BUILD_SDES_ITEM(note, RTCP_SDES_NOTE); #undef BUILD_SDES_ITEM /* Null termination */ *p++ = 0; /* Pad to 32bit */ while ((p-(pj_uint8_t*)buf) % 4) *p++ = 0; /* Finally */ pj_assert((int)len == p-(pj_uint8_t*)buf); *length = len; return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_rtcp_build_rtcp_bye(pjmedia_rtcp_session *session, void *buf, pj_size_t *length, const pj_str_t *reason) { pjmedia_rtcp_common *hdr; pj_uint8_t *p; pj_size_t len; PJ_ASSERT_RETURN(session && buf && length, PJ_EINVAL); /* Verify BYE reason length */ if (reason && reason->slen > 255) return PJ_EINVAL; /* Verify buffer length */ len = sizeof(*hdr); if (reason && reason->slen) len += reason->slen + 1; len = ((len+3)/4) * 4; if (len > *length) return PJ_ETOOSMALL; /* Build RTCP BYE header */ hdr = (pjmedia_rtcp_common*)buf; pj_memcpy(hdr, &session->rtcp_sr_pkt.common, sizeof(*hdr)); hdr->pt = RTCP_BYE; hdr->length = pj_htons((pj_uint16_t)(len/4 - 1)); /* Write RTCP BYE reason */ p = (pj_uint8_t*)hdr + sizeof(*hdr); if (reason && reason->slen) { *p++ = (pj_uint8_t)reason->slen; pj_memcpy(p, reason->ptr, reason->slen); p += reason->slen; } /* Pad to 32bit */ while ((p-(pj_uint8_t*)buf) % 4) *p++ = 0; pj_assert((int)len == p-(pj_uint8_t*)buf); *length = len; return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_rtcp_build_rtcp_pli(pjmedia_rtcp_session *session, void *buf, pj_size_t *length) { pjmedia_rtcp_common *hdr; pj_uint8_t *p; pj_size_t len = 12; /* pjmedia_rtcp_common + media SSRC (uint32_t) */ PJ_ASSERT_RETURN(session && buf && length, PJ_EINVAL); /* Verify buffer length */ if (len > *length) return PJ_ETOOSMALL; /* Build RTCP PLI */ hdr = (pjmedia_rtcp_common*)buf; pj_memcpy(hdr, &session->rtcp_sr_pkt.common, sizeof(*hdr)); hdr->pt = RTCP_PSFB; hdr->count = 1; /* FMT: 1 == Picture Loss Indication (PLI) */ hdr->length = pj_htons((pj_uint16_t)(len/4 - 1)); p = (pj_uint8_t*)hdr + sizeof(*hdr); pj_memset(p, 0, (pj_uint8_t*)hdr + len - p); *length = len; return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_rtcp_enable_xr( pjmedia_rtcp_session *sess, pj_bool_t enable) { #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) /* Check if request won't change anything */ if (!(enable ^ sess->xr_enabled)) return PJ_SUCCESS; if (!enable) { sess->xr_enabled = PJ_FALSE; return PJ_SUCCESS; } pjmedia_rtcp_xr_init(&sess->xr_session, sess, 0, 1); sess->xr_enabled = PJ_TRUE; return PJ_SUCCESS; #else PJ_UNUSED_ARG(sess); PJ_UNUSED_ARG(enable); return PJ_ENOTSUP; #endif } diff --git a/deps/pjsip/pjmedia/src/pjmedia/sdp_neg.c b/deps/pjsip/pjmedia/src/pjmedia/sdp_neg.c index 029bb29b..190d3a0e 100644 --- a/deps/pjsip/pjmedia/src/pjmedia/sdp_neg.c +++ b/deps/pjsip/pjmedia/src/pjmedia/sdp_neg.c @@ -1,1632 +1,1632 @@ -/* $Id: sdp_neg.c 4498 2013-04-24 09:52:25Z bennylp $ */ +/* $Id: sdp_neg.c 5170 2015-08-25 08:45:46Z nanang $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include /** * This structure describes SDP media negotiator. */ struct pjmedia_sdp_neg { pjmedia_sdp_neg_state state; /**< Negotiator state. */ pj_bool_t prefer_remote_codec_order; pj_bool_t answer_with_multiple_codecs; pj_bool_t has_remote_answer; pj_bool_t answer_was_remote; pjmedia_sdp_session *initial_sdp, /**< Initial local SDP */ *initial_sdp_tmp, /**< Temporary initial local SDP */ *active_local_sdp, /**< Currently active local SDP. */ *active_remote_sdp, /**< Currently active remote's. */ *neg_local_sdp, /**< Temporary local SDP. */ *neg_remote_sdp; /**< Temporary remote SDP. */ }; static const char *state_str[] = { "STATE_NULL", "STATE_LOCAL_OFFER", "STATE_REMOTE_OFFER", "STATE_WAIT_NEGO", "STATE_DONE", }; /* Definition of customized SDP format negotiation callback */ struct fmt_match_cb_t { pj_str_t fmt_name; pjmedia_sdp_neg_fmt_match_cb cb; }; /* Number of registered customized SDP format negotiation callbacks */ static unsigned fmt_match_cb_cnt; /* The registered customized SDP format negotiation callbacks */ static struct fmt_match_cb_t fmt_match_cb[PJMEDIA_SDP_NEG_MAX_CUSTOM_FMT_NEG_CB]; /* Redefining a very long identifier name, just for convenience */ #define ALLOW_MODIFY_ANSWER PJMEDIA_SDP_NEG_FMT_MATCH_ALLOW_MODIFY_ANSWER static pj_status_t custom_fmt_match( pj_pool_t *pool, const pj_str_t *fmt_name, pjmedia_sdp_media *offer, unsigned o_fmt_idx, pjmedia_sdp_media *answer, unsigned a_fmt_idx, unsigned option); /* * Get string representation of negotiator state. */ PJ_DEF(const char*) pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_state state) { if ((int)state >=0 && state < (pjmedia_sdp_neg_state)PJ_ARRAY_SIZE(state_str)) return state_str[state]; return ""; } /* * Create with local offer. */ PJ_DEF(pj_status_t) pjmedia_sdp_neg_create_w_local_offer( pj_pool_t *pool, const pjmedia_sdp_session *local, pjmedia_sdp_neg **p_neg) { pjmedia_sdp_neg *neg; pj_status_t status; /* Check arguments are valid. */ PJ_ASSERT_RETURN(pool && local && p_neg, PJ_EINVAL); *p_neg = NULL; /* Validate local offer. */ PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(local))==PJ_SUCCESS, status); /* Create and initialize negotiator. */ neg = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_neg); PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM); neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER; neg->prefer_remote_codec_order = PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER; neg->answer_with_multiple_codecs = PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS; neg->initial_sdp = pjmedia_sdp_session_clone(pool, local); neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local); *p_neg = neg; return PJ_SUCCESS; } /* * Create with remote offer and initial local offer/answer. */ PJ_DEF(pj_status_t) pjmedia_sdp_neg_create_w_remote_offer(pj_pool_t *pool, const pjmedia_sdp_session *initial, const pjmedia_sdp_session *remote, pjmedia_sdp_neg **p_neg) { pjmedia_sdp_neg *neg; pj_status_t status; /* Check arguments are valid. */ PJ_ASSERT_RETURN(pool && remote && p_neg, PJ_EINVAL); *p_neg = NULL; /* Validate remote offer and initial answer */ status = pjmedia_sdp_validate2(remote, PJ_FALSE); if (status != PJ_SUCCESS) return status; /* Create and initialize negotiator. */ neg = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_neg); PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM); neg->prefer_remote_codec_order = PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER; neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); if (initial) { PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(initial))==PJ_SUCCESS, status); neg->initial_sdp = pjmedia_sdp_session_clone(pool, initial); neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, initial); neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO; } else { neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER; } *p_neg = neg; return PJ_SUCCESS; } /* * Set codec order preference. */ PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_prefer_remote_codec_order( pjmedia_sdp_neg *neg, pj_bool_t prefer_remote) { PJ_ASSERT_RETURN(neg, PJ_EINVAL); neg->prefer_remote_codec_order = prefer_remote; return PJ_SUCCESS; } /* * Set multiple codec answering. */ PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_answer_multiple_codecs( pjmedia_sdp_neg *neg, pj_bool_t answer_multiple) { PJ_ASSERT_RETURN(neg, PJ_EINVAL); neg->answer_with_multiple_codecs = answer_multiple; return PJ_SUCCESS; } /* * Get SDP negotiator state. */ PJ_DEF(pjmedia_sdp_neg_state) pjmedia_sdp_neg_get_state( pjmedia_sdp_neg *neg ) { /* Check arguments are valid. */ PJ_ASSERT_RETURN(neg != NULL, PJMEDIA_SDP_NEG_STATE_NULL); return neg->state; } PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_active_local( pjmedia_sdp_neg *neg, const pjmedia_sdp_session **local) { PJ_ASSERT_RETURN(neg && local, PJ_EINVAL); PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE); *local = neg->active_local_sdp; return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_active_remote( pjmedia_sdp_neg *neg, const pjmedia_sdp_session **remote) { PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL); PJ_ASSERT_RETURN(neg->active_remote_sdp, PJMEDIA_SDPNEG_ENOACTIVE); *remote = neg->active_remote_sdp; return PJ_SUCCESS; } PJ_DEF(pj_bool_t) pjmedia_sdp_neg_was_answer_remote(pjmedia_sdp_neg *neg) { PJ_ASSERT_RETURN(neg, PJ_FALSE); return neg->answer_was_remote; } PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_neg_remote( pjmedia_sdp_neg *neg, const pjmedia_sdp_session **remote) { PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL); PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJMEDIA_SDPNEG_ENONEG); *remote = neg->neg_remote_sdp; return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_neg_local( pjmedia_sdp_neg *neg, const pjmedia_sdp_session **local) { PJ_ASSERT_RETURN(neg && local, PJ_EINVAL); PJ_ASSERT_RETURN(neg->neg_local_sdp, PJMEDIA_SDPNEG_ENONEG); *local = neg->neg_local_sdp; return PJ_SUCCESS; } static pjmedia_sdp_media *sdp_media_clone_deactivate( pj_pool_t *pool, const pjmedia_sdp_media *rem_med, const pjmedia_sdp_media *local_med, const pjmedia_sdp_session *local_sess) { pjmedia_sdp_media *res; res = pjmedia_sdp_media_clone_deactivate(pool, rem_med); if (!res) return NULL; if (!res->conn && (!local_sess || !local_sess->conn)) { if (local_med && local_med->conn) res->conn = pjmedia_sdp_conn_clone(pool, local_med->conn); else { res->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn); res->conn->net_type = pj_str("IN"); res->conn->addr_type = pj_str("IP4"); res->conn->addr = pj_str("127.0.0.1"); } } return res; } /* * Modify local SDP and wait for remote answer. */ PJ_DEF(pj_status_t) pjmedia_sdp_neg_modify_local_offer( pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session *local) { return pjmedia_sdp_neg_modify_local_offer2(pool, neg, 0, local); } PJ_DEF(pj_status_t) pjmedia_sdp_neg_modify_local_offer2( pj_pool_t *pool, pjmedia_sdp_neg *neg, unsigned flags, const pjmedia_sdp_session *local) { pjmedia_sdp_session *new_offer; pjmedia_sdp_session *old_offer; char media_used[PJMEDIA_MAX_SDP_MEDIA]; unsigned oi; /* old offer media index */ pj_status_t status; /* Check arguments are valid. */ PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL); /* Can only do this in STATE_DONE. */ PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE, PJMEDIA_SDPNEG_EINSTATE); /* Validate the new offer */ status = pjmedia_sdp_validate(local); if (status != PJ_SUCCESS) return status; /* Change state to STATE_LOCAL_OFFER */ neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER; /* Init vars */ pj_bzero(media_used, sizeof(media_used)); old_offer = neg->active_local_sdp; new_offer = pjmedia_sdp_session_clone(pool, local); /* RFC 3264 Section 8: When issuing an offer that modifies the session, * the "o=" line of the new SDP MUST be identical to that in the * previous SDP, except that the version in the origin field MUST * increment by one from the previous SDP. */ pj_strdup(pool, &new_offer->origin.user, &old_offer->origin.user); new_offer->origin.id = old_offer->origin.id; new_offer->origin.version = old_offer->origin.version + 1; pj_strdup(pool, &new_offer->origin.net_type, &old_offer->origin.net_type); pj_strdup(pool, &new_offer->origin.addr_type,&old_offer->origin.addr_type); pj_strdup(pool, &new_offer->origin.addr, &old_offer->origin.addr); if ((flags & PJMEDIA_SDP_NEG_ALLOW_MEDIA_CHANGE) == 0) { /* Generating the new offer, in the case media lines doesn't match the * active SDP (e.g. current/active SDP's have m=audio and m=video lines, * and the new offer only has m=audio line), the negotiator will fix * the new offer by reordering and adding the missing media line with * port number set to zero. */ for (oi = 0; oi < old_offer->media_count; ++oi) { pjmedia_sdp_media *om; pjmedia_sdp_media *nm; unsigned ni; /* new offer media index */ pj_bool_t found = PJ_FALSE; om = old_offer->media[oi]; for (ni = oi; ni < new_offer->media_count; ++ni) { nm = new_offer->media[ni]; if (pj_strcmp(&nm->desc.media, &om->desc.media) == 0) { if (ni != oi) { /* The same media found but the position unmatched to * the old offer, so let's put this media in the right * place, and keep the order of the rest. */ pj_array_insert( new_offer->media, /* array */ sizeof(new_offer->media[0]), /* elmt size*/ ni, /* count */ oi, /* pos */ &nm); /* new elmt */ } found = PJ_TRUE; break; } } if (!found) { pjmedia_sdp_media *m; m = sdp_media_clone_deactivate(pool, om, om, local); pj_array_insert(new_offer->media, sizeof(new_offer->media[0]), new_offer->media_count++, oi, &m); } } } else { /* If media type change is allowed, the negotiator only needs to fix * the new offer by adding the missing media line(s) with port number * set to zero. */ for (oi = new_offer->media_count; oi < old_offer->media_count; ++oi) { pjmedia_sdp_media *m; m = sdp_media_clone_deactivate(pool, old_offer->media[oi], old_offer->media[oi], local); pj_array_insert(new_offer->media, sizeof(new_offer->media[0]), new_offer->media_count++, oi, &m); } } /* New_offer fixed */ neg->initial_sdp_tmp = neg->initial_sdp; neg->initial_sdp = new_offer; neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, new_offer); return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_sdp_neg_send_local_offer( pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session **offer) { /* Check arguments are valid. */ PJ_ASSERT_RETURN(neg && offer, PJ_EINVAL); *offer = NULL; /* Can only do this in STATE_DONE or STATE_LOCAL_OFFER. */ PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE || neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, PJMEDIA_SDPNEG_EINSTATE); if (neg->state == PJMEDIA_SDP_NEG_STATE_DONE) { /* If in STATE_DONE, set the active SDP as the offer. */ PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE); /* Retain initial SDP */ if (neg->initial_sdp) { neg->initial_sdp_tmp = neg->initial_sdp; neg->initial_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp); } neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER; neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, neg->active_local_sdp); *offer = neg->active_local_sdp; } else { /* We assume that we're in STATE_LOCAL_OFFER. * In this case set the neg_local_sdp as the offer. */ *offer = neg->neg_local_sdp; } return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_remote_answer( pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session *remote) { /* Check arguments are valid. */ PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL); /* Can only do this in STATE_LOCAL_OFFER. * If we haven't provided local offer, then rx_remote_offer() should * be called instead of this function. */ PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, PJMEDIA_SDPNEG_EINSTATE); /* We're ready to negotiate. */ neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO; neg->has_remote_answer = PJ_TRUE; neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_remote_offer( pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session *remote) { /* Check arguments are valid. */ PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL); /* Can only do this in STATE_DONE. * If we already provide local offer, then rx_remote_answer() should * be called instead of this function. */ PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE, PJMEDIA_SDPNEG_EINSTATE); /* State now is STATE_REMOTE_OFFER. */ neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER; neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_local_answer( pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session *local) { /* Check arguments are valid. */ PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL); /* Can only do this in STATE_REMOTE_OFFER. * If we already provide local offer, then rx_remote_answer() should * be called instead of this function. */ PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER, PJMEDIA_SDPNEG_EINSTATE); /* State now is STATE_WAIT_NEGO. */ neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO; if (local) { neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local); if (neg->initial_sdp) { /* Retain initial_sdp value. */ neg->initial_sdp_tmp = neg->initial_sdp; neg->initial_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp); /* I don't think there is anything in RFC 3264 that mandates * answerer to place the same origin (and increment version) * in the answer, but probably it won't hurt either. * Note that the version will be incremented in * pjmedia_sdp_neg_negotiate() */ neg->neg_local_sdp->origin.id = neg->initial_sdp->origin.id; } else { neg->initial_sdp = pjmedia_sdp_session_clone(pool, local); } } else { PJ_ASSERT_RETURN(neg->initial_sdp, PJMEDIA_SDPNEG_ENOINITIAL); neg->initial_sdp_tmp = neg->initial_sdp; neg->initial_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp); neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp); } return PJ_SUCCESS; } PJ_DEF(pj_bool_t) pjmedia_sdp_neg_has_local_answer(pjmedia_sdp_neg *neg) { pj_assert(neg && neg->state==PJMEDIA_SDP_NEG_STATE_WAIT_NEGO); return !neg->has_remote_answer; } /* Swap string. */ static void str_swap(pj_str_t *str1, pj_str_t *str2) { pj_str_t tmp = *str1; *str1 = *str2; *str2 = tmp; } static void remove_all_media_directions(pjmedia_sdp_media *m) { pjmedia_sdp_media_remove_all_attr(m, "inactive"); pjmedia_sdp_media_remove_all_attr(m, "sendrecv"); pjmedia_sdp_media_remove_all_attr(m, "sendonly"); pjmedia_sdp_media_remove_all_attr(m, "recvonly"); } /* Update media direction based on peer's media direction */ static void update_media_direction(pj_pool_t *pool, const pjmedia_sdp_media *remote, pjmedia_sdp_media *local) { pjmedia_dir old_dir = PJMEDIA_DIR_ENCODING_DECODING, new_dir; /* Get the media direction of local SDP */ if (pjmedia_sdp_media_find_attr2(local, "sendonly", NULL)) old_dir = PJMEDIA_DIR_ENCODING; else if (pjmedia_sdp_media_find_attr2(local, "recvonly", NULL)) old_dir = PJMEDIA_DIR_DECODING; else if (pjmedia_sdp_media_find_attr2(local, "inactive", NULL)) old_dir = PJMEDIA_DIR_NONE; new_dir = old_dir; /* Adjust local media direction based on remote media direction */ if (pjmedia_sdp_media_find_attr2(remote, "inactive", NULL) != NULL) { /* If remote has "a=inactive", then local is inactive too */ new_dir = PJMEDIA_DIR_NONE; } else if(pjmedia_sdp_media_find_attr2(remote, "sendonly", NULL) != NULL) { /* If remote has "a=sendonly", then set local to "recvonly" if * it is currently "sendrecv". Otherwise if local is NOT "recvonly", * then set local direction to "inactive". */ switch (old_dir) { case PJMEDIA_DIR_ENCODING_DECODING: new_dir = PJMEDIA_DIR_DECODING; break; case PJMEDIA_DIR_DECODING: /* No change */ break; default: new_dir = PJMEDIA_DIR_NONE; break; } } else if(pjmedia_sdp_media_find_attr2(remote, "recvonly", NULL) != NULL) { /* If remote has "a=recvonly", then set local to "sendonly" if * it is currently "sendrecv". Otherwise if local is NOT "sendonly", * then set local direction to "inactive" */ switch (old_dir) { case PJMEDIA_DIR_ENCODING_DECODING: new_dir = PJMEDIA_DIR_ENCODING; break; case PJMEDIA_DIR_ENCODING: /* No change */ break; default: new_dir = PJMEDIA_DIR_NONE; break; } } else { /* Remote indicates "sendrecv" capability. No change to local * direction */ } if (new_dir != old_dir) { pjmedia_sdp_attr *a = NULL; remove_all_media_directions(local); switch (new_dir) { case PJMEDIA_DIR_NONE: a = pjmedia_sdp_attr_create(pool, "inactive", NULL); break; case PJMEDIA_DIR_ENCODING: a = pjmedia_sdp_attr_create(pool, "sendonly", NULL); break; case PJMEDIA_DIR_DECODING: a = pjmedia_sdp_attr_create(pool, "recvonly", NULL); break; default: /* sendrecv */ break; } if (a) { pjmedia_sdp_media_add_attr(local, a); } } } /* Update single local media description to after receiving answer * from remote. */ static pj_status_t process_m_answer( pj_pool_t *pool, pjmedia_sdp_media *offer, pjmedia_sdp_media *answer, pj_bool_t allow_asym) { unsigned i; /* Check that the media type match our offer. */ if (pj_strcmp(&answer->desc.media, &offer->desc.media)!=0) { /* The media type in the answer is different than the offer! */ return PJMEDIA_SDPNEG_EINVANSMEDIA; } /* Check that transport in the answer match our offer. */ /* At this point, transport type must be compatible, * the transport instance will do more validation later. */ if (pjmedia_sdp_transport_cmp(&answer->desc.transport, &offer->desc.transport) != PJ_SUCCESS) { return PJMEDIA_SDPNEG_EINVANSTP; } /* Check if remote has rejected our offer */ if (answer->desc.port == 0) { /* Remote has rejected our offer. * Deactivate our media too. */ pjmedia_sdp_media_deactivate(pool, offer); /* Don't need to proceed */ return PJ_SUCCESS; } /* Ticket #1148: check if remote answer does not set port to zero when * offered with port zero. Let's just tolerate it. */ if (offer->desc.port == 0) { /* Don't need to proceed */ return PJ_SUCCESS; } /* No need to update the direction when processing an answer */ /* If asymetric media is allowed, then just check that remote answer has * codecs that are within the offer. * * Otherwise if asymetric media is not allowed, then we will choose only * one codec in our initial offer to match the answer. */ if (allow_asym) { for (i=0; idesc.fmt_count; ++i) { unsigned j; pj_str_t *rem_fmt = &answer->desc.fmt[i]; for (j=0; jdesc.fmt_count; ++j) { if (pj_strcmp(rem_fmt, &answer->desc.fmt[j])==0) break; } if (j != offer->desc.fmt_count) { /* Found at least one common codec. */ break; } } if (i == answer->desc.fmt_count) { /* No common codec in the answer! */ return PJMEDIA_SDPNEG_EANSNOMEDIA; } PJ_TODO(CHECK_SDP_NEGOTIATION_WHEN_ASYMETRIC_MEDIA_IS_ALLOWED); } else { /* Offer format priority based on answer format index/priority */ unsigned offer_fmt_prior[PJMEDIA_MAX_SDP_FMT]; /* Remove all format in the offer that has no matching answer */ for (i=0; idesc.fmt_count;) { unsigned pt; pj_uint32_t j; pj_str_t *fmt = &offer->desc.fmt[i]; /* Find matching answer */ pt = pj_strtoul(fmt); if (pt < 96) { for (j=0; jdesc.fmt_count; ++j) { if (pj_strcmp(fmt, &answer->desc.fmt[j])==0) break; } } else { /* This is dynamic payload type. * For dynamic payload type, we must look the rtpmap and * compare the encoding name. */ const pjmedia_sdp_attr *a; pjmedia_sdp_rtpmap or_; /* Get the rtpmap for the payload type in the offer. */ a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", fmt); if (!a) { pj_assert(!"Bug! Offer should have been validated"); return PJ_EBUG; } pjmedia_sdp_attr_get_rtpmap(a, &or_); /* Find paylaod in answer SDP with matching * encoding name and clock rate. */ for (j=0; jdesc.fmt_count; ++j) { a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", &answer->desc.fmt[j]); if (a) { pjmedia_sdp_rtpmap ar; pjmedia_sdp_attr_get_rtpmap(a, &ar); /* See if encoding name, clock rate, and channel * count match */ if (!pj_stricmp(&or_.enc_name, &ar.enc_name) && or_.clock_rate == ar.clock_rate && (pj_stricmp(&or_.param, &ar.param)==0 || (ar.param.slen==1 && *ar.param.ptr=='1'))) { /* Call custom format matching callbacks */ if (custom_fmt_match(pool, &or_.enc_name, offer, i, answer, j, 0) == PJ_SUCCESS) { /* Match! */ break; } } } } } if (j == answer->desc.fmt_count) { /* This format has no matching answer. * Remove it from our offer. */ pjmedia_sdp_attr *a; /* Remove rtpmap associated with this format */ a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", fmt); if (a) pjmedia_sdp_media_remove_attr(offer, a); /* Remove fmtp associated with this format */ a = pjmedia_sdp_media_find_attr2(offer, "fmtp", fmt); if (a) pjmedia_sdp_media_remove_attr(offer, a); /* Remove this format from offer's array */ pj_array_erase(offer->desc.fmt, sizeof(offer->desc.fmt[0]), offer->desc.fmt_count, i); --offer->desc.fmt_count; } else { offer_fmt_prior[i] = j; ++i; } } if (0 == offer->desc.fmt_count) { /* No common codec in the answer! */ return PJMEDIA_SDPNEG_EANSNOMEDIA; } /* Post process: * - Resort offer formats so the order match to the answer. * - Remove answer formats that unmatches to the offer. */ /* Resort offer formats */ for (i=0; idesc.fmt_count; ++i) { unsigned j; for (j=i+1; jdesc.fmt_count; ++j) { if (offer_fmt_prior[i] > offer_fmt_prior[j]) { unsigned tmp = offer_fmt_prior[i]; offer_fmt_prior[i] = offer_fmt_prior[j]; offer_fmt_prior[j] = tmp; str_swap(&offer->desc.fmt[i], &offer->desc.fmt[j]); } } } /* Remove unmatched answer formats */ { unsigned del_cnt = 0; for (i=0; idesc.fmt_count;) { /* The offer is ordered now, also the offer_fmt_prior */ if (i >= offer->desc.fmt_count || offer_fmt_prior[i]-del_cnt != i) { pj_str_t *fmt = &answer->desc.fmt[i]; pjmedia_sdp_attr *a; /* Remove rtpmap associated with this format */ a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", fmt); if (a) pjmedia_sdp_media_remove_attr(answer, a); /* Remove fmtp associated with this format */ a = pjmedia_sdp_media_find_attr2(answer, "fmtp", fmt); if (a) pjmedia_sdp_media_remove_attr(answer, a); /* Remove this format from answer's array */ pj_array_erase(answer->desc.fmt, sizeof(answer->desc.fmt[0]), answer->desc.fmt_count, i); --answer->desc.fmt_count; ++del_cnt; } else { ++i; } } } } /* Looks okay */ return PJ_SUCCESS; } /* Update local media session (offer) to create active local session * after receiving remote answer. */ static pj_status_t process_answer(pj_pool_t *pool, pjmedia_sdp_session *offer, pjmedia_sdp_session *answer, pj_bool_t allow_asym, pjmedia_sdp_session **p_active) { unsigned omi = 0; /* Offer media index */ unsigned ami = 0; /* Answer media index */ pj_bool_t has_active = PJ_FALSE; pj_status_t status; /* Check arguments. */ PJ_ASSERT_RETURN(pool && offer && answer && p_active, PJ_EINVAL); /* Check that media count match between offer and answer */ // Ticket #527, different media count is allowed for more interoperability, // however, the media order must be same between offer and answer. // if (offer->media_count != answer->media_count) // return PJMEDIA_SDPNEG_EMISMEDIA; /* Now update each media line in the offer with the answer. */ for (; omimedia_count; ++omi) { if (ami == answer->media_count) { /* The answer has less media than the offer */ pjmedia_sdp_media *am; /* Generate matching-but-disabled-media for the answer */ am = sdp_media_clone_deactivate(pool, offer->media[omi], offer->media[omi], offer); answer->media[answer->media_count++] = am; ++ami; /* Deactivate our media offer too */ pjmedia_sdp_media_deactivate(pool, offer->media[omi]); /* No answer media to be negotiated */ continue; } status = process_m_answer(pool, offer->media[omi], answer->media[ami], allow_asym); /* If media type is mismatched, just disable the media. */ if (status == PJMEDIA_SDPNEG_EINVANSMEDIA) { pjmedia_sdp_media_deactivate(pool, offer->media[omi]); continue; } /* No common format in the answer media. */ else if (status == PJMEDIA_SDPNEG_EANSNOMEDIA) { pjmedia_sdp_media_deactivate(pool, offer->media[omi]); pjmedia_sdp_media_deactivate(pool, answer->media[ami]); } /* Return the error code, for other errors. */ else if (status != PJ_SUCCESS) { return status; } if (offer->media[omi]->desc.port != 0) has_active = PJ_TRUE; ++ami; } *p_active = offer; return has_active ? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA; } /* Internal function to rewrite the format string in SDP attribute rtpmap * and fmtp. */ PJ_INLINE(void) rewrite_pt(pj_pool_t *pool, pj_str_t *attr_val, const pj_str_t *old_pt, const pj_str_t *new_pt) { int len_diff = (int)(new_pt->slen - old_pt->slen); /* Note that attribute value should be null-terminated. */ if (len_diff > 0) { pj_str_t new_val; new_val.ptr = (char*)pj_pool_alloc(pool, attr_val->slen+len_diff+1); new_val.slen = attr_val->slen + len_diff; pj_memcpy(new_val.ptr + len_diff, attr_val->ptr, attr_val->slen + 1); *attr_val = new_val; } else if (len_diff < 0) { attr_val->slen += len_diff; pj_memmove(attr_val->ptr, attr_val->ptr - len_diff, attr_val->slen + 1); } pj_memcpy(attr_val->ptr, new_pt->ptr, new_pt->slen); } /* Internal function to apply symmetric PT for the local answer. */ static void apply_answer_symmetric_pt(pj_pool_t *pool, pjmedia_sdp_media *answer, unsigned pt_cnt, const pj_str_t pt_offer[], const pj_str_t pt_answer[]) { pjmedia_sdp_attr *a_tmp[PJMEDIA_MAX_SDP_ATTR]; unsigned i, a_tmp_cnt = 0; /* Rewrite the payload types in the answer if different to * the ones in the offer. */ for (i = 0; i < pt_cnt; ++i) { pjmedia_sdp_attr *a; /* Skip if the PTs are the same already, e.g: static PT. */ if (pj_strcmp(&pt_answer[i], &pt_offer[i]) == 0) continue; /* Rewrite payload type in the answer to match to the offer */ pj_strdup(pool, &answer->desc.fmt[i], &pt_offer[i]); /* Also update payload type in rtpmap */ a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", &pt_answer[i]); if (a) { rewrite_pt(pool, &a->value, &pt_answer[i], &pt_offer[i]); /* Temporarily remove the attribute in case the new payload * type is being used by another format in the media. */ pjmedia_sdp_media_remove_attr(answer, a); a_tmp[a_tmp_cnt++] = a; } /* Also update payload type in fmtp */ a = pjmedia_sdp_media_find_attr2(answer, "fmtp", &pt_answer[i]); if (a) { rewrite_pt(pool, &a->value, &pt_answer[i], &pt_offer[i]); /* Temporarily remove the attribute in case the new payload * type is being used by another format in the media. */ pjmedia_sdp_media_remove_attr(answer, a); a_tmp[a_tmp_cnt++] = a; } } /* Return back 'rtpmap' and 'fmtp' attributes */ for (i = 0; i < a_tmp_cnt; ++i) pjmedia_sdp_media_add_attr(answer, a_tmp[i]); } /* Try to match offer with answer. */ static pj_status_t match_offer(pj_pool_t *pool, pj_bool_t prefer_remote_codec_order, pj_bool_t answer_with_multiple_codecs, const pjmedia_sdp_media *offer, const pjmedia_sdp_media *preanswer, const pjmedia_sdp_session *preanswer_sdp, pjmedia_sdp_media **p_answer) { unsigned i; pj_bool_t master_has_codec = 0, master_has_other = 0, found_matching_codec = 0, found_matching_telephone_event = 0, found_matching_other = 0; unsigned pt_answer_count = 0; pj_str_t pt_answer[PJMEDIA_MAX_SDP_FMT]; pj_str_t pt_offer[PJMEDIA_MAX_SDP_FMT]; pjmedia_sdp_media *answer; const pjmedia_sdp_media *master, *slave; /* If offer has zero port, just clone the offer */ if (offer->desc.port == 0) { answer = sdp_media_clone_deactivate(pool, offer, preanswer, preanswer_sdp); *p_answer = answer; return PJ_SUCCESS; } /* If the preanswer define zero port, this media is being rejected, * just clone the preanswer. */ if (preanswer->desc.port == 0) { answer = pjmedia_sdp_media_clone(pool, preanswer); *p_answer = answer; return PJ_SUCCESS; } /* Set master/slave negotiator based on prefer_remote_codec_order. */ if (prefer_remote_codec_order) { master = offer; slave = preanswer; } else { master = preanswer; slave = offer; } /* With the addition of telephone-event and dodgy MS RTC SDP, * the answer generation algorithm looks really shitty... */ for (i=0; idesc.fmt_count; ++i) { unsigned j; if (pj_isdigit(*master->desc.fmt[i].ptr)) { /* This is normal/standard payload type, where it's identified * by payload number. */ unsigned pt; pt = pj_strtoul(&master->desc.fmt[i]); if (pt < 96) { /* For static payload type, it's enough to compare just * the payload number. */ master_has_codec = 1; /* We just need to select one codec if not allowing multiple. * Continue if we have selected matching codec for previous * payload. */ if (!answer_with_multiple_codecs && found_matching_codec) continue; /* Find matching codec in local descriptor. */ for (j=0; jdesc.fmt_count; ++j) { unsigned p; p = pj_strtoul(&slave->desc.fmt[j]); if (p == pt && pj_isdigit(*slave->desc.fmt[j].ptr)) { found_matching_codec = 1; pt_offer[pt_answer_count] = slave->desc.fmt[j]; pt_answer[pt_answer_count++] = slave->desc.fmt[j]; break; } } } else { /* This is dynamic payload type. * For dynamic payload type, we must look the rtpmap and * compare the encoding name. */ const pjmedia_sdp_attr *a; pjmedia_sdp_rtpmap or_; pj_bool_t is_codec; /* Get the rtpmap for the payload type in the master. */ a = pjmedia_sdp_media_find_attr2(master, "rtpmap", &master->desc.fmt[i]); if (!a) { pj_assert(!"Bug! Offer should have been validated"); return PJMEDIA_SDP_EMISSINGRTPMAP; } pjmedia_sdp_attr_get_rtpmap(a, &or_); if (!pj_stricmp2(&or_.enc_name, "telephone-event")) { if (found_matching_telephone_event) continue; is_codec = 0; } else { master_has_codec = 1; if (!answer_with_multiple_codecs && found_matching_codec) continue; is_codec = 1; } /* Find paylaod in our initial SDP with matching * encoding name and clock rate. */ for (j=0; jdesc.fmt_count; ++j) { a = pjmedia_sdp_media_find_attr2(slave, "rtpmap", &slave->desc.fmt[j]); if (a) { pjmedia_sdp_rtpmap lr; pjmedia_sdp_attr_get_rtpmap(a, &lr); /* See if encoding name, clock rate, and * channel count match */ if (!pj_stricmp(&or_.enc_name, &lr.enc_name) && or_.clock_rate == lr.clock_rate && (pj_stricmp(&or_.param, &lr.param)==0 || (lr.param.slen==0 && or_.param.slen==1 && *or_.param.ptr=='1') || (or_.param.slen==0 && lr.param.slen==1 && *lr.param.ptr=='1'))) { /* Match! */ if (is_codec) { pjmedia_sdp_media *o_med, *a_med; unsigned o_fmt_idx, a_fmt_idx; o_med = (pjmedia_sdp_media*)offer; a_med = (pjmedia_sdp_media*)preanswer; o_fmt_idx = prefer_remote_codec_order? i:j; a_fmt_idx = prefer_remote_codec_order? j:i; /* Call custom format matching callbacks */ if (custom_fmt_match(pool, &or_.enc_name, o_med, o_fmt_idx, a_med, a_fmt_idx, ALLOW_MODIFY_ANSWER) != PJ_SUCCESS) { continue; } found_matching_codec = 1; } else { found_matching_telephone_event = 1; } pt_offer[pt_answer_count] = prefer_remote_codec_order? offer->desc.fmt[i]: offer->desc.fmt[j]; pt_answer[pt_answer_count++] = prefer_remote_codec_order? preanswer->desc.fmt[j]: preanswer->desc.fmt[i]; break; } } } } } else { /* This is a non-standard, brain damaged SDP where the payload * type is non-numeric. It exists e.g. in Microsoft RTC based * UA, to indicate instant messaging capability. * Example: * - m=x-ms-message 5060 sip null */ master_has_other = 1; if (found_matching_other) continue; for (j=0; jdesc.fmt_count; ++j) { if (!pj_strcmp(&master->desc.fmt[i], &slave->desc.fmt[j])) { /* Match */ found_matching_other = 1; pt_offer[pt_answer_count] = prefer_remote_codec_order? offer->desc.fmt[i]: offer->desc.fmt[j]; pt_answer[pt_answer_count++] = prefer_remote_codec_order? preanswer->desc.fmt[j]: preanswer->desc.fmt[i]; break; } } } } /* See if all types of master can be matched. */ if (master_has_codec && !found_matching_codec) { return PJMEDIA_SDPNEG_NOANSCODEC; } /* If this comment is removed, negotiation will fail if remote has offered telephone-event and local is not configured with telephone-event if (offer_has_telephone_event && !found_matching_telephone_event) { return PJMEDIA_SDPNEG_NOANSTELEVENT; } */ if (master_has_other && !found_matching_other) { return PJMEDIA_SDPNEG_NOANSUNKNOWN; } /* Seems like everything is in order. * Build the answer by cloning from preanswer, but rearrange the payload * to suit the offer. */ answer = pjmedia_sdp_media_clone(pool, preanswer); for (i=0; idesc.fmt_count; ++j) { if (!pj_strcmp(&answer->desc.fmt[j], &pt_answer[i])) break; } pj_assert(j != answer->desc.fmt_count); str_swap(&answer->desc.fmt[i], &answer->desc.fmt[j]); } /* Remove unwanted local formats. */ for (i=pt_answer_count; idesc.fmt_count; ++i) { pjmedia_sdp_attr *a; /* Remove rtpmap for this format */ a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", &answer->desc.fmt[i]); if (a) { pjmedia_sdp_media_remove_attr(answer, a); } /* Remove fmtp for this format */ a = pjmedia_sdp_media_find_attr2(answer, "fmtp", &answer->desc.fmt[i]); if (a) { pjmedia_sdp_media_remove_attr(answer, a); } } answer->desc.fmt_count = pt_answer_count; #if PJMEDIA_SDP_NEG_ANSWER_SYMMETRIC_PT apply_answer_symmetric_pt(pool, answer, pt_answer_count, pt_offer, pt_answer); #endif /* Update media direction. */ update_media_direction(pool, offer, answer); *p_answer = answer; return PJ_SUCCESS; } /* Create complete answer for remote's offer. */ static pj_status_t create_answer( pj_pool_t *pool, pj_bool_t prefer_remote_codec_order, pj_bool_t answer_with_multiple_codecs, const pjmedia_sdp_session *initial, const pjmedia_sdp_session *offer, pjmedia_sdp_session **p_answer) { pj_status_t status = PJMEDIA_SDPNEG_ENOMEDIA; pj_bool_t has_active = PJ_FALSE; pjmedia_sdp_session *answer; char media_used[PJMEDIA_MAX_SDP_MEDIA]; unsigned i; /* Validate remote offer. * This should have been validated before. */ PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(offer))==PJ_SUCCESS, status); /* Create initial answer by duplicating initial SDP, * but clear all media lines. The media lines will be filled up later. */ answer = pjmedia_sdp_session_clone(pool, initial); PJ_ASSERT_RETURN(answer != NULL, PJ_ENOMEM); answer->media_count = 0; pj_bzero(media_used, sizeof(media_used)); /* For each media line, create our answer based on our initial * capability. */ for (i=0; imedia_count; ++i) { const pjmedia_sdp_media *om; /* offer */ const pjmedia_sdp_media *im; /* initial media */ pjmedia_sdp_media *am = NULL; /* answer/result */ unsigned j; om = offer->media[i]; /* Find media description in our initial capability that matches * the media type and transport type of offer's media, has * matching codec, and has not been used to answer other offer. */ for (im=NULL, j=0; jmedia_count; ++j) { im = initial->media[j]; if (pj_strcmp(&om->desc.media, &im->desc.media)==0 && pj_strcmp(&om->desc.transport, &im->desc.transport)==0 && media_used[j] == 0) { pj_status_t status2; /* See if it has matching codec. */ status2 = match_offer(pool, prefer_remote_codec_order, answer_with_multiple_codecs, om, im, initial, &am); if (status2 == PJ_SUCCESS) { /* Mark media as used. */ media_used[j] = 1; break; } else { status = status2; } } } if (j==initial->media_count) { /* No matching media. * Reject the offer by setting the port to zero in the answer. */ /* For simplicity in the construction of the answer, we'll * just clone the media from the offer. Anyway receiver will * ignore anything in the media once it sees that the port * number is zero. */ am = sdp_media_clone_deactivate(pool, om, om, answer); } else { /* The answer is in am */ pj_assert(am != NULL); } /* Add the media answer */ answer->media[answer->media_count++] = am; /* Check if this media is active.*/ if (am->desc.port != 0) has_active = PJ_TRUE; } *p_answer = answer; return has_active ? PJ_SUCCESS : status; } /* Cancel offer */ PJ_DEF(pj_status_t) pjmedia_sdp_neg_cancel_offer(pjmedia_sdp_neg *neg) { PJ_ASSERT_RETURN(neg, PJ_EINVAL); /* Must be in LOCAL_OFFER state. */ PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER || neg->state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER, PJMEDIA_SDPNEG_EINSTATE); if (neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER && neg->active_local_sdp) { /* Increment next version number. This happens if for example * the reinvite offer is rejected by 488. If we don't increment * the version here, the next offer will have the same version. */ neg->active_local_sdp->origin.version++; } /* Revert back initial SDP */ if (neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) neg->initial_sdp = neg->initial_sdp_tmp; /* Clear temporary SDP */ neg->initial_sdp_tmp = NULL; neg->neg_local_sdp = neg->neg_remote_sdp = NULL; neg->has_remote_answer = PJ_FALSE; /* Reset state to done */ neg->state = PJMEDIA_SDP_NEG_STATE_DONE; return PJ_SUCCESS; } /* The best bit: SDP negotiation function! */ PJ_DEF(pj_status_t) pjmedia_sdp_neg_negotiate( pj_pool_t *pool, pjmedia_sdp_neg *neg, pj_bool_t allow_asym) { pj_status_t status; /* Check arguments are valid. */ PJ_ASSERT_RETURN(pool && neg, PJ_EINVAL); /* Must be in STATE_WAIT_NEGO state. */ PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, PJMEDIA_SDPNEG_EINSTATE); /* Must have remote offer. */ PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJ_EBUG); if (neg->has_remote_answer) { pjmedia_sdp_session *active; status = process_answer(pool, neg->neg_local_sdp, neg->neg_remote_sdp, allow_asym, &active); if (status == PJ_SUCCESS) { /* Only update active SDPs when negotiation is successfull */ neg->active_local_sdp = active; neg->active_remote_sdp = neg->neg_remote_sdp; } } else { pjmedia_sdp_session *answer = NULL; status = create_answer(pool, neg->prefer_remote_codec_order, neg->answer_with_multiple_codecs, neg->neg_local_sdp, neg->neg_remote_sdp, &answer); if (status == PJ_SUCCESS) { pj_uint32_t active_ver; if (neg->active_local_sdp) active_ver = neg->active_local_sdp->origin.version; else active_ver = neg->initial_sdp->origin.version; /* Only update active SDPs when negotiation is successfull */ neg->active_local_sdp = answer; neg->active_remote_sdp = neg->neg_remote_sdp; /* Increment SDP version */ neg->active_local_sdp->origin.version = ++active_ver; } } /* State is DONE regardless */ neg->state = PJMEDIA_SDP_NEG_STATE_DONE; /* Save state */ neg->answer_was_remote = neg->has_remote_answer; /* Revert back initial SDP if nego fails */ if (status != PJ_SUCCESS) neg->initial_sdp = neg->initial_sdp_tmp; /* Clear temporary SDP */ neg->initial_sdp_tmp = NULL; neg->neg_local_sdp = neg->neg_remote_sdp = NULL; neg->has_remote_answer = PJ_FALSE; return status; } static pj_status_t custom_fmt_match(pj_pool_t *pool, const pj_str_t *fmt_name, pjmedia_sdp_media *offer, unsigned o_fmt_idx, pjmedia_sdp_media *answer, unsigned a_fmt_idx, unsigned option) { unsigned i; for (i = 0; i < fmt_match_cb_cnt; ++i) { if (pj_stricmp(fmt_name, &fmt_match_cb[i].fmt_name) == 0) { pj_assert(fmt_match_cb[i].cb); return (*fmt_match_cb[i].cb)(pool, offer, o_fmt_idx, answer, a_fmt_idx, option); } } /* Not customized format matching found, should be matched */ return PJ_SUCCESS; } /* Register customized SDP format negotiation callback function. */ PJ_DEF(pj_status_t) pjmedia_sdp_neg_register_fmt_match_cb( const pj_str_t *fmt_name, pjmedia_sdp_neg_fmt_match_cb cb) { struct fmt_match_cb_t *f = NULL; unsigned i; PJ_ASSERT_RETURN(fmt_name, PJ_EINVAL); /* Check if the callback for the format name has been registered */ for (i = 0; i < fmt_match_cb_cnt; ++i) { if (pj_stricmp(fmt_name, &fmt_match_cb[i].fmt_name) == 0) break; } /* Unregistration */ if (cb == NULL) { if (i == fmt_match_cb_cnt) return PJ_ENOTFOUND; pj_array_erase(fmt_match_cb, sizeof(fmt_match_cb[0]), fmt_match_cb_cnt, i); fmt_match_cb_cnt--; return PJ_SUCCESS; } /* Registration */ if (i < fmt_match_cb_cnt) { /* The same format name has been registered before */ if (cb != fmt_match_cb[i].cb) return PJ_EEXISTS; else return PJ_SUCCESS; } if (fmt_match_cb_cnt >= PJ_ARRAY_SIZE(fmt_match_cb)) return PJ_ETOOMANY; f = &fmt_match_cb[fmt_match_cb_cnt++]; f->fmt_name = *fmt_name; f->cb = cb; return PJ_SUCCESS; } /* Match format in the SDP media offer and answer. */ PJ_DEF(pj_status_t) pjmedia_sdp_neg_fmt_match(pj_pool_t *pool, pjmedia_sdp_media *offer, unsigned o_fmt_idx, pjmedia_sdp_media *answer, unsigned a_fmt_idx, unsigned option) { const pjmedia_sdp_attr *attr; pjmedia_sdp_rtpmap o_rtpmap, a_rtpmap; unsigned o_pt; unsigned a_pt; o_pt = pj_strtoul(&offer->desc.fmt[o_fmt_idx]); a_pt = pj_strtoul(&answer->desc.fmt[a_fmt_idx]); if (o_pt < 96 || a_pt < 96) { if (o_pt == a_pt) return PJ_SUCCESS; else return PJMEDIA_SDP_EFORMATNOTEQUAL; } /* Get the format rtpmap from the offer. */ attr = pjmedia_sdp_media_find_attr2(offer, "rtpmap", &offer->desc.fmt[o_fmt_idx]); if (!attr) { pj_assert(!"Bug! Offer haven't been validated"); return PJ_EBUG; } pjmedia_sdp_attr_get_rtpmap(attr, &o_rtpmap); /* Get the format rtpmap from the answer. */ attr = pjmedia_sdp_media_find_attr2(answer, "rtpmap", &answer->desc.fmt[a_fmt_idx]); if (!attr) { pj_assert(!"Bug! Answer haven't been validated"); return PJ_EBUG; } pjmedia_sdp_attr_get_rtpmap(attr, &a_rtpmap); if (pj_stricmp(&o_rtpmap.enc_name, &a_rtpmap.enc_name) != 0 || (o_rtpmap.clock_rate != a_rtpmap.clock_rate) || (!(pj_stricmp(&o_rtpmap.param, &a_rtpmap.param) == 0 || (a_rtpmap.param.slen == 0 && o_rtpmap.param.slen == 1 && *o_rtpmap.param.ptr == '1') || (o_rtpmap.param.slen == 0 && a_rtpmap.param.slen == 1 && *a_rtpmap.param.ptr=='1')))) { return PJMEDIA_SDP_EFORMATNOTEQUAL; } return custom_fmt_match(pool, &o_rtpmap.enc_name, offer, o_fmt_idx, answer, a_fmt_idx, option); } diff --git a/deps/pjsip/pjmedia/src/pjmedia/sound_legacy.c b/deps/pjsip/pjmedia/src/pjmedia/sound_legacy.c index 6d44e385..38ff3cdb 100644 --- a/deps/pjsip/pjmedia/src/pjmedia/sound_legacy.c +++ b/deps/pjsip/pjmedia/src/pjmedia/sound_legacy.c @@ -1,284 +1,284 @@ -/* $Id: sound_legacy.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* $Id: sound_legacy.c 5140 2015-07-31 07:12:36Z nanang $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * This is implementation of legacy sound device API, for applications * that still use the old/deprecated sound device API. This implementation * uses the new Audio Device API. * * Please see http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more * information. */ #include #include #include #if PJMEDIA_HAS_LEGACY_SOUND_API static struct legacy_subsys { pjmedia_snd_dev_info info[4]; unsigned info_counter; unsigned user_rec_latency; unsigned user_play_latency; } g_sys; struct pjmedia_snd_stream { pj_pool_t *pool; pjmedia_aud_stream *aud_strm; pjmedia_snd_rec_cb user_rec_cb; pjmedia_snd_play_cb user_play_cb; void *user_user_data; }; PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory) { return pjmedia_aud_subsys_init(factory); } PJ_DEF(pj_status_t) pjmedia_snd_deinit(void) { return pjmedia_aud_subsys_shutdown(); } PJ_DEF(int) pjmedia_snd_get_dev_count(void) { return pjmedia_aud_dev_count(); } PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index) { pjmedia_snd_dev_info *oi = &g_sys.info[g_sys.info_counter]; pjmedia_aud_dev_info di; g_sys.info_counter = (g_sys.info_counter+1) % PJ_ARRAY_SIZE(g_sys.info); if (pjmedia_aud_dev_get_info(index, &di) != PJ_SUCCESS) return NULL; pj_bzero(oi, sizeof(*oi)); pj_ansi_strncpy(oi->name, di.name, sizeof(oi->name)); oi->name[sizeof(oi->name)-1] = '\0'; oi->input_count = di.input_count; oi->output_count = di.output_count; oi->default_samples_per_sec = di.default_samples_per_sec; return oi; } static pj_status_t snd_play_cb(void *user_data, pjmedia_frame *frame) { pjmedia_snd_stream *strm = (pjmedia_snd_stream*)user_data; frame->type = PJMEDIA_FRAME_TYPE_AUDIO; return strm->user_play_cb(strm->user_user_data, frame->timestamp.u32.lo, frame->buf, (unsigned)frame->size); } static pj_status_t snd_rec_cb(void *user_data, pjmedia_frame *frame) { pjmedia_snd_stream *strm = (pjmedia_snd_stream*)user_data; return strm->user_rec_cb(strm->user_user_data, frame->timestamp.u32.lo, frame->buf, (unsigned)frame->size); } static pj_status_t open_stream( pjmedia_dir dir, int rec_id, int play_id, unsigned clock_rate, unsigned channel_count, unsigned samples_per_frame, unsigned bits_per_sample, pjmedia_snd_rec_cb rec_cb, pjmedia_snd_play_cb play_cb, void *user_data, pjmedia_snd_stream **p_snd_strm) { pj_pool_t *pool; pjmedia_snd_stream *snd_strm; pjmedia_aud_param param; pj_status_t status; /* Initialize parameters */ if (dir & PJMEDIA_DIR_CAPTURE) { status = pjmedia_aud_dev_default_param(rec_id, ¶m); } else { status = pjmedia_aud_dev_default_param(play_id, ¶m); } if (status != PJ_SUCCESS) return status; param.dir = dir; param.rec_id = rec_id; param.play_id = play_id; param.clock_rate = clock_rate; param.channel_count = channel_count; param.samples_per_frame = samples_per_frame; param.bits_per_sample = bits_per_sample; /* Latencies setting */ if ((dir & PJMEDIA_DIR_CAPTURE) && g_sys.user_rec_latency) { param.flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY; param.input_latency_ms = g_sys.user_rec_latency; } if ((dir & PJMEDIA_DIR_PLAYBACK) && g_sys.user_play_latency) { param.flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; param.output_latency_ms = g_sys.user_play_latency; } /* Create sound wrapper */ pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(), "legacy-snd", 512, 512, NULL); snd_strm = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_stream); snd_strm->pool = pool; snd_strm->user_rec_cb = rec_cb; snd_strm->user_play_cb = play_cb; snd_strm->user_user_data = user_data; /* Create the stream */ status = pjmedia_aud_stream_create(¶m, &snd_rec_cb, &snd_play_cb, snd_strm, &snd_strm->aud_strm); if (status != PJ_SUCCESS) { pj_pool_release(pool); return status; } *p_snd_strm = snd_strm; return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index, unsigned clock_rate, unsigned channel_count, unsigned samples_per_frame, unsigned bits_per_sample, pjmedia_snd_rec_cb rec_cb, void *user_data, pjmedia_snd_stream **p_snd_strm) { return open_stream(PJMEDIA_DIR_CAPTURE, index, PJMEDIA_AUD_INVALID_DEV, clock_rate, channel_count, samples_per_frame, bits_per_sample, rec_cb, NULL, user_data, p_snd_strm); } PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index, unsigned clock_rate, unsigned channel_count, unsigned samples_per_frame, unsigned bits_per_sample, pjmedia_snd_play_cb play_cb, void *user_data, pjmedia_snd_stream **p_snd_strm ) { return open_stream(PJMEDIA_DIR_PLAYBACK, PJMEDIA_AUD_INVALID_DEV, index, clock_rate, channel_count, samples_per_frame, bits_per_sample, NULL, play_cb, user_data, p_snd_strm); } PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id, int play_id, unsigned clock_rate, unsigned channel_count, unsigned samples_per_frame, unsigned bits_per_sample, pjmedia_snd_rec_cb rec_cb, pjmedia_snd_play_cb play_cb, void *user_data, pjmedia_snd_stream **p_snd_strm) { return open_stream(PJMEDIA_DIR_CAPTURE_PLAYBACK, rec_id, play_id, clock_rate, channel_count, samples_per_frame, bits_per_sample, rec_cb, play_cb, user_data, p_snd_strm); } PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream) { return pjmedia_aud_stream_start(stream->aud_strm); } PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream) { return pjmedia_aud_stream_stop(stream->aud_strm); } PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm, pjmedia_snd_stream_info *pi) { pjmedia_aud_param param; pj_status_t status; status = pjmedia_aud_stream_get_param(strm->aud_strm, ¶m); if (status != PJ_SUCCESS) return status; pj_bzero(pi, sizeof(*pi)); pi->dir = param.dir; pi->play_id = param.play_id; pi->rec_id = param.rec_id; pi->clock_rate = param.clock_rate; pi->channel_count = param.channel_count; pi->samples_per_frame = param.samples_per_frame; pi->bits_per_sample = param.bits_per_sample; if (param.flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) { pi->rec_latency = param.input_latency_ms; } if (param.flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) { pi->play_latency = param.output_latency_ms; } return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream) { pj_status_t status; status = pjmedia_aud_stream_destroy(stream->aud_strm); if (status != PJ_SUCCESS) return status; pj_pool_release(stream->pool); return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency, unsigned output_latency) { g_sys.user_rec_latency = input_latency; g_sys.user_play_latency = output_latency; return PJ_SUCCESS; } #endif /* PJMEDIA_HAS_LEGACY_SOUND_API */ diff --git a/deps/pjsip/pjmedia/src/pjmedia/sound_port.c b/deps/pjsip/pjmedia/src/pjmedia/sound_port.c index 8d8f970c..0ca59b0d 100644 --- a/deps/pjsip/pjmedia/src/pjmedia/sound_port.c +++ b/deps/pjsip/pjmedia/src/pjmedia/sound_port.c @@ -1,790 +1,790 @@ -/* $Id: sound_port.c 4487 2013-04-23 05:37:41Z bennylp $ */ +/* $Id: sound_port.c 5140 2015-07-31 07:12:36Z nanang $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include /* pj_memset() */ #define AEC_TAIL 128 /* default AEC length in ms */ #define AEC_SUSPEND_LIMIT 5 /* seconds of no activity */ #define THIS_FILE "sound_port.c" //#define TEST_OVERFLOW_UNDERFLOW struct pjmedia_snd_port { int rec_id; int play_id; pj_uint32_t aud_caps; pjmedia_aud_param aud_param; pjmedia_aud_stream *aud_stream; pjmedia_dir dir; pjmedia_port *port; pjmedia_clock_src cap_clocksrc, play_clocksrc; unsigned clock_rate; unsigned channel_count; unsigned samples_per_frame; unsigned bits_per_sample; unsigned options; unsigned prm_ec_options; /* software ec */ pjmedia_echo_state *ec_state; unsigned ec_options; unsigned ec_tail_len; pj_bool_t ec_suspended; unsigned ec_suspend_count; unsigned ec_suspend_limit; /* audio frame preview callbacks */ void *user_data; pjmedia_aud_play_cb on_play_frame; pjmedia_aud_rec_cb on_rec_frame; }; /* * The callback called by sound player when it needs more samples to be * played. */ static pj_status_t play_cb(void *user_data, pjmedia_frame *frame) { pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data; pjmedia_port *port; const unsigned required_size = (unsigned)frame->size; pj_status_t status; pjmedia_clock_src_update(&snd_port->play_clocksrc, &frame->timestamp); port = snd_port->port; if (port == NULL) goto no_frame; status = pjmedia_port_get_frame(port, frame); if (status != PJ_SUCCESS) goto no_frame; if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO) goto no_frame; /* Must supply the required samples */ pj_assert(frame->size == required_size); if (snd_port->ec_state) { if (snd_port->ec_suspended) { snd_port->ec_suspended = PJ_FALSE; pjmedia_echo_reset(snd_port->ec_state); PJ_LOG(4,(THIS_FILE, "EC activated")); } snd_port->ec_suspend_count = 0; pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)frame->buf); } /* Invoke preview callback */ if (snd_port->on_play_frame) (*snd_port->on_play_frame)(snd_port->user_data, frame); return PJ_SUCCESS; no_frame: frame->type = PJMEDIA_FRAME_TYPE_AUDIO; frame->size = required_size; pj_bzero(frame->buf, frame->size); if (snd_port->ec_state && !snd_port->ec_suspended) { ++snd_port->ec_suspend_count; if (snd_port->ec_suspend_count > snd_port->ec_suspend_limit) { snd_port->ec_suspended = PJ_TRUE; PJ_LOG(4,(THIS_FILE, "EC suspended because of inactivity")); } if (snd_port->ec_state) { /* To maintain correct delay in EC */ pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)frame->buf); } } /* Invoke preview callback */ if (snd_port->on_play_frame) (*snd_port->on_play_frame)(snd_port->user_data, frame); return PJ_SUCCESS; } /* * The callback called by sound recorder when it has finished capturing a * frame. */ static pj_status_t rec_cb(void *user_data, pjmedia_frame *frame) { pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data; pjmedia_port *port; pjmedia_clock_src_update(&snd_port->cap_clocksrc, &frame->timestamp); /* Invoke preview callback */ if (snd_port->on_rec_frame) (*snd_port->on_rec_frame)(snd_port->user_data, frame); port = snd_port->port; if (port == NULL) return PJ_SUCCESS; /* Cancel echo */ if (snd_port->ec_state && !snd_port->ec_suspended) { pjmedia_echo_capture(snd_port->ec_state, (pj_int16_t*) frame->buf, 0); } pjmedia_port_put_frame(port, frame); return PJ_SUCCESS; } /* * The callback called by sound player when it needs more samples to be * played. This version is for non-PCM data. */ static pj_status_t play_cb_ext(void *user_data, pjmedia_frame *frame) { pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data; pjmedia_port *port = snd_port->port; if (port == NULL) { frame->type = PJMEDIA_FRAME_TYPE_NONE; return PJ_SUCCESS; } pjmedia_port_get_frame(port, frame); /* Invoke preview callback */ if (snd_port->on_play_frame) (*snd_port->on_play_frame)(snd_port->user_data, frame); return PJ_SUCCESS; } /* * The callback called by sound recorder when it has finished capturing a * frame. This version is for non-PCM data. */ static pj_status_t rec_cb_ext(void *user_data, pjmedia_frame *frame) { pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data; pjmedia_port *port; /* Invoke preview callback */ if (snd_port->on_rec_frame) (*snd_port->on_rec_frame)(snd_port->user_data, frame); port = snd_port->port; if (port == NULL) return PJ_SUCCESS; pjmedia_port_put_frame(port, frame); return PJ_SUCCESS; } /* Initialize with default values (zero) */ PJ_DEF(void) pjmedia_snd_port_param_default(pjmedia_snd_port_param *prm) { pj_bzero(prm, sizeof(*prm)); } /* * Start the sound stream. * This may be called even when the sound stream has already been started. */ static pj_status_t start_sound_device( pj_pool_t *pool, pjmedia_snd_port *snd_port ) { pjmedia_aud_rec_cb snd_rec_cb; pjmedia_aud_play_cb snd_play_cb; pjmedia_aud_param param_copy; pj_status_t status; /* Check if sound has been started. */ if (snd_port->aud_stream != NULL) return PJ_SUCCESS; PJ_ASSERT_RETURN(snd_port->dir == PJMEDIA_DIR_CAPTURE || snd_port->dir == PJMEDIA_DIR_PLAYBACK || snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK, PJ_EBUG); /* Get device caps */ if (snd_port->aud_param.dir & PJMEDIA_DIR_CAPTURE) { pjmedia_aud_dev_info dev_info; status = pjmedia_aud_dev_get_info(snd_port->aud_param.rec_id, &dev_info); if (status != PJ_SUCCESS) return status; snd_port->aud_caps = dev_info.caps; } else { snd_port->aud_caps = 0; } /* Process EC settings */ pj_memcpy(¶m_copy, &snd_port->aud_param, sizeof(param_copy)); if (param_copy.flags & PJMEDIA_AUD_DEV_CAP_EC) { /* EC is wanted */ if ((snd_port->prm_ec_options & PJMEDIA_ECHO_USE_SW_ECHO) == 0 && (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC)) { /* Device supports EC */ /* Nothing to do */ } else { /* Application wants to use software EC or device * doesn't support EC, remove EC settings from * device parameters */ param_copy.flags &= ~(PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL); } } /* Use different callback if format is not PCM */ if (snd_port->aud_param.ext_fmt.id == PJMEDIA_FORMAT_L16) { snd_rec_cb = &rec_cb; snd_play_cb = &play_cb; } else { snd_rec_cb = &rec_cb_ext; snd_play_cb = &play_cb_ext; } /* Open the device */ status = pjmedia_aud_stream_create(¶m_copy, snd_rec_cb, snd_play_cb, snd_port, &snd_port->aud_stream); if (status != PJ_SUCCESS) return status; /* Inactivity limit before EC is suspended. */ snd_port->ec_suspend_limit = AEC_SUSPEND_LIMIT * (snd_port->clock_rate / snd_port->samples_per_frame); /* Create software EC if parameter specifies EC and * (app specifically requests software EC or device * doesn't support EC). Only do this if the format is PCM! */ if ((snd_port->aud_param.flags & PJMEDIA_AUD_DEV_CAP_EC) && ((snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC)==0 || (snd_port->prm_ec_options & PJMEDIA_ECHO_USE_SW_ECHO) != 0) && param_copy.ext_fmt.id == PJMEDIA_FORMAT_PCM) { if ((snd_port->aud_param.flags & PJMEDIA_AUD_DEV_CAP_EC_TAIL)==0) { snd_port->aud_param.flags |= PJMEDIA_AUD_DEV_CAP_EC_TAIL; snd_port->aud_param.ec_tail_ms = AEC_TAIL; PJ_LOG(4,(THIS_FILE, "AEC tail is set to default %u ms", snd_port->aud_param.ec_tail_ms)); } pjmedia_snd_port_set_ec(snd_port, pool, snd_port->aud_param.ec_tail_ms, snd_port->prm_ec_options); } /* Start sound stream. */ if (!(snd_port->options & PJMEDIA_SND_PORT_NO_AUTO_START)) { status = pjmedia_aud_stream_start(snd_port->aud_stream); } if (status != PJ_SUCCESS) { pjmedia_aud_stream_destroy(snd_port->aud_stream); snd_port->aud_stream = NULL; return status; } return PJ_SUCCESS; } /* * Stop the sound device. * This may be called even when there's no sound device in the port. */ static pj_status_t stop_sound_device( pjmedia_snd_port *snd_port ) { /* Check if we have sound stream device. */ if (snd_port->aud_stream) { pjmedia_aud_stream_stop(snd_port->aud_stream); pjmedia_aud_stream_destroy(snd_port->aud_stream); snd_port->aud_stream = NULL; } /* Destroy AEC */ if (snd_port->ec_state) { pjmedia_echo_destroy(snd_port->ec_state); snd_port->ec_state = NULL; } return PJ_SUCCESS; } /* * Create bidirectional port. */ PJ_DEF(pj_status_t) pjmedia_snd_port_create( pj_pool_t *pool, int rec_id, int play_id, unsigned clock_rate, unsigned channel_count, unsigned samples_per_frame, unsigned bits_per_sample, unsigned options, pjmedia_snd_port **p_port) { pjmedia_snd_port_param param; pj_status_t status; pjmedia_snd_port_param_default(¶m); /* Normalize rec_id & play_id */ if (rec_id < 0) rec_id = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV; if (play_id < 0) play_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV; status = pjmedia_aud_dev_default_param(rec_id, ¶m.base); if (status != PJ_SUCCESS) return status; param.base.dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; param.base.rec_id = rec_id; param.base.play_id = play_id; param.base.clock_rate = clock_rate; param.base.channel_count = channel_count; param.base.samples_per_frame = samples_per_frame; param.base.bits_per_sample = bits_per_sample; param.options = options; param.ec_options = 0; return pjmedia_snd_port_create2(pool, ¶m, p_port); } /* * Create sound recorder AEC. */ PJ_DEF(pj_status_t) pjmedia_snd_port_create_rec( pj_pool_t *pool, int dev_id, unsigned clock_rate, unsigned channel_count, unsigned samples_per_frame, unsigned bits_per_sample, unsigned options, pjmedia_snd_port **p_port) { pjmedia_snd_port_param param; pj_status_t status; pjmedia_snd_port_param_default(¶m); /* Normalize dev_id */ if (dev_id < 0) dev_id = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV; status = pjmedia_aud_dev_default_param(dev_id, ¶m.base); if (status != PJ_SUCCESS) return status; param.base.dir = PJMEDIA_DIR_CAPTURE; param.base.rec_id = dev_id; param.base.clock_rate = clock_rate; param.base.channel_count = channel_count; param.base.samples_per_frame = samples_per_frame; param.base.bits_per_sample = bits_per_sample; param.options = options; param.ec_options = 0; return pjmedia_snd_port_create2(pool, ¶m, p_port); } /* * Create sound player port. */ PJ_DEF(pj_status_t) pjmedia_snd_port_create_player( pj_pool_t *pool, int dev_id, unsigned clock_rate, unsigned channel_count, unsigned samples_per_frame, unsigned bits_per_sample, unsigned options, pjmedia_snd_port **p_port) { pjmedia_snd_port_param param; pj_status_t status; pjmedia_snd_port_param_default(¶m); /* Normalize dev_id */ if (dev_id < 0) dev_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV; status = pjmedia_aud_dev_default_param(dev_id, ¶m.base); if (status != PJ_SUCCESS) return status; param.base.dir = PJMEDIA_DIR_PLAYBACK; param.base.play_id = dev_id; param.base.clock_rate = clock_rate; param.base.channel_count = channel_count; param.base.samples_per_frame = samples_per_frame; param.base.bits_per_sample = bits_per_sample; param.options = options; param.ec_options = 0; return pjmedia_snd_port_create2(pool, ¶m, p_port); } /* * Create sound port. */ PJ_DEF(pj_status_t) pjmedia_snd_port_create2(pj_pool_t *pool, const pjmedia_snd_port_param *prm, pjmedia_snd_port **p_port) { pjmedia_snd_port *snd_port; pj_status_t status; unsigned ptime_usec; PJ_ASSERT_RETURN(pool && prm && p_port, PJ_EINVAL); snd_port = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_port); PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM); snd_port->dir = prm->base.dir; snd_port->rec_id = prm->base.rec_id; snd_port->play_id = prm->base.play_id; snd_port->clock_rate = prm->base.clock_rate; snd_port->channel_count = prm->base.channel_count; snd_port->samples_per_frame = prm->base.samples_per_frame; snd_port->bits_per_sample = prm->base.bits_per_sample; pj_memcpy(&snd_port->aud_param, &prm->base, sizeof(snd_port->aud_param)); snd_port->options = prm->options; snd_port->prm_ec_options = prm->ec_options; snd_port->user_data = prm->user_data; snd_port->on_play_frame = prm->on_play_frame; snd_port->on_rec_frame = prm->on_rec_frame; ptime_usec = prm->base.samples_per_frame * 1000 / prm->base.channel_count / prm->base.clock_rate * 1000; pjmedia_clock_src_init(&snd_port->cap_clocksrc, PJMEDIA_TYPE_AUDIO, snd_port->clock_rate, ptime_usec); pjmedia_clock_src_init(&snd_port->play_clocksrc, PJMEDIA_TYPE_AUDIO, snd_port->clock_rate, ptime_usec); /* Start sound device immediately. * If there's no port connected, the sound callback will return * empty signal. */ status = start_sound_device( pool, snd_port ); if (status != PJ_SUCCESS) { pjmedia_snd_port_destroy(snd_port); return status; } *p_port = snd_port; return PJ_SUCCESS; } /* * Destroy port (also destroys the sound device). */ PJ_DEF(pj_status_t) pjmedia_snd_port_destroy(pjmedia_snd_port *snd_port) { PJ_ASSERT_RETURN(snd_port, PJ_EINVAL); return stop_sound_device(snd_port); } /* * Retrieve the sound stream associated by this sound device port. */ PJ_DEF(pjmedia_aud_stream*) pjmedia_snd_port_get_snd_stream( pjmedia_snd_port *snd_port) { PJ_ASSERT_RETURN(snd_port, NULL); return snd_port->aud_stream; } /* Reset EC state */ PJ_DEF(pj_status_t) pjmedia_snd_port_reset_ec_state( pjmedia_snd_port *snd_port ) { PJ_ASSERT_RETURN(snd_port, PJ_EINVAL); if (snd_port->ec_state) { pjmedia_echo_reset(snd_port->ec_state); PJ_LOG(4,(THIS_FILE, "EC reset")); } return PJ_SUCCESS; } /* * Change EC settings. */ PJ_DEF(pj_status_t) pjmedia_snd_port_set_ec( pjmedia_snd_port *snd_port, pj_pool_t *pool, unsigned tail_ms, unsigned options) { pjmedia_aud_param prm; pj_status_t status; /* Sound must be opened in full-duplex mode */ PJ_ASSERT_RETURN(snd_port && snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK, PJ_EINVALIDOP); /* Determine whether we use device or software EC */ if ((snd_port->prm_ec_options & PJMEDIA_ECHO_USE_SW_ECHO) == 0 && (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC)) { /* We use device EC */ pj_bool_t ec_enabled; /* Query EC status */ status = pjmedia_aud_stream_get_cap(snd_port->aud_stream, PJMEDIA_AUD_DEV_CAP_EC, &ec_enabled); if (status != PJ_SUCCESS) return status; if (tail_ms != 0) { /* Change EC setting */ if (!ec_enabled) { /* Enable EC first */ pj_bool_t value = PJ_TRUE; status = pjmedia_aud_stream_set_cap(snd_port->aud_stream, PJMEDIA_AUD_DEV_CAP_EC, &value); if (status != PJ_SUCCESS) return status; } if ((snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC_TAIL)==0) { /* Device does not support setting EC tail */ return PJMEDIA_EAUD_INVCAP; } return pjmedia_aud_stream_set_cap(snd_port->aud_stream, PJMEDIA_AUD_DEV_CAP_EC_TAIL, &tail_ms); } else if (ec_enabled) { /* Disable EC */ pj_bool_t value = PJ_FALSE; return pjmedia_aud_stream_set_cap(snd_port->aud_stream, PJMEDIA_AUD_DEV_CAP_EC, &value); } else { /* Request to disable EC but EC has been disabled */ /* Do nothing */ return PJ_SUCCESS; } } else { /* We use software EC */ /* Check if there is change in parameters */ if (tail_ms==snd_port->ec_tail_len && options==snd_port->ec_options) { PJ_LOG(5,(THIS_FILE, "pjmedia_snd_port_set_ec() ignored, no " "change in settings")); return PJ_SUCCESS; } status = pjmedia_aud_stream_get_param(snd_port->aud_stream, &prm); if (status != PJ_SUCCESS) return status; /* Audio stream must be in PCM format */ PJ_ASSERT_RETURN(prm.ext_fmt.id == PJMEDIA_FORMAT_PCM, PJ_EINVALIDOP); /* Destroy AEC */ if (snd_port->ec_state) { pjmedia_echo_destroy(snd_port->ec_state); snd_port->ec_state = NULL; } if (tail_ms != 0) { unsigned delay_ms; //No need to add input latency in the latency calculation, //since actual input latency should be zero. //delay_ms = (si.rec_latency + si.play_latency) * 1000 / // snd_port->clock_rate; /* Set EC latency to 3/4 of output latency to reduce the * possibility of missing/late reference frame. */ delay_ms = prm.output_latency_ms * 3/4; status = pjmedia_echo_create2(pool, snd_port->clock_rate, snd_port->channel_count, snd_port->samples_per_frame, tail_ms, delay_ms, options, &snd_port->ec_state); if (status != PJ_SUCCESS) snd_port->ec_state = NULL; else snd_port->ec_suspended = PJ_FALSE; } else { PJ_LOG(4,(THIS_FILE, "Echo canceller is now disabled in the " "sound port")); status = PJ_SUCCESS; } snd_port->ec_options = options; snd_port->ec_tail_len = tail_ms; } return status; } /* Get AEC tail length */ PJ_DEF(pj_status_t) pjmedia_snd_port_get_ec_tail( pjmedia_snd_port *snd_port, unsigned *p_length) { PJ_ASSERT_RETURN(snd_port && p_length, PJ_EINVAL); /* Determine whether we use device or software EC */ if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC) { /* We use device EC */ pj_bool_t ec_enabled; pj_status_t status; /* Query EC status */ status = pjmedia_aud_stream_get_cap(snd_port->aud_stream, PJMEDIA_AUD_DEV_CAP_EC, &ec_enabled); if (status != PJ_SUCCESS) return status; if (!ec_enabled) { *p_length = 0; } else if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC_TAIL) { /* Get device EC tail */ status = pjmedia_aud_stream_get_cap(snd_port->aud_stream, PJMEDIA_AUD_DEV_CAP_EC_TAIL, p_length); if (status != PJ_SUCCESS) return status; } else { /* Just use default */ *p_length = AEC_TAIL; } } else { /* We use software EC */ *p_length = snd_port->ec_state ? snd_port->ec_tail_len : 0; } return PJ_SUCCESS; } /* * Get clock source. */ PJ_DEF(pjmedia_clock_src *) pjmedia_snd_port_get_clock_src( pjmedia_snd_port *snd_port, pjmedia_dir dir ) { return (dir == PJMEDIA_DIR_CAPTURE? &snd_port->cap_clocksrc: &snd_port->play_clocksrc); } /* * Connect a port. */ PJ_DEF(pj_status_t) pjmedia_snd_port_connect( pjmedia_snd_port *snd_port, pjmedia_port *port) { pjmedia_audio_format_detail *afd; PJ_ASSERT_RETURN(snd_port && port, PJ_EINVAL); afd = pjmedia_format_get_audio_format_detail(&port->info.fmt, PJ_TRUE); /* Check that port has the same configuration as the sound device * port. */ if (afd->clock_rate != snd_port->clock_rate) return PJMEDIA_ENCCLOCKRATE; if (PJMEDIA_AFD_SPF(afd) != snd_port->samples_per_frame) return PJMEDIA_ENCSAMPLESPFRAME; if (afd->channel_count != snd_port->channel_count) return PJMEDIA_ENCCHANNEL; if (afd->bits_per_sample != snd_port->bits_per_sample) return PJMEDIA_ENCBITS; /* Port is okay. */ snd_port->port = port; return PJ_SUCCESS; } /* * Get the connected port. */ PJ_DEF(pjmedia_port*) pjmedia_snd_port_get_port(pjmedia_snd_port *snd_port) { PJ_ASSERT_RETURN(snd_port, NULL); return snd_port->port; } /* * Disconnect port. */ PJ_DEF(pj_status_t) pjmedia_snd_port_disconnect(pjmedia_snd_port *snd_port) { PJ_ASSERT_RETURN(snd_port, PJ_EINVAL); snd_port->port = NULL; return PJ_SUCCESS; } diff --git a/deps/pjsip/pjmedia/src/pjmedia/stream_info.c b/deps/pjsip/pjmedia/src/pjmedia/stream_info.c index 512ecb1e..f9ac96e6 100644 --- a/deps/pjsip/pjmedia/src/pjmedia/stream_info.c +++ b/deps/pjsip/pjmedia/src/pjmedia/stream_info.c @@ -1,532 +1,532 @@ -/* $Id: stream_info.c 3982 2012-03-22 09:56:52Z bennylp $ */ +/* $Id: stream_info.c 5239 2016-02-04 06:11:58Z ming $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include static const pj_str_t ID_AUDIO = { "audio", 5}; static const pj_str_t ID_IN = { "IN", 2 }; static const pj_str_t ID_IP4 = { "IP4", 3}; static const pj_str_t ID_IP6 = { "IP6", 3}; static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 }; static const pj_str_t ID_RTP_SAVP = { "RTP/SAVP", 8 }; //static const pj_str_t ID_SDP_NAME = { "pjmedia", 7 }; static const pj_str_t ID_RTPMAP = { "rtpmap", 6 }; static const pj_str_t ID_TELEPHONE_EVENT = { "telephone-event", 15 }; /* * Internal function for collecting codec info and param from the SDP media. */ static pj_status_t get_audio_codec_info_param(pjmedia_stream_info *si, pj_pool_t *pool, pjmedia_codec_mgr *mgr, const pjmedia_sdp_media *local_m, const pjmedia_sdp_media *rem_m) { const pjmedia_sdp_attr *attr; pjmedia_sdp_rtpmap *rtpmap; unsigned i, fmti, pt = 0; pj_status_t status; /* Find the first codec which is not telephone-event */ for ( fmti = 0; fmti < local_m->desc.fmt_count; ++fmti ) { pjmedia_sdp_rtpmap r; if ( !pj_isdigit(*local_m->desc.fmt[fmti].ptr) ) return PJMEDIA_EINVALIDPT; pt = pj_strtoul(&local_m->desc.fmt[fmti]); if (pt < 96) { /* This is known static PT. Skip rtpmap checking because it is * optional. */ break; } attr = pjmedia_sdp_media_find_attr(local_m, &ID_RTPMAP, &local_m->desc.fmt[fmti]); if (attr == NULL) continue; status = pjmedia_sdp_attr_get_rtpmap(attr, &r); if (status != PJ_SUCCESS) continue; if (pj_strcmp(&r.enc_name, &ID_TELEPHONE_EVENT) != 0) break; } if ( fmti >= local_m->desc.fmt_count ) return PJMEDIA_EINVALIDPT; /* Get payload type for receiving direction */ si->rx_pt = pt; /* Get codec info. * For static payload types, get the info from codec manager. * For dynamic payload types, MUST get the rtpmap. */ if (pt < 96) { pj_bool_t has_rtpmap; rtpmap = NULL; has_rtpmap = PJ_TRUE; attr = pjmedia_sdp_media_find_attr(local_m, &ID_RTPMAP, &local_m->desc.fmt[fmti]); if (attr == NULL) { has_rtpmap = PJ_FALSE; } if (attr != NULL) { status = pjmedia_sdp_attr_to_rtpmap(pool, attr, &rtpmap); if (status != PJ_SUCCESS) has_rtpmap = PJ_FALSE; } /* Build codec format info: */ if (has_rtpmap) { si->fmt.type = si->type; si->fmt.pt = pj_strtoul(&local_m->desc.fmt[fmti]); pj_strdup(pool, &si->fmt.encoding_name, &rtpmap->enc_name); si->fmt.clock_rate = rtpmap->clock_rate; #if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG != 0) /* The session info should have the actual clock rate, because * this info is used for calculationg buffer size, etc in stream */ if (si->fmt.pt == PJMEDIA_RTP_PT_G722) si->fmt.clock_rate = 16000; #endif /* For audio codecs, rtpmap parameters denotes the number of * channels. */ if (si->type == PJMEDIA_TYPE_AUDIO && rtpmap->param.slen) { si->fmt.channel_cnt = (unsigned) pj_strtoul(&rtpmap->param); } else { si->fmt.channel_cnt = 1; } } else { const pjmedia_codec_info *p_info; status = pjmedia_codec_mgr_get_codec_info( mgr, pt, &p_info); if (status != PJ_SUCCESS) return status; pj_memcpy(&si->fmt, p_info, sizeof(pjmedia_codec_info)); } /* For static payload type, pt's are symetric */ si->tx_pt = pt; } else { pjmedia_codec_id codec_id; pj_str_t codec_id_st; const pjmedia_codec_info *p_info; attr = pjmedia_sdp_media_find_attr(local_m, &ID_RTPMAP, &local_m->desc.fmt[fmti]); if (attr == NULL) return PJMEDIA_EMISSINGRTPMAP; status = pjmedia_sdp_attr_to_rtpmap(pool, attr, &rtpmap); if (status != PJ_SUCCESS) return status; /* Build codec format info: */ si->fmt.type = si->type; si->fmt.pt = pj_strtoul(&local_m->desc.fmt[fmti]); si->fmt.encoding_name = rtpmap->enc_name; si->fmt.clock_rate = rtpmap->clock_rate; /* For audio codecs, rtpmap parameters denotes the number of * channels. */ if (si->type == PJMEDIA_TYPE_AUDIO && rtpmap->param.slen) { si->fmt.channel_cnt = (unsigned) pj_strtoul(&rtpmap->param); } else { si->fmt.channel_cnt = 1; } /* Normalize the codec info from codec manager. Note that the * payload type will be resetted to its default (it might have * been rewritten by the SDP negotiator to match to the remote * offer), this is intentional as currently some components may * prefer (or even require) the default PT in codec info. */ pjmedia_codec_info_to_id(&si->fmt, codec_id, sizeof(codec_id)); i = 1; codec_id_st = pj_str(codec_id); status = pjmedia_codec_mgr_find_codecs_by_id(mgr, &codec_id_st, &i, &p_info, NULL); if (status != PJ_SUCCESS) return status; pj_memcpy(&si->fmt, p_info, sizeof(pjmedia_codec_info)); /* Determine payload type for outgoing channel, by finding * dynamic payload type in remote SDP that matches the answer. */ si->tx_pt = 0xFFFF; for (i=0; idesc.fmt_count; ++i) { if (pjmedia_sdp_neg_fmt_match(pool, (pjmedia_sdp_media*)local_m, fmti, (pjmedia_sdp_media*)rem_m, i, 0) == PJ_SUCCESS) { /* Found matched codec. */ si->tx_pt = pj_strtoul(&rem_m->desc.fmt[i]); break; } } if (si->tx_pt == 0xFFFF) return PJMEDIA_EMISSINGRTPMAP; } /* Now that we have codec info, get the codec param. */ si->param = PJ_POOL_ALLOC_T(pool, pjmedia_codec_param); status = pjmedia_codec_mgr_get_default_param(mgr, &si->fmt, si->param); /* Get remote fmtp for our encoder. */ pjmedia_stream_info_parse_fmtp(pool, rem_m, si->tx_pt, &si->param->setting.enc_fmtp); /* Get local fmtp for our decoder. */ pjmedia_stream_info_parse_fmtp(pool, local_m, si->rx_pt, &si->param->setting.dec_fmtp); /* Get the remote ptime for our encoder. */ attr = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr, "ptime", NULL); if (attr) { pj_str_t tmp_val = attr->value; unsigned frm_per_pkt; pj_strltrim(&tmp_val); /* Round up ptime when the specified is not multiple of frm_ptime */ frm_per_pkt = (pj_strtoul(&tmp_val) + si->param->info.frm_ptime/2) / si->param->info.frm_ptime; if (frm_per_pkt != 0) { si->param->setting.frm_per_pkt = (pj_uint8_t)frm_per_pkt; } } /* Get remote maxptime for our encoder. */ attr = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr, "maxptime", NULL); if (attr) { pj_str_t tmp_val = attr->value; pj_strltrim(&tmp_val); si->tx_maxptime = pj_strtoul(&tmp_val); } /* When direction is NONE (it means SDP negotiation has failed) we don't * need to return a failure here, as returning failure will cause * the whole SDP to be rejected. See ticket #: * http:// * * Thanks Alain Totouom */ if (status != PJ_SUCCESS && si->dir != PJMEDIA_DIR_NONE) return status; /* Get incomming payload type for telephone-events */ si->rx_event_pt = -1; for (i=0; iattr_count; ++i) { pjmedia_sdp_rtpmap r; attr = local_m->attr[i]; if (pj_strcmp(&attr->name, &ID_RTPMAP) != 0) continue; if (pjmedia_sdp_attr_get_rtpmap(attr, &r) != PJ_SUCCESS) continue; if (pj_strcmp(&r.enc_name, &ID_TELEPHONE_EVENT) == 0) { si->rx_event_pt = pj_strtoul(&r.pt); break; } } /* Get outgoing payload type for telephone-events */ si->tx_event_pt = -1; for (i=0; iattr_count; ++i) { pjmedia_sdp_rtpmap r; attr = rem_m->attr[i]; if (pj_strcmp(&attr->name, &ID_RTPMAP) != 0) continue; if (pjmedia_sdp_attr_get_rtpmap(attr, &r) != PJ_SUCCESS) continue; if (pj_strcmp(&r.enc_name, &ID_TELEPHONE_EVENT) == 0) { si->tx_event_pt = pj_strtoul(&r.pt); break; } } return PJ_SUCCESS; } /* * Create stream info from SDP media line. */ PJ_DEF(pj_status_t) pjmedia_stream_info_from_sdp( pjmedia_stream_info *si, pj_pool_t *pool, pjmedia_endpt *endpt, const pjmedia_sdp_session *local, const pjmedia_sdp_session *remote, unsigned stream_idx) { const pj_str_t STR_INACTIVE = { "inactive", 8 }; const pj_str_t STR_SENDONLY = { "sendonly", 8 }; const pj_str_t STR_RECVONLY = { "recvonly", 8 }; pjmedia_codec_mgr *mgr; const pjmedia_sdp_attr *attr; const pjmedia_sdp_media *local_m; const pjmedia_sdp_media *rem_m; const pjmedia_sdp_conn *local_conn; const pjmedia_sdp_conn *rem_conn; int rem_af, local_af; pj_sockaddr local_addr; pj_status_t status; /* Validate arguments: */ PJ_ASSERT_RETURN(pool && si && local && remote, PJ_EINVAL); PJ_ASSERT_RETURN(stream_idx < local->media_count, PJ_EINVAL); PJ_ASSERT_RETURN(stream_idx < remote->media_count, PJ_EINVAL); /* Keep SDP shortcuts */ local_m = local->media[stream_idx]; rem_m = remote->media[stream_idx]; local_conn = local_m->conn ? local_m->conn : local->conn; if (local_conn == NULL) return PJMEDIA_SDP_EMISSINGCONN; rem_conn = rem_m->conn ? rem_m->conn : remote->conn; if (rem_conn == NULL) return PJMEDIA_SDP_EMISSINGCONN; /* Media type must be audio */ if (pj_stricmp(&local_m->desc.media, &ID_AUDIO) != 0) return PJMEDIA_EINVALIMEDIATYPE; /* Get codec manager. */ mgr = pjmedia_endpt_get_codec_mgr(endpt); /* Reset: */ pj_bzero(si, sizeof(*si)); #if PJMEDIA_HAS_RTCP_XR && PJMEDIA_STREAM_ENABLE_XR /* Set default RTCP XR enabled/disabled */ si->rtcp_xr_enabled = PJ_TRUE; #endif /* Media type: */ si->type = PJMEDIA_TYPE_AUDIO; /* Transport protocol */ /* At this point, transport type must be compatible, * the transport instance will do more validation later. */ status = pjmedia_sdp_transport_cmp(&rem_m->desc.transport, &local_m->desc.transport); if (status != PJ_SUCCESS) return PJMEDIA_SDPNEG_EINVANSTP; if (pj_stricmp(&local_m->desc.transport, &ID_RTP_AVP) == 0) { si->proto = PJMEDIA_TP_PROTO_RTP_AVP; } else if (pj_stricmp(&local_m->desc.transport, &ID_RTP_SAVP) == 0) { si->proto = PJMEDIA_TP_PROTO_RTP_SAVP; } else { si->proto = PJMEDIA_TP_PROTO_UNKNOWN; return PJ_SUCCESS; } /* Check address family in remote SDP */ rem_af = pj_AF_UNSPEC(); if (pj_stricmp(&rem_conn->net_type, &ID_IN)==0) { if (pj_stricmp(&rem_conn->addr_type, &ID_IP4)==0) { rem_af = pj_AF_INET(); } else if (pj_stricmp(&rem_conn->addr_type, &ID_IP6)==0) { rem_af = pj_AF_INET6(); } } if (rem_af==pj_AF_UNSPEC()) { /* Unsupported address family */ return PJ_EAFNOTSUP; } /* Set remote address: */ status = pj_sockaddr_init(rem_af, &si->rem_addr, &rem_conn->addr, rem_m->desc.port); if (status != PJ_SUCCESS) { /* Invalid IP address. */ return PJMEDIA_EINVALIDIP; } /* Check address family of local info */ local_af = pj_AF_UNSPEC(); if (pj_stricmp(&local_conn->net_type, &ID_IN)==0) { if (pj_stricmp(&local_conn->addr_type, &ID_IP4)==0) { local_af = pj_AF_INET(); } else if (pj_stricmp(&local_conn->addr_type, &ID_IP6)==0) { local_af = pj_AF_INET6(); } } if (local_af==pj_AF_UNSPEC()) { /* Unsupported address family */ return PJ_SUCCESS; } /* Set remote address: */ status = pj_sockaddr_init(local_af, &local_addr, &local_conn->addr, local_m->desc.port); if (status != PJ_SUCCESS) { /* Invalid IP address. */ return PJMEDIA_EINVALIDIP; } /* Local and remote address family must match */ if (local_af != rem_af) return PJ_EAFNOTSUP; /* Media direction: */ if (local_m->desc.port == 0 || pj_sockaddr_has_addr(&local_addr)==PJ_FALSE || pj_sockaddr_has_addr(&si->rem_addr)==PJ_FALSE || pjmedia_sdp_media_find_attr(local_m, &STR_INACTIVE, NULL)!=NULL) { /* Inactive stream. */ si->dir = PJMEDIA_DIR_NONE; } else if (pjmedia_sdp_media_find_attr(local_m, &STR_SENDONLY, NULL)!=NULL) { /* Send only stream. */ si->dir = PJMEDIA_DIR_ENCODING; } else if (pjmedia_sdp_media_find_attr(local_m, &STR_RECVONLY, NULL)!=NULL) { /* Recv only stream. */ si->dir = PJMEDIA_DIR_DECODING; } else { /* Send and receive stream. */ si->dir = PJMEDIA_DIR_ENCODING_DECODING; } /* No need to do anything else if stream is rejected */ if (local_m->desc.port == 0) { return PJ_SUCCESS; } /* If "rtcp" attribute is present in the SDP, set the RTCP address * from that attribute. Otherwise, calculate from RTP address. */ attr = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr, "rtcp", NULL); if (attr) { pjmedia_sdp_rtcp_attr rtcp; status = pjmedia_sdp_attr_get_rtcp(attr, &rtcp); if (status == PJ_SUCCESS) { if (rtcp.addr.slen) { status = pj_sockaddr_init(rem_af, &si->rem_rtcp, &rtcp.addr, (pj_uint16_t)rtcp.port); } else { pj_sockaddr_init(rem_af, &si->rem_rtcp, NULL, (pj_uint16_t)rtcp.port); pj_memcpy(pj_sockaddr_get_addr(&si->rem_rtcp), pj_sockaddr_get_addr(&si->rem_addr), pj_sockaddr_get_addr_len(&si->rem_addr)); } } } if (!pj_sockaddr_has_addr(&si->rem_rtcp)) { int rtcp_port; pj_memcpy(&si->rem_rtcp, &si->rem_addr, sizeof(pj_sockaddr)); rtcp_port = pj_sockaddr_get_port(&si->rem_addr) + 1; pj_sockaddr_set_port(&si->rem_rtcp, (pj_uint16_t)rtcp_port); } /* Get the payload number for receive channel. */ /* Previously we used to rely on fmt[0] being the selected codec, but some UA sends telephone-event as fmt[0] and this would cause assert failure below. Thanks Chris Hamilton for this patch. // And codec must be numeric! if (!pj_isdigit(*local_m->desc.fmt[0].ptr) || !pj_isdigit(*rem_m->desc.fmt[0].ptr)) { return PJMEDIA_EINVALIDPT; } pt = pj_strtoul(&local_m->desc.fmt[0]); pj_assert(PJMEDIA_RTP_PT_TELEPHONE_EVENTS==0 || pt != PJMEDIA_RTP_PT_TELEPHONE_EVENTS); */ /* Get codec info and param */ status = get_audio_codec_info_param(si, pool, mgr, local_m, rem_m); /* Leave SSRC to random. */ si->ssrc = pj_rand(); /* Set default jitter buffer parameter. */ si->jb_init = si->jb_max = si->jb_min_pre = si->jb_max_pre = -1; return status; } diff --git a/deps/pjsip/pjmedia/src/pjmedia/transport_ice.c b/deps/pjsip/pjmedia/src/pjmedia/transport_ice.c index 55a814a8..28806c81 100644 --- a/deps/pjsip/pjmedia/src/pjmedia/transport_ice.c +++ b/deps/pjsip/pjmedia/src/pjmedia/transport_ice.c @@ -1,1908 +1,1908 @@ -/* $Id: transport_ice.c 4350 2013-02-15 03:57:31Z nanang $ */ +/* $Id: transport_ice.c 4949 2014-10-17 00:48:33Z ming $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #define THIS_FILE "transport_ice.c" #if 0 # define TRACE__(expr) PJ_LOG(5,expr) #else # define TRACE__(expr) #endif enum oa_role { ROLE_NONE, ROLE_OFFERER, ROLE_ANSWERER }; struct sdp_state { unsigned match_comp_cnt; /* Matching number of components */ pj_bool_t ice_mismatch; /* Address doesn't match candidates */ pj_bool_t ice_restart; /* Offer to restart ICE */ pj_ice_sess_role local_role; /* Our role */ }; struct transport_ice { pjmedia_transport base; pj_pool_t *pool; int af; unsigned options; /**< Transport options. */ unsigned comp_cnt; pj_ice_strans *ice_st; pjmedia_ice_cb cb; unsigned media_option; pj_bool_t initial_sdp; enum oa_role oa_role; /**< Last role in SDP offer/answer */ struct sdp_state rem_offer_state;/**< Describes the remote offer */ void *stream; pj_sockaddr remote_rtp; pj_sockaddr remote_rtcp; unsigned addr_len; /**< Length of addresses. */ pj_bool_t use_ice; pj_sockaddr rtp_src_addr; /**< Actual source RTP address. */ pj_sockaddr rtcp_src_addr; /**< Actual source RTCP address. */ unsigned rtp_src_cnt; /**< How many pkt from this addr. */ unsigned rtcp_src_cnt; /**< How many pkt from this addr. */ unsigned tx_drop_pct; /**< Percent of tx pkts to drop. */ unsigned rx_drop_pct; /**< Percent of rx pkts to drop. */ void (*rtp_cb)(void*, void*, pj_ssize_t); void (*rtcp_cb)(void*, void*, pj_ssize_t); }; /* * These are media transport operations. */ static pj_status_t transport_get_info (pjmedia_transport *tp, pjmedia_transport_info *info); static pj_status_t transport_attach (pjmedia_transport *tp, void *user_data, const pj_sockaddr_t *rem_addr, const pj_sockaddr_t *rem_rtcp, unsigned addr_len, void (*rtp_cb)(void*, void*, pj_ssize_t), void (*rtcp_cb)(void*, void*, pj_ssize_t)); static void transport_detach (pjmedia_transport *tp, void *strm); static pj_status_t transport_send_rtp( pjmedia_transport *tp, const void *pkt, pj_size_t size); static pj_status_t transport_send_rtcp(pjmedia_transport *tp, const void *pkt, pj_size_t size); static pj_status_t transport_send_rtcp2(pjmedia_transport *tp, const pj_sockaddr_t *addr, unsigned addr_len, const void *pkt, pj_size_t size); static pj_status_t transport_media_create(pjmedia_transport *tp, pj_pool_t *pool, unsigned options, const pjmedia_sdp_session *rem_sdp, unsigned media_index); static pj_status_t transport_encode_sdp(pjmedia_transport *tp, pj_pool_t *tmp_pool, pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *rem_sdp, unsigned media_index); static pj_status_t transport_media_start(pjmedia_transport *tp, pj_pool_t *pool, const pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *rem_sdp, unsigned media_index); static pj_status_t transport_media_stop(pjmedia_transport *tp); static pj_status_t transport_simulate_lost(pjmedia_transport *tp, pjmedia_dir dir, unsigned pct_lost); static pj_status_t transport_destroy (pjmedia_transport *tp); /* * And these are ICE callbacks. */ static void ice_on_rx_data(pj_ice_strans *ice_st, unsigned comp_id, void *pkt, pj_size_t size, const pj_sockaddr_t *src_addr, unsigned src_addr_len); static void ice_on_ice_complete(pj_ice_strans *ice_st, pj_ice_strans_op op, pj_status_t status); static void ice_on_ice_state(pj_ice_strans *ice_st, pj_ice_strans_state prev, pj_ice_strans_state curr); static pjmedia_transport_op transport_ice_op = { &transport_get_info, &transport_attach, &transport_detach, &transport_send_rtp, &transport_send_rtcp, &transport_send_rtcp2, &transport_media_create, &transport_encode_sdp, &transport_media_start, &transport_media_stop, &transport_simulate_lost, &transport_destroy }; static const pj_str_t STR_RTP_AVP = { "RTP/AVP", 7 }; static const pj_str_t STR_CANDIDATE = { "candidate", 9}; static const pj_str_t STR_REM_CAND = { "remote-candidates", 17 }; static const pj_str_t STR_ICE_LITE = { "ice-lite", 8}; static const pj_str_t STR_ICE_MISMATCH = { "ice-mismatch", 12}; static const pj_str_t STR_ICE_UFRAG = { "ice-ufrag", 9 }; static const pj_str_t STR_ICE_PWD = { "ice-pwd", 7 }; static const pj_str_t STR_IP4 = { "IP4", 3 }; static const pj_str_t STR_IP6 = { "IP6", 3 }; static const pj_str_t STR_RTCP = { "rtcp", 4 }; static const pj_str_t STR_BANDW_RR = { "RR", 2 }; static const pj_str_t STR_BANDW_RS = { "RS", 2 }; enum { COMP_RTP = 1, COMP_RTCP = 2 }; /* * Create ICE media transport. */ PJ_DEF(pj_status_t) pjmedia_ice_create(pjmedia_endpt *endpt, const char *name, unsigned comp_cnt, const pj_ice_strans_cfg *cfg, const pjmedia_ice_cb *cb, pjmedia_transport **p_tp) { return pjmedia_ice_create2(endpt, name, comp_cnt, cfg, cb, 0, p_tp); } /* * Create ICE media transport. */ PJ_DEF(pj_status_t) pjmedia_ice_create2(pjmedia_endpt *endpt, const char *name, unsigned comp_cnt, const pj_ice_strans_cfg *cfg, const pjmedia_ice_cb *cb, unsigned options, pjmedia_transport **p_tp) { return pjmedia_ice_create3(endpt, name, comp_cnt, cfg, cb, options, NULL, p_tp); } /* * Create ICE media transport. */ PJ_DEF(pj_status_t) pjmedia_ice_create3(pjmedia_endpt *endpt, const char *name, unsigned comp_cnt, const pj_ice_strans_cfg *cfg, const pjmedia_ice_cb *cb, unsigned options, void *user_data, pjmedia_transport **p_tp) { pj_pool_t *pool; pj_ice_strans_cb ice_st_cb; pj_ice_strans_cfg ice_st_cfg; struct transport_ice *tp_ice; pj_status_t status; PJ_ASSERT_RETURN(endpt && comp_cnt && cfg && p_tp, PJ_EINVAL); /* Create transport instance */ pool = pjmedia_endpt_create_pool(endpt, name, 512, 512); tp_ice = PJ_POOL_ZALLOC_T(pool, struct transport_ice); tp_ice->pool = pool; tp_ice->af = cfg->af; tp_ice->options = options; tp_ice->comp_cnt = comp_cnt; pj_ansi_strcpy(tp_ice->base.name, pool->obj_name); tp_ice->base.op = &transport_ice_op; tp_ice->base.type = PJMEDIA_TRANSPORT_TYPE_ICE; tp_ice->base.user_data = user_data; tp_ice->initial_sdp = PJ_TRUE; tp_ice->oa_role = ROLE_NONE; tp_ice->use_ice = PJ_FALSE; pj_memcpy(&ice_st_cfg, cfg, sizeof(pj_ice_strans_cfg)); if (cb) pj_memcpy(&tp_ice->cb, cb, sizeof(pjmedia_ice_cb)); /* Assign return value first because ICE might call callback * in create() */ *p_tp = &tp_ice->base; /* Configure ICE callbacks */ pj_bzero(&ice_st_cb, sizeof(ice_st_cb)); ice_st_cb.on_ice_complete = &ice_on_ice_complete; ice_st_cb.on_ice_state = &ice_on_ice_state; ice_st_cb.on_rx_data = &ice_on_rx_data; /* Configure RTP socket buffer settings, if not set */ if (ice_st_cfg.comp[COMP_RTP-1].so_rcvbuf_size == 0) { ice_st_cfg.comp[COMP_RTP-1].so_rcvbuf_size = PJMEDIA_TRANSPORT_SO_RCVBUF_SIZE; } if (ice_st_cfg.comp[COMP_RTP-1].so_sndbuf_size == 0) { ice_st_cfg.comp[COMP_RTP-1].so_sndbuf_size = PJMEDIA_TRANSPORT_SO_SNDBUF_SIZE; } /* Create ICE */ status = pj_ice_strans_create(name, &ice_st_cfg, comp_cnt, tp_ice, &ice_st_cb, &tp_ice->ice_st); if (status != PJ_SUCCESS) { pj_pool_release(pool); *p_tp = NULL; return status; } /* Done */ return PJ_SUCCESS; } /* * Get the ICE stream transport associated with this media transport. */ PJ_DEF(pj_ice_strans*) pjmedia_ice_get_strans(pjmedia_transport *tp) { struct transport_ice *tp_ice; tp_ice = (struct transport_ice*) tp; return tp_ice->ice_st; } PJ_DEF(pj_grp_lock_t *) pjmedia_ice_get_grp_lock(pjmedia_transport *tp) { PJ_ASSERT_RETURN(tp, NULL); return pj_ice_strans_get_grp_lock(((struct transport_ice *)tp)->ice_st); } /* Disable ICE when SDP from remote doesn't contain a=candidate line */ static void set_no_ice(struct transport_ice *tp_ice, const char *reason, pj_status_t err) { if (err != PJ_SUCCESS) { char errmsg[PJ_ERR_MSG_SIZE]; pj_strerror(err, errmsg, sizeof(errmsg)); PJ_LOG(4,(tp_ice->base.name, "Stopping ICE, reason=%s:%s", reason, errmsg)); } else { PJ_LOG(4,(tp_ice->base.name, "Stopping ICE, reason=%s", reason)); } /* Notify application about ICE stop */ if (tp_ice->cb.on_ice_stop) (*tp_ice->cb.on_ice_stop)(&tp_ice->base, (char *)reason, err); if (tp_ice->ice_st) { pj_ice_strans_stop_ice(tp_ice->ice_st); } tp_ice->use_ice = PJ_FALSE; } /* Create SDP candidate attribute */ static int print_sdp_cand_attr(char *buffer, int max_len, const pj_ice_sess_cand *cand) { char ipaddr[PJ_INET6_ADDRSTRLEN+2]; int len, len2; len = pj_ansi_snprintf( buffer, max_len, "%.*s %u UDP %u %s %u typ ", (int)cand->foundation.slen, cand->foundation.ptr, (unsigned)cand->comp_id, cand->prio, pj_sockaddr_print(&cand->addr, ipaddr, sizeof(ipaddr), 0), (unsigned)pj_sockaddr_get_port(&cand->addr)); if (len < 1 || len >= max_len) return -1; switch (cand->type) { case PJ_ICE_CAND_TYPE_HOST: len2 = pj_ansi_snprintf(buffer+len, max_len-len, "host"); break; case PJ_ICE_CAND_TYPE_SRFLX: case PJ_ICE_CAND_TYPE_RELAYED: case PJ_ICE_CAND_TYPE_PRFLX: len2 = pj_ansi_snprintf(buffer+len, max_len-len, "%s raddr %s rport %d", pj_ice_get_cand_type_name(cand->type), pj_sockaddr_print(&cand->rel_addr, ipaddr, sizeof(ipaddr), 0), (int)pj_sockaddr_get_port(&cand->rel_addr)); break; default: pj_assert(!"Invalid candidate type"); len2 = -1; break; } if (len2 < 1 || len2 >= max_len-len) return -1; return len+len2; } /* Get ice-ufrag and ice-pwd attribute */ static void get_ice_attr(const pjmedia_sdp_session *rem_sdp, const pjmedia_sdp_media *rem_m, const pjmedia_sdp_attr **p_ice_ufrag, const pjmedia_sdp_attr **p_ice_pwd) { pjmedia_sdp_attr *attr; /* Find ice-ufrag attribute in media descriptor */ attr = pjmedia_sdp_attr_find(rem_m->attr_count, rem_m->attr, &STR_ICE_UFRAG, NULL); if (attr == NULL) { /* Find ice-ufrag attribute in session descriptor */ attr = pjmedia_sdp_attr_find(rem_sdp->attr_count, rem_sdp->attr, &STR_ICE_UFRAG, NULL); } *p_ice_ufrag = attr; /* Find ice-pwd attribute in media descriptor */ attr = pjmedia_sdp_attr_find(rem_m->attr_count, rem_m->attr, &STR_ICE_PWD, NULL); if (attr == NULL) { /* Find ice-pwd attribute in session descriptor */ attr = pjmedia_sdp_attr_find(rem_sdp->attr_count, rem_sdp->attr, &STR_ICE_PWD, NULL); } *p_ice_pwd = attr; } /* Encode and add "a=ice-mismatch" attribute in the SDP */ static void encode_ice_mismatch(pj_pool_t *sdp_pool, pjmedia_sdp_session *sdp_local, unsigned media_index) { pjmedia_sdp_attr *attr; pjmedia_sdp_media *m = sdp_local->media[media_index]; attr = PJ_POOL_ALLOC_T(sdp_pool, pjmedia_sdp_attr); attr->name = STR_ICE_MISMATCH; attr->value.slen = 0; pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); } /* Encode ICE information in SDP */ static pj_status_t encode_session_in_sdp(struct transport_ice *tp_ice, pj_pool_t *sdp_pool, pjmedia_sdp_session *sdp_local, unsigned media_index, unsigned comp_cnt, pj_bool_t restart_session) { enum { ATTR_BUF_LEN = 160, /* Max len of a=candidate attr */ RATTR_BUF_LEN= 160 /* Max len of a=remote-candidates attr */ }; pjmedia_sdp_media *m = sdp_local->media[media_index]; pj_str_t local_ufrag, local_pwd; pjmedia_sdp_attr *attr; pj_status_t status; /* Must have a session */ PJ_ASSERT_RETURN(pj_ice_strans_has_sess(tp_ice->ice_st), PJ_EBUG); /* Get ufrag and pwd from current session */ pj_ice_strans_get_ufrag_pwd(tp_ice->ice_st, &local_ufrag, &local_pwd, NULL, NULL); /* The listing of candidates depends on whether ICE has completed * or not. When ICE has completed: * * 9.1.2.2: Existing Media Streams with ICE Completed * The agent MUST include a candidate attributes for candidates * matching the default destination for each component of the * media stream, and MUST NOT include any other candidates. * * When ICE has not completed, we shall include all candidates. * * Except when we have detected that remote is offering to restart * the session, in this case we will answer with full ICE SDP and * new ufrag/pwd pair. */ if (!restart_session && pj_ice_strans_sess_is_complete(tp_ice->ice_st) && pj_ice_strans_get_state(tp_ice->ice_st) != PJ_ICE_STRANS_STATE_FAILED) { const pj_ice_sess_check *check; char *attr_buf; pjmedia_sdp_conn *conn; pjmedia_sdp_attr *a_rtcp; pj_str_t rem_cand; unsigned comp; /* Encode ice-ufrag attribute */ attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_UFRAG.ptr, &local_ufrag); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); /* Encode ice-pwd attribute */ attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_PWD.ptr, &local_pwd); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); /* Prepare buffer */ attr_buf = (char*) pj_pool_alloc(sdp_pool, ATTR_BUF_LEN); rem_cand.ptr = (char*) pj_pool_alloc(sdp_pool, RATTR_BUF_LEN); rem_cand.slen = 0; /* 9.1.2.2: Existing Media Streams with ICE Completed * The default destination for media (i.e., the values of * the IP addresses and ports in the m and c line used for * that media stream) MUST be the local candidate from the * highest priority nominated pair in the valid list for each * component. */ check = pj_ice_strans_get_valid_pair(tp_ice->ice_st, 1); if (check == NULL) { pj_assert(!"Shouldn't happen"); return PJ_EBUG; } /* Override connection line address and media port number */ conn = m->conn; if (conn == NULL) conn = sdp_local->conn; conn->addr.ptr = (char*) pj_pool_alloc(sdp_pool, PJ_INET6_ADDRSTRLEN); pj_sockaddr_print(&check->lcand->addr, conn->addr.ptr, PJ_INET6_ADDRSTRLEN, 0); conn->addr.slen = pj_ansi_strlen(conn->addr.ptr); m->desc.port = pj_sockaddr_get_port(&check->lcand->addr); /* Override address RTCP attribute if it's present */ if (comp_cnt == 2 && (check = pj_ice_strans_get_valid_pair(tp_ice->ice_st, COMP_RTCP)) != NULL && (a_rtcp = pjmedia_sdp_attr_find(m->attr_count, m->attr, &STR_RTCP, 0)) != NULL) { pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a_rtcp); a_rtcp = pjmedia_sdp_attr_create_rtcp(sdp_pool, &check->lcand->addr); if (a_rtcp) pjmedia_sdp_attr_add(&m->attr_count, m->attr, a_rtcp); } /* Encode only candidates matching the default destination * for each component */ for (comp=0; comp < comp_cnt; ++comp) { int len; pj_str_t value; /* Get valid pair for this component */ check = pj_ice_strans_get_valid_pair(tp_ice->ice_st, comp+1); if (check == NULL) continue; /* Print and add local candidate in the pair */ value.ptr = attr_buf; value.slen = print_sdp_cand_attr(attr_buf, ATTR_BUF_LEN, check->lcand); if (value.slen < 0) { pj_assert(!"Not enough attr_buf to print candidate"); return PJ_EBUG; } attr = pjmedia_sdp_attr_create(sdp_pool, STR_CANDIDATE.ptr, &value); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); /* Append to a=remote-candidates attribute */ if (pj_ice_strans_get_role(tp_ice->ice_st) == PJ_ICE_SESS_ROLE_CONTROLLING) { char rem_addr[PJ_INET6_ADDRSTRLEN]; pj_sockaddr_print(&check->rcand->addr, rem_addr, sizeof(rem_addr), 0); len = pj_ansi_snprintf( rem_cand.ptr + rem_cand.slen, RATTR_BUF_LEN - rem_cand.slen, "%s%u %s %u", (rem_cand.slen==0? "" : " "), comp+1, rem_addr, pj_sockaddr_get_port(&check->rcand->addr) ); if (len < 1 || len >= RATTR_BUF_LEN - rem_cand.slen) { pj_assert(!"Not enough buffer to print " "remote-candidates"); return PJ_EBUG; } rem_cand.slen += len; } } /* 9.1.2.2: Existing Media Streams with ICE Completed * In addition, if the agent is controlling, it MUST include * the a=remote-candidates attribute for each media stream * whose check list is in the Completed state. The attribute * contains the remote candidates from the highest priority * nominated pair in the valid list for each component of that * media stream. */ if (pj_ice_strans_get_role(tp_ice->ice_st) == PJ_ICE_SESS_ROLE_CONTROLLING) { attr = pjmedia_sdp_attr_create(sdp_pool, STR_REM_CAND.ptr, &rem_cand); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); } } else if (pj_ice_strans_has_sess(tp_ice->ice_st) && (restart_session || pj_ice_strans_get_state(tp_ice->ice_st) != PJ_ICE_STRANS_STATE_FAILED)) { /* Encode all candidates to SDP media */ char *attr_buf; unsigned comp; /* If ICE is not restarted, encode current ICE ufrag/pwd. * Otherwise generate new one. */ if (!restart_session) { attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_UFRAG.ptr, &local_ufrag); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_PWD.ptr, &local_pwd); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); } else { pj_str_t str; str.slen = PJ_ICE_UFRAG_LEN; str.ptr = (char*) pj_pool_alloc(sdp_pool, str.slen); pj_create_random_string(str.ptr, str.slen); attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_UFRAG.ptr, &str); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); str.ptr = (char*) pj_pool_alloc(sdp_pool, str.slen); pj_create_random_string(str.ptr, str.slen); attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_PWD.ptr, &str); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); } /* Create buffer to encode candidates as SDP attribute */ attr_buf = (char*) pj_pool_alloc(sdp_pool, ATTR_BUF_LEN); for (comp=0; comp < comp_cnt; ++comp) { unsigned cand_cnt; pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND]; unsigned i; cand_cnt = PJ_ARRAY_SIZE(cand); status = pj_ice_strans_enum_cands(tp_ice->ice_st, comp+1, &cand_cnt, cand); if (status != PJ_SUCCESS) return status; for (i=0; iattr_count, m->attr, attr); } } } else { /* ICE has failed, application should have terminated this call */ } /* Removing a=rtcp line when there is only one component. */ if (comp_cnt == 1) { attr = pjmedia_sdp_attr_find(m->attr_count, m->attr, &STR_RTCP, NULL); if (attr) pjmedia_sdp_attr_remove(&m->attr_count, m->attr, attr); /* If RTCP is not in use, we MUST send b=RS:0 and b=RR:0. */ pj_assert(m->bandw_count + 2 <= PJ_ARRAY_SIZE(m->bandw)); if (m->bandw_count + 2 <= PJ_ARRAY_SIZE(m->bandw)) { m->bandw[m->bandw_count] = PJ_POOL_ZALLOC_T(sdp_pool, pjmedia_sdp_bandw); pj_memcpy(&m->bandw[m->bandw_count]->modifier, &STR_BANDW_RS, sizeof(pj_str_t)); m->bandw_count++; m->bandw[m->bandw_count] = PJ_POOL_ZALLOC_T(sdp_pool, pjmedia_sdp_bandw); pj_memcpy(&m->bandw[m->bandw_count]->modifier, &STR_BANDW_RR, sizeof(pj_str_t)); m->bandw_count++; } } return PJ_SUCCESS; } /* Parse a=candidate line */ static pj_status_t parse_cand(const char *obj_name, pj_pool_t *pool, const pj_str_t *orig_input, pj_ice_sess_cand *cand) { pj_str_t input; char *token, *host; int af; pj_str_t s; pj_status_t status = PJNATH_EICEINCANDSDP; pj_bzero(cand, sizeof(*cand)); pj_strdup_with_null(pool, &input, orig_input); PJ_UNUSED_ARG(obj_name); /* Foundation */ token = strtok(input.ptr, " "); if (!token) { TRACE__((obj_name, "Expecting ICE foundation in candidate")); goto on_return; } pj_strdup2(pool, &cand->foundation, token); /* Component ID */ token = strtok(NULL, " "); if (!token) { TRACE__((obj_name, "Expecting ICE component ID in candidate")); goto on_return; } cand->comp_id = (pj_uint8_t) atoi(token); /* Transport */ token = strtok(NULL, " "); if (!token) { TRACE__((obj_name, "Expecting ICE transport in candidate")); goto on_return; } if (pj_ansi_stricmp(token, "UDP") != 0) { TRACE__((obj_name, "Expecting ICE UDP transport only in candidate")); goto on_return; } /* Priority */ token = strtok(NULL, " "); if (!token) { TRACE__((obj_name, "Expecting ICE priority in candidate")); goto on_return; } cand->prio = atoi(token); /* Host */ host = strtok(NULL, " "); if (!host) { TRACE__((obj_name, "Expecting ICE host in candidate")); goto on_return; } /* Detect address family */ if (pj_ansi_strchr(host, ':')) af = pj_AF_INET6(); else af = pj_AF_INET(); /* Assign address */ if (pj_sockaddr_init(af, &cand->addr, pj_cstr(&s, host), 0)) { TRACE__((obj_name, "Invalid ICE candidate address")); goto on_return; } /* Port */ token = strtok(NULL, " "); if (!token) { TRACE__((obj_name, "Expecting ICE port number in candidate")); goto on_return; } pj_sockaddr_set_port(&cand->addr, (pj_uint16_t)atoi(token)); /* typ */ token = strtok(NULL, " "); if (!token) { TRACE__((obj_name, "Expecting ICE \"typ\" in candidate")); goto on_return; } if (pj_ansi_stricmp(token, "typ") != 0) { TRACE__((obj_name, "Expecting ICE \"typ\" in candidate")); goto on_return; } /* candidate type */ token = strtok(NULL, " "); if (!token) { TRACE__((obj_name, "Expecting ICE candidate type in candidate")); goto on_return; } if (pj_ansi_stricmp(token, "host") == 0) { cand->type = PJ_ICE_CAND_TYPE_HOST; } else if (pj_ansi_stricmp(token, "srflx") == 0) { cand->type = PJ_ICE_CAND_TYPE_SRFLX; } else if (pj_ansi_stricmp(token, "relay") == 0) { cand->type = PJ_ICE_CAND_TYPE_RELAYED; } else if (pj_ansi_stricmp(token, "prflx") == 0) { cand->type = PJ_ICE_CAND_TYPE_PRFLX; } else { PJ_LOG(5,(obj_name, "Invalid ICE candidate type %s in candidate", token)); goto on_return; } status = PJ_SUCCESS; on_return: return status; } /* Create initial SDP offer */ static pj_status_t create_initial_offer(struct transport_ice *tp_ice, pj_pool_t *sdp_pool, pjmedia_sdp_session *loc_sdp, unsigned media_index) { pj_status_t status; /* Encode ICE in SDP */ status = encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index, tp_ice->comp_cnt, PJ_FALSE); if (status != PJ_SUCCESS) { set_no_ice(tp_ice, "Error encoding SDP answer", status); return status; } return PJ_SUCCESS; } /* Verify incoming offer */ static pj_status_t verify_ice_sdp(struct transport_ice *tp_ice, pj_pool_t *tmp_pool, const pjmedia_sdp_session *rem_sdp, unsigned media_index, pj_ice_sess_role current_ice_role, struct sdp_state *sdp_state) { const pjmedia_sdp_media *rem_m; const pjmedia_sdp_attr *ufrag_attr, *pwd_attr; const pjmedia_sdp_conn *rem_conn; pj_bool_t comp1_found=PJ_FALSE, comp2_found=PJ_FALSE, has_rtcp=PJ_FALSE; pj_sockaddr rem_conn_addr, rtcp_addr; unsigned i; pj_status_t status; rem_m = rem_sdp->media[media_index]; /* Get the "ice-ufrag" and "ice-pwd" attributes */ get_ice_attr(rem_sdp, rem_m, &ufrag_attr, &pwd_attr); /* If "ice-ufrag" or "ice-pwd" are not found, disable ICE */ if (ufrag_attr==NULL || pwd_attr==NULL) { sdp_state->match_comp_cnt = 0; return PJ_SUCCESS; } /* Verify that default target for each component matches one of the * candidate for the component. Otherwise stop ICE with ICE ice_mismatch * error. */ /* Component 1 is the c= line */ rem_conn = rem_m->conn; if (rem_conn == NULL) rem_conn = rem_sdp->conn; if (!rem_conn) return PJMEDIA_SDP_EMISSINGCONN; /* Verify address family matches */ if ((tp_ice->af==pj_AF_INET() && pj_strcmp(&rem_conn->addr_type, &STR_IP4)!=0) || (tp_ice->af==pj_AF_INET6() && pj_strcmp(&rem_conn->addr_type, &STR_IP6)!=0)) { return PJMEDIA_SDP_ETPORTNOTEQUAL; } /* Assign remote connection address */ status = pj_sockaddr_init(tp_ice->af, &rem_conn_addr, &rem_conn->addr, (pj_uint16_t)rem_m->desc.port); if (status != PJ_SUCCESS) return status; if (tp_ice->comp_cnt > 1) { const pjmedia_sdp_attr *attr; /* Get default RTCP candidate from a=rtcp line, if present, otherwise * calculate default RTCP candidate from default RTP target. */ attr = pjmedia_sdp_attr_find(rem_m->attr_count, rem_m->attr, &STR_RTCP, NULL); has_rtcp = (attr != NULL); if (attr) { pjmedia_sdp_rtcp_attr rtcp_attr; status = pjmedia_sdp_attr_get_rtcp(attr, &rtcp_attr); if (status != PJ_SUCCESS) { /* Error parsing a=rtcp attribute */ return status; } if (rtcp_attr.addr.slen) { /* Verify address family matches */ if ((tp_ice->af==pj_AF_INET() && pj_strcmp(&rtcp_attr.addr_type, &STR_IP4)!=0) || (tp_ice->af==pj_AF_INET6() && pj_strcmp(&rtcp_attr.addr_type, &STR_IP6)!=0)) { return PJMEDIA_SDP_ETPORTNOTEQUAL; } /* Assign RTCP address */ status = pj_sockaddr_init(tp_ice->af, &rtcp_addr, &rtcp_attr.addr, (pj_uint16_t)rtcp_attr.port); if (status != PJ_SUCCESS) { return PJMEDIA_SDP_EINRTCP; } } else { /* Assign RTCP address */ status = pj_sockaddr_init(tp_ice->af, &rtcp_addr, NULL, (pj_uint16_t)rtcp_attr.port); if (status != PJ_SUCCESS) { return PJMEDIA_SDP_EINRTCP; } pj_sockaddr_copy_addr(&rtcp_addr, &rem_conn_addr); } } else { unsigned rtcp_port; rtcp_port = pj_sockaddr_get_port(&rem_conn_addr) + 1; pj_sockaddr_cp(&rtcp_addr, &rem_conn_addr); pj_sockaddr_set_port(&rtcp_addr, (pj_uint16_t)rtcp_port); } } /* Find the default addresses in a=candidate attributes. */ for (i=0; iattr_count; ++i) { pj_ice_sess_cand cand; if (pj_strcmp(&rem_m->attr[i]->name, &STR_CANDIDATE)!=0) continue; status = parse_cand(tp_ice->base.name, tmp_pool, &rem_m->attr[i]->value, &cand); if (status != PJ_SUCCESS) { PJ_LOG(4,(tp_ice->base.name, "Error in parsing SDP candidate attribute '%.*s', " "candidate is ignored", (int)rem_m->attr[i]->value.slen, rem_m->attr[i]->value.ptr)); continue; } if (!comp1_found && cand.comp_id==COMP_RTP && pj_sockaddr_cmp(&rem_conn_addr, &cand.addr)==0) { comp1_found = PJ_TRUE; } else if (!comp2_found && cand.comp_id==COMP_RTCP && pj_sockaddr_cmp(&rtcp_addr, &cand.addr)==0) { comp2_found = PJ_TRUE; } if (cand.comp_id == COMP_RTCP) has_rtcp = PJ_TRUE; if (comp1_found && (comp2_found || tp_ice->comp_cnt==1)) break; } /* Check matched component count and ice_mismatch */ if (comp1_found && (tp_ice->comp_cnt==1 || !has_rtcp)) { sdp_state->match_comp_cnt = 1; sdp_state->ice_mismatch = PJ_FALSE; } else if (comp1_found && comp2_found) { sdp_state->match_comp_cnt = 2; sdp_state->ice_mismatch = PJ_FALSE; } else { sdp_state->match_comp_cnt = (tp_ice->comp_cnt > 1 && has_rtcp)? 2 : 1; sdp_state->ice_mismatch = PJ_TRUE; } /* Detect remote restarting session */ if (pj_ice_strans_has_sess(tp_ice->ice_st) && (pj_ice_strans_sess_is_running(tp_ice->ice_st) || pj_ice_strans_sess_is_complete(tp_ice->ice_st))) { pj_str_t rem_run_ufrag, rem_run_pwd; pj_ice_strans_get_ufrag_pwd(tp_ice->ice_st, NULL, NULL, &rem_run_ufrag, &rem_run_pwd); if (pj_strcmp(&ufrag_attr->value, &rem_run_ufrag) || pj_strcmp(&pwd_attr->value, &rem_run_pwd)) { /* Remote offers to restart ICE */ sdp_state->ice_restart = PJ_TRUE; } else { sdp_state->ice_restart = PJ_FALSE; } } else { sdp_state->ice_restart = PJ_FALSE; } /* Detect our role */ if (current_ice_role==PJ_ICE_SESS_ROLE_CONTROLLING) { sdp_state->local_role = PJ_ICE_SESS_ROLE_CONTROLLING; } else { if (pjmedia_sdp_attr_find(rem_sdp->attr_count, rem_sdp->attr, &STR_ICE_LITE, NULL) != NULL) { /* Remote is ICE Lite */ sdp_state->local_role = PJ_ICE_SESS_ROLE_CONTROLLING; } else { sdp_state->local_role = PJ_ICE_SESS_ROLE_CONTROLLED; } } PJ_LOG(4,(tp_ice->base.name, "Processing SDP: support ICE=%u, common comp_cnt=%u, " "ice_mismatch=%u, ice_restart=%u, local_role=%s", (sdp_state->match_comp_cnt != 0), sdp_state->match_comp_cnt, sdp_state->ice_mismatch, sdp_state->ice_restart, pj_ice_sess_role_name(sdp_state->local_role))); return PJ_SUCCESS; } /* Verify incoming offer and create initial answer */ static pj_status_t create_initial_answer(struct transport_ice *tp_ice, pj_pool_t *sdp_pool, pjmedia_sdp_session *loc_sdp, const pjmedia_sdp_session *rem_sdp, unsigned media_index) { const pjmedia_sdp_media *rem_m = rem_sdp->media[media_index]; pj_status_t status; /* Check if media is removed (just in case) */ if (rem_m->desc.port == 0) { return PJ_SUCCESS; } /* Verify the offer */ status = verify_ice_sdp(tp_ice, sdp_pool, rem_sdp, media_index, PJ_ICE_SESS_ROLE_CONTROLLED, &tp_ice->rem_offer_state); if (status != PJ_SUCCESS) { set_no_ice(tp_ice, "Invalid SDP offer", status); return status; } /* Does remote support ICE? */ if (tp_ice->rem_offer_state.match_comp_cnt==0) { set_no_ice(tp_ice, "No ICE found in SDP offer", PJ_SUCCESS); return PJ_SUCCESS; } /* ICE ice_mismatch? */ if (tp_ice->rem_offer_state.ice_mismatch) { set_no_ice(tp_ice, "ICE ice_mismatch in remote offer", PJ_SUCCESS); encode_ice_mismatch(sdp_pool, loc_sdp, media_index); return PJ_SUCCESS; } /* Encode ICE in SDP */ status = encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index, tp_ice->rem_offer_state.match_comp_cnt, PJ_FALSE); if (status != PJ_SUCCESS) { set_no_ice(tp_ice, "Error encoding SDP answer", status); return status; } return PJ_SUCCESS; } /* Create subsequent SDP offer */ static pj_status_t create_subsequent_offer(struct transport_ice *tp_ice, pj_pool_t *sdp_pool, pjmedia_sdp_session *loc_sdp, unsigned media_index) { unsigned comp_cnt; if (pj_ice_strans_has_sess(tp_ice->ice_st) == PJ_FALSE) { /* We don't have ICE */ return PJ_SUCCESS; } comp_cnt = pj_ice_strans_get_running_comp_cnt(tp_ice->ice_st); return encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index, comp_cnt, PJ_FALSE); } /* Create subsequent SDP answer */ static pj_status_t create_subsequent_answer(struct transport_ice *tp_ice, pj_pool_t *sdp_pool, pjmedia_sdp_session *loc_sdp, const pjmedia_sdp_session *rem_sdp, unsigned media_index) { pj_status_t status; /* We have a session */ status = verify_ice_sdp(tp_ice, sdp_pool, rem_sdp, media_index, PJ_ICE_SESS_ROLE_CONTROLLED, &tp_ice->rem_offer_state); if (status != PJ_SUCCESS) { /* Something wrong with the offer */ return status; } if (pj_ice_strans_has_sess(tp_ice->ice_st)) { /* * Received subsequent offer while we have ICE active. */ if (tp_ice->rem_offer_state.match_comp_cnt == 0) { /* Remote no longer offers ICE */ return PJ_SUCCESS; } if (tp_ice->rem_offer_state.ice_mismatch) { encode_ice_mismatch(sdp_pool, loc_sdp, media_index); return PJ_SUCCESS; } status = encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index, tp_ice->rem_offer_state.match_comp_cnt, tp_ice->rem_offer_state.ice_restart); if (status != PJ_SUCCESS) return status; /* Done */ } else { /* * Received subsequent offer while we DON'T have ICE active. */ if (tp_ice->rem_offer_state.match_comp_cnt == 0) { /* Remote does not support ICE */ return PJ_SUCCESS; } if (tp_ice->rem_offer_state.ice_mismatch) { encode_ice_mismatch(sdp_pool, loc_sdp, media_index); return PJ_SUCCESS; } /* Looks like now remote is offering ICE, so we need to create * ICE session now. */ status = pj_ice_strans_init_ice(tp_ice->ice_st, PJ_ICE_SESS_ROLE_CONTROLLED, NULL, NULL); if (status != PJ_SUCCESS) { /* Fail to create new ICE session */ return status; } status = encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index, tp_ice->rem_offer_state.match_comp_cnt, tp_ice->rem_offer_state.ice_restart); if (status != PJ_SUCCESS) return status; /* Done */ } return PJ_SUCCESS; } /* * For both UAC and UAS, pass in the SDP before sending it to remote. * This will add ICE attributes to the SDP. */ static pj_status_t transport_media_create(pjmedia_transport *tp, pj_pool_t *sdp_pool, unsigned options, const pjmedia_sdp_session *rem_sdp, unsigned media_index) { struct transport_ice *tp_ice = (struct transport_ice*)tp; pj_ice_sess_role ice_role; pj_status_t status; PJ_UNUSED_ARG(media_index); PJ_UNUSED_ARG(sdp_pool); tp_ice->media_option = options; tp_ice->oa_role = ROLE_NONE; tp_ice->initial_sdp = PJ_TRUE; /* Init ICE, the initial role is set now based on availability of * rem_sdp, but it will be checked again later. */ ice_role = (rem_sdp==NULL ? PJ_ICE_SESS_ROLE_CONTROLLING : PJ_ICE_SESS_ROLE_CONTROLLED); status = pj_ice_strans_init_ice(tp_ice->ice_st, ice_role, NULL, NULL); /* Done */ return status; } static pj_status_t transport_encode_sdp(pjmedia_transport *tp, pj_pool_t *sdp_pool, pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *rem_sdp, unsigned media_index) { struct transport_ice *tp_ice = (struct transport_ice*)tp; pj_status_t status; /* Validate media transport */ /* This transport only support RTP/AVP transport, unless if * transport checking is disabled */ if ((tp_ice->media_option & PJMEDIA_TPMED_NO_TRANSPORT_CHECKING) == 0) { pjmedia_sdp_media *loc_m, *rem_m; rem_m = rem_sdp? rem_sdp->media[media_index] : NULL; loc_m = sdp_local->media[media_index]; if (pj_stricmp(&loc_m->desc.transport, &STR_RTP_AVP) || (rem_m && pj_stricmp(&rem_m->desc.transport, &STR_RTP_AVP))) { pjmedia_sdp_media_deactivate(sdp_pool, loc_m); return PJMEDIA_SDP_EINPROTO; } } if (tp_ice->initial_sdp) { if (rem_sdp) { status = create_initial_answer(tp_ice, sdp_pool, sdp_local, rem_sdp, media_index); } else { status = create_initial_offer(tp_ice, sdp_pool, sdp_local, media_index); } } else { if (rem_sdp) { status = create_subsequent_answer(tp_ice, sdp_pool, sdp_local, rem_sdp, media_index); } else { status = create_subsequent_offer(tp_ice, sdp_pool, sdp_local, media_index); } } if (status==PJ_SUCCESS) { if (rem_sdp) tp_ice->oa_role = ROLE_ANSWERER; else tp_ice->oa_role = ROLE_OFFERER; } return status; } /* Start ICE session with the specified remote SDP */ static pj_status_t start_ice(struct transport_ice *tp_ice, pj_pool_t *tmp_pool, const pjmedia_sdp_session *rem_sdp, unsigned media_index) { pjmedia_sdp_media *rem_m = rem_sdp->media[media_index]; const pjmedia_sdp_attr *ufrag_attr, *pwd_attr; pj_ice_sess_cand *cand; unsigned i, cand_cnt; pj_status_t status; get_ice_attr(rem_sdp, rem_m, &ufrag_attr, &pwd_attr); /* Allocate candidate array */ cand = (pj_ice_sess_cand*) pj_pool_calloc(tmp_pool, PJ_ICE_MAX_CAND, sizeof(pj_ice_sess_cand)); /* Get all candidates in the media */ cand_cnt = 0; for (i=0; iattr_count && cand_cnt < PJ_ICE_MAX_CAND; ++i) { pjmedia_sdp_attr *attr; attr = rem_m->attr[i]; if (pj_strcmp(&attr->name, &STR_CANDIDATE)!=0) continue; /* Parse candidate */ status = parse_cand(tp_ice->base.name, tmp_pool, &attr->value, &cand[cand_cnt]); if (status != PJ_SUCCESS) { PJ_LOG(4,(tp_ice->base.name, "Error in parsing SDP candidate attribute '%.*s', " "candidate is ignored", (int)attr->value.slen, attr->value.ptr)); continue; } cand_cnt++; } /* Start ICE */ return pj_ice_strans_start_ice(tp_ice->ice_st, &ufrag_attr->value, &pwd_attr->value, cand_cnt, cand); } /* * Start ICE checks when both offer and answer have been negotiated * by SDP negotiator. */ static pj_status_t transport_media_start(pjmedia_transport *tp, pj_pool_t *tmp_pool, const pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *rem_sdp, unsigned media_index) { struct transport_ice *tp_ice = (struct transport_ice*)tp; pjmedia_sdp_media *rem_m; enum oa_role current_oa_role; pj_bool_t initial_oa; pj_status_t status; PJ_ASSERT_RETURN(tp && tmp_pool && rem_sdp, PJ_EINVAL); PJ_ASSERT_RETURN(media_index < rem_sdp->media_count, PJ_EINVAL); rem_m = rem_sdp->media[media_index]; initial_oa = tp_ice->initial_sdp; current_oa_role = tp_ice->oa_role; /* SDP has been negotiated */ tp_ice->initial_sdp = PJ_FALSE; tp_ice->oa_role = ROLE_NONE; /* Nothing to do if we don't have ICE session */ if (pj_ice_strans_has_sess(tp_ice->ice_st) == PJ_FALSE) { return PJ_SUCCESS; } /* Special case for Session Timer. The re-INVITE for session refresh * doesn't call transport_encode_sdp(), causing current_oa_role to * be set to ROLE_NONE. This is a workaround. */ if (current_oa_role == ROLE_NONE) { current_oa_role = ROLE_OFFERER; } /* Processing depends on the offer/answer role */ if (current_oa_role == ROLE_OFFERER) { /* * We are offerer. So this will be the first time we see the * remote's SDP. */ struct sdp_state answer_state; /* Verify the answer */ status = verify_ice_sdp(tp_ice, tmp_pool, rem_sdp, media_index, PJ_ICE_SESS_ROLE_CONTROLLING, &answer_state); if (status != PJ_SUCCESS) { /* Something wrong in the SDP answer */ set_no_ice(tp_ice, "Invalid remote SDP answer", status); return status; } /* Does it have ICE? */ if (answer_state.match_comp_cnt == 0) { /* Remote doesn't support ICE */ set_no_ice(tp_ice, "Remote answer doesn't support ICE", PJ_SUCCESS); return PJ_SUCCESS; } /* Check if remote has reported ice-mismatch */ if (pjmedia_sdp_attr_find(rem_m->attr_count, rem_m->attr, &STR_ICE_MISMATCH, NULL) != NULL) { /* Remote has reported ice-mismatch */ set_no_ice(tp_ice, "Remote answer contains 'ice-mismatch' attribute", PJ_SUCCESS); return PJ_SUCCESS; } /* Check if remote has indicated a restart */ if (answer_state.ice_restart) { PJ_LOG(2,(tp_ice->base.name, "Warning: remote has signalled ICE restart in SDP " "answer which is disallowed. Remote ICE negotiation" " may fail.")); } /* Check if the answer itself is mismatched */ if (answer_state.ice_mismatch) { /* This happens either when a B2BUA modified remote answer but * strangely didn't modify our offer, or remote is not capable * of detecting mismatch in our offer (it didn't put * 'ice-mismatch' attribute in the answer). */ PJ_LOG(2,(tp_ice->base.name, "Warning: remote answer mismatch, but it does not " "reject our offer with 'ice-mismatch'. ICE negotiation " "may fail")); } /* Do nothing if ICE is complete or running */ if (pj_ice_strans_sess_is_running(tp_ice->ice_st)) { PJ_LOG(4,(tp_ice->base.name, "Ignored offer/answer because ICE is running")); return PJ_SUCCESS; } if (pj_ice_strans_sess_is_complete(tp_ice->ice_st)) { PJ_LOG(4,(tp_ice->base.name, "ICE session unchanged")); return PJ_SUCCESS; } /* Start ICE */ } else { /* * We are answerer. We've seen and negotiated remote's SDP * before, and the result is in "rem_offer_state". */ const pjmedia_sdp_attr *ufrag_attr, *pwd_attr; /* Check for ICE in remote offer */ if (tp_ice->rem_offer_state.match_comp_cnt == 0) { /* No ICE attribute present */ set_no_ice(tp_ice, "Remote no longer offers ICE", PJ_SUCCESS); return PJ_SUCCESS; } /* Check for ICE ice_mismatch condition in the offer */ if (tp_ice->rem_offer_state.ice_mismatch) { set_no_ice(tp_ice, "Remote offer mismatch: ", PJNATH_EICEMISMATCH); return PJ_SUCCESS; } /* If ICE is complete and remote doesn't request restart, * then leave the session as is. */ if (!initial_oa && tp_ice->rem_offer_state.ice_restart == PJ_FALSE) { /* Remote has not requested ICE restart, so session is * unchanged. */ PJ_LOG(4,(tp_ice->base.name, "ICE session unchanged")); return PJ_SUCCESS; } /* Either remote has requested ICE restart or this is our * first answer. */ /* Stop ICE */ if (!initial_oa) { set_no_ice(tp_ice, "restarting by remote request..", PJ_SUCCESS); /* We have put new ICE ufrag and pwd in the answer. Now * create a new ICE session with that ufrag/pwd pair. */ get_ice_attr(sdp_local, sdp_local->media[media_index], &ufrag_attr, &pwd_attr); status = pj_ice_strans_init_ice(tp_ice->ice_st, tp_ice->rem_offer_state.local_role, &ufrag_attr->value, &pwd_attr->value); if (status != PJ_SUCCESS) { PJ_LOG(1,(tp_ice->base.name, "ICE re-initialization failed (status=%d)!", status)); return status; } } /* Ticket #977: Update role if turns out we're supposed to be the * Controlling agent (e.g. when talking to ice-lite peer). */ if (tp_ice->rem_offer_state.local_role==PJ_ICE_SESS_ROLE_CONTROLLING && pj_ice_strans_has_sess(tp_ice->ice_st)) { pj_ice_strans_change_role(tp_ice->ice_st, PJ_ICE_SESS_ROLE_CONTROLLING); } /* start ICE */ } /* Now start ICE */ status = start_ice(tp_ice, tmp_pool, rem_sdp, media_index); if (status != PJ_SUCCESS) { PJ_LOG(1,(tp_ice->base.name, "ICE restart failed (status=%d)!", status)); return status; } /* Done */ tp_ice->use_ice = PJ_TRUE; return PJ_SUCCESS; } static pj_status_t transport_media_stop(pjmedia_transport *tp) { struct transport_ice *tp_ice = (struct transport_ice*)tp; set_no_ice(tp_ice, "media stop requested", PJ_SUCCESS); return PJ_SUCCESS; } static pj_status_t transport_get_info(pjmedia_transport *tp, pjmedia_transport_info *info) { struct transport_ice *tp_ice = (struct transport_ice*)tp; pj_ice_sess_cand cand; pj_status_t status; pj_bzero(&info->sock_info, sizeof(info->sock_info)); info->sock_info.rtp_sock = info->sock_info.rtcp_sock = PJ_INVALID_SOCKET; /* Get RTP default address */ status = pj_ice_strans_get_def_cand(tp_ice->ice_st, 1, &cand); if (status != PJ_SUCCESS) return status; pj_sockaddr_cp(&info->sock_info.rtp_addr_name, &cand.base_addr); /* Get RTCP default address */ if (tp_ice->comp_cnt > 1) { status = pj_ice_strans_get_def_cand(tp_ice->ice_st, 2, &cand); if (status != PJ_SUCCESS) return status; pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, &cand.base_addr); } /* Set remote address originating RTP & RTCP if this transport has * ICE activated or received any packets. */ if (tp_ice->use_ice || tp_ice->rtp_src_cnt) { info->src_rtp_name = tp_ice->rtp_src_addr; } if (tp_ice->use_ice || tp_ice->rtcp_src_cnt) { info->src_rtcp_name = tp_ice->rtcp_src_addr; } /* Fill up transport specific info */ if (info->specific_info_cnt < PJ_ARRAY_SIZE(info->spc_info)) { pjmedia_transport_specific_info *tsi; pjmedia_ice_transport_info *ii; unsigned i; pj_assert(sizeof(*ii) <= sizeof(tsi->buffer)); tsi = &info->spc_info[info->specific_info_cnt++]; tsi->type = PJMEDIA_TRANSPORT_TYPE_ICE; tsi->cbsize = sizeof(*ii); ii = (pjmedia_ice_transport_info*) tsi->buffer; pj_bzero(ii, sizeof(*ii)); ii->active = tp_ice->use_ice; if (pj_ice_strans_has_sess(tp_ice->ice_st)) ii->role = pj_ice_strans_get_role(tp_ice->ice_st); else ii->role = PJ_ICE_SESS_ROLE_UNKNOWN; ii->sess_state = pj_ice_strans_get_state(tp_ice->ice_st); ii->comp_cnt = pj_ice_strans_get_running_comp_cnt(tp_ice->ice_st); for (i=1; i<=ii->comp_cnt && i<=PJ_ARRAY_SIZE(ii->comp); ++i) { const pj_ice_sess_check *chk; chk = pj_ice_strans_get_valid_pair(tp_ice->ice_st, i); if (chk) { ii->comp[i-1].lcand_type = chk->lcand->type; pj_sockaddr_cp(&ii->comp[i-1].lcand_addr, &chk->lcand->addr); ii->comp[i-1].rcand_type = chk->rcand->type; pj_sockaddr_cp(&ii->comp[i-1].rcand_addr, &chk->rcand->addr); } } } return PJ_SUCCESS; } static pj_status_t transport_attach (pjmedia_transport *tp, void *stream, const pj_sockaddr_t *rem_addr, const pj_sockaddr_t *rem_rtcp, unsigned addr_len, void (*rtp_cb)(void*, void*, pj_ssize_t), void (*rtcp_cb)(void*, void*, pj_ssize_t)) { struct transport_ice *tp_ice = (struct transport_ice*)tp; tp_ice->stream = stream; tp_ice->rtp_cb = rtp_cb; tp_ice->rtcp_cb = rtcp_cb; pj_memcpy(&tp_ice->remote_rtp, rem_addr, addr_len); pj_memcpy(&tp_ice->remote_rtcp, rem_rtcp, addr_len); tp_ice->addr_len = addr_len; /* Init source RTP & RTCP addresses and counter */ tp_ice->rtp_src_addr = tp_ice->remote_rtp; tp_ice->rtcp_src_addr = tp_ice->remote_rtcp; tp_ice->rtp_src_cnt = 0; tp_ice->rtcp_src_cnt = 0; return PJ_SUCCESS; } static void transport_detach(pjmedia_transport *tp, void *strm) { struct transport_ice *tp_ice = (struct transport_ice*)tp; /* TODO: need to solve ticket #460 here */ tp_ice->rtp_cb = NULL; tp_ice->rtcp_cb = NULL; tp_ice->stream = NULL; PJ_UNUSED_ARG(strm); } static pj_status_t transport_send_rtp(pjmedia_transport *tp, const void *pkt, pj_size_t size) { struct transport_ice *tp_ice = (struct transport_ice*)tp; /* Simulate packet lost on TX direction */ if (tp_ice->tx_drop_pct) { if ((pj_rand() % 100) <= (int)tp_ice->tx_drop_pct) { PJ_LOG(5,(tp_ice->base.name, "TX RTP packet dropped because of pkt lost " "simulation")); return PJ_SUCCESS; } } return pj_ice_strans_sendto(tp_ice->ice_st, 1, pkt, size, &tp_ice->remote_rtp, tp_ice->addr_len); } static pj_status_t transport_send_rtcp(pjmedia_transport *tp, const void *pkt, pj_size_t size) { return transport_send_rtcp2(tp, NULL, 0, pkt, size); } static pj_status_t transport_send_rtcp2(pjmedia_transport *tp, const pj_sockaddr_t *addr, unsigned addr_len, const void *pkt, pj_size_t size) { struct transport_ice *tp_ice = (struct transport_ice*)tp; if (tp_ice->comp_cnt > 1) { if (addr == NULL) { addr = &tp_ice->remote_rtcp; addr_len = pj_sockaddr_get_len(addr); } return pj_ice_strans_sendto(tp_ice->ice_st, 2, pkt, size, addr, addr_len); } else { return PJ_SUCCESS; } } static void ice_on_rx_data(pj_ice_strans *ice_st, unsigned comp_id, void *pkt, pj_size_t size, const pj_sockaddr_t *src_addr, unsigned src_addr_len) { struct transport_ice *tp_ice; pj_bool_t discard = PJ_FALSE; tp_ice = (struct transport_ice*) pj_ice_strans_get_user_data(ice_st); if (comp_id==1 && tp_ice->rtp_cb) { /* Simulate packet lost on RX direction */ if (tp_ice->rx_drop_pct) { if ((pj_rand() % 100) <= (int)tp_ice->rx_drop_pct) { PJ_LOG(5,(tp_ice->base.name, "RX RTP packet dropped because of pkt lost " "simulation")); return; } } /* See if source address of RTP packet is different than the * configured address, and switch RTP remote address to * source packet address after several consecutive packets * have been received. */ if (!tp_ice->use_ice) { pj_bool_t enable_switch = ((tp_ice->options & PJMEDIA_ICE_NO_SRC_ADDR_CHECKING)==0); if (!enable_switch || pj_sockaddr_cmp(&tp_ice->remote_rtp, src_addr) == 0) { /* Don't switch while we're receiving from remote_rtp */ tp_ice->rtp_src_cnt = 0; } else { ++tp_ice->rtp_src_cnt; /* Check if the source address is recognized. */ if (pj_sockaddr_cmp(src_addr, &tp_ice->rtp_src_addr) != 0) { /* Remember the new source address. */ pj_sockaddr_cp(&tp_ice->rtp_src_addr, src_addr); /* Reset counter */ tp_ice->rtp_src_cnt = 0; discard = PJ_TRUE; } if (tp_ice->rtp_src_cnt < PJMEDIA_RTP_NAT_PROBATION_CNT) { discard = PJ_TRUE; } else { char addr_text[80]; /* Set remote RTP address to source address */ pj_sockaddr_cp(&tp_ice->remote_rtp, &tp_ice->rtp_src_addr); tp_ice->addr_len = pj_sockaddr_get_len(&tp_ice->remote_rtp); /* Reset counter */ tp_ice->rtp_src_cnt = 0; PJ_LOG(4,(tp_ice->base.name, "Remote RTP address switched to %s", pj_sockaddr_print(&tp_ice->remote_rtp, addr_text, sizeof(addr_text), 3))); /* Also update remote RTCP address if actual RTCP source * address is not heard yet. */ if (!pj_sockaddr_has_addr(&tp_ice->rtcp_src_addr)) { pj_uint16_t port; pj_sockaddr_cp(&tp_ice->remote_rtcp, &tp_ice->remote_rtp); port = (pj_uint16_t) (pj_sockaddr_get_port(&tp_ice->remote_rtp)+1); pj_sockaddr_set_port(&tp_ice->remote_rtcp, port); PJ_LOG(4,(tp_ice->base.name, "Remote RTCP address switched to predicted " "address %s", pj_sockaddr_print(&tp_ice->remote_rtcp, addr_text, sizeof(addr_text), 3))); } } } } if (!discard) (*tp_ice->rtp_cb)(tp_ice->stream, pkt, size); } else if (comp_id==2 && tp_ice->rtcp_cb) { /* Check if RTCP source address is the same as the configured * remote address, and switch the address when they are * different. */ if (!tp_ice->use_ice && (tp_ice->options & PJMEDIA_ICE_NO_SRC_ADDR_CHECKING)==0) { if (pj_sockaddr_cmp(&tp_ice->remote_rtcp, src_addr) == 0) { tp_ice->rtcp_src_cnt = 0; } else { char addr_text[80]; ++tp_ice->rtcp_src_cnt; if (tp_ice->rtcp_src_cnt < PJMEDIA_RTCP_NAT_PROBATION_CNT) { discard = PJ_TRUE; } else { tp_ice->rtcp_src_cnt = 0; pj_sockaddr_cp(&tp_ice->rtcp_src_addr, src_addr); pj_sockaddr_cp(&tp_ice->remote_rtcp, src_addr); pj_assert(tp_ice->addr_len==pj_sockaddr_get_len(src_addr)); PJ_LOG(4,(tp_ice->base.name, "Remote RTCP address switched to %s", pj_sockaddr_print(&tp_ice->remote_rtcp, addr_text, sizeof(addr_text), 3))); } } } if (!discard) (*tp_ice->rtcp_cb)(tp_ice->stream, pkt, size); } PJ_UNUSED_ARG(src_addr_len); } static void ice_on_ice_complete(pj_ice_strans *ice_st, pj_ice_strans_op op, pj_status_t result) { struct transport_ice *tp_ice; tp_ice = (struct transport_ice*) pj_ice_strans_get_user_data(ice_st); /* Notify application */ if (tp_ice->cb.on_ice_complete) (*tp_ice->cb.on_ice_complete)(&tp_ice->base, op, result); } static void ice_on_ice_state(pj_ice_strans *ice_st, pj_ice_strans_state prev, pj_ice_strans_state curr) { struct transport_ice *tp_ice; tp_ice = (struct transport_ice*) pj_ice_strans_get_user_data(ice_st); /* Notify application */ if (tp_ice->cb.on_ice_state) (*tp_ice->cb.on_ice_state)(&tp_ice->base, prev, curr); } /* Simulate lost */ static pj_status_t transport_simulate_lost(pjmedia_transport *tp, pjmedia_dir dir, unsigned pct_lost) { struct transport_ice *ice = (struct transport_ice*) tp; PJ_ASSERT_RETURN(tp && pct_lost <= 100, PJ_EINVAL); if (dir & PJMEDIA_DIR_ENCODING) ice->tx_drop_pct = pct_lost; if (dir & PJMEDIA_DIR_DECODING) ice->rx_drop_pct = pct_lost; return PJ_SUCCESS; } /* * Destroy ICE media transport. */ static pj_status_t transport_destroy(pjmedia_transport *tp) { struct transport_ice *tp_ice = (struct transport_ice*)tp; if (tp_ice->ice_st) { pj_ice_strans_destroy(tp_ice->ice_st); tp_ice->ice_st = NULL; } if (tp_ice->pool) { pj_pool_t *pool = tp_ice->pool; tp_ice->pool = NULL; pj_pool_release(pool); } return PJ_SUCCESS; } diff --git a/deps/pjsip/pjmedia/src/pjmedia/transport_srtp.c b/deps/pjsip/pjmedia/src/pjmedia/transport_srtp.c index df639fa6..306d45e5 100644 --- a/deps/pjsip/pjmedia/src/pjmedia/transport_srtp.c +++ b/deps/pjsip/pjmedia/src/pjmedia/transport_srtp.c @@ -1,1779 +1,1779 @@ -/* $Id: transport_srtp.c 4366 2013-02-21 20:41:31Z ming $ */ +/* $Id: transport_srtp.c 5219 2015-12-30 03:35:53Z ming $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) #if defined(PJ_HAS_SSL_SOCK) && (PJ_HAS_SSL_SOCK != 0) # include /* Suppress compile warning of OpenSSL deprecation (OpenSSL is deprecated * since MacOSX 10.7). */ #if defined(PJ_DARWINOS) && PJ_DARWINOS==1 # pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif #endif #if defined(PJMEDIA_EXTERNAL_SRTP) && (PJMEDIA_EXTERNAL_SRTP != 0) # include # include #else # include #endif #define THIS_FILE "transport_srtp.c" /* Maximum size of outgoing packet */ #define MAX_RTP_BUFFER_LEN PJMEDIA_MAX_MTU #define MAX_RTCP_BUFFER_LEN PJMEDIA_MAX_MTU /* Maximum SRTP crypto key length */ #define MAX_KEY_LEN 128 /* Initial value of probation counter. When probation counter > 0, * it means SRTP is in probation state, and it may restart when * srtp_unprotect() returns err_status_replay_* */ #define PROBATION_CNT_INIT 100 #define DEACTIVATE_MEDIA(pool, m) pjmedia_sdp_media_deactivate(pool, m) static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 }; static const pj_str_t ID_RTP_SAVP = { "RTP/SAVP", 8 }; static const pj_str_t ID_INACTIVE = { "inactive", 8 }; static const pj_str_t ID_CRYPTO = { "crypto", 6 }; typedef struct crypto_suite { char *name; cipher_type_id_t cipher_type; unsigned cipher_key_len; auth_type_id_t auth_type; unsigned auth_key_len; unsigned srtp_auth_tag_len; unsigned srtcp_auth_tag_len; sec_serv_t service; } crypto_suite; /* Crypto suites as defined on RFC 4568 */ static crypto_suite crypto_suites[] = { /* plain RTP/RTCP (no cipher & no auth) */ {"NULL", NULL_CIPHER, 0, NULL_AUTH, 0, 0, 0, sec_serv_none}, /* cipher AES_CM, auth HMAC_SHA1, auth tag len = 10 octets */ {"AES_CM_128_HMAC_SHA1_80", AES_128_ICM, 30, HMAC_SHA1, 20, 10, 10, sec_serv_conf_and_auth}, /* cipher AES_CM, auth HMAC_SHA1, auth tag len = 4 octets */ {"AES_CM_128_HMAC_SHA1_32", AES_128_ICM, 30, HMAC_SHA1, 20, 4, 10, sec_serv_conf_and_auth}, /* * F8_128_HMAC_SHA1_8 not supported by libsrtp? * {"F8_128_HMAC_SHA1_8", NULL_CIPHER, 0, NULL_AUTH, 0, 0, 0, sec_serv_none} */ }; typedef struct transport_srtp { pjmedia_transport base; /**< Base transport interface. */ pj_pool_t *pool; /**< Pool for transport SRTP. */ pj_lock_t *mutex; /**< Mutex for libsrtp contexts.*/ char rtp_tx_buffer[MAX_RTP_BUFFER_LEN]; char rtcp_tx_buffer[MAX_RTCP_BUFFER_LEN]; pjmedia_srtp_setting setting; unsigned media_option; /* SRTP policy */ pj_bool_t session_inited; pj_bool_t offerer_side; pj_bool_t bypass_srtp; char tx_key[MAX_KEY_LEN]; char rx_key[MAX_KEY_LEN]; pjmedia_srtp_crypto tx_policy; pjmedia_srtp_crypto rx_policy; /* Temporary policy for negotiation */ pjmedia_srtp_crypto tx_policy_neg; pjmedia_srtp_crypto rx_policy_neg; /* libSRTP contexts */ srtp_t srtp_tx_ctx; srtp_t srtp_rx_ctx; /* Stream information */ void *user_data; void (*rtp_cb)( void *user_data, void *pkt, pj_ssize_t size); void (*rtcp_cb)(void *user_data, void *pkt, pj_ssize_t size); /* Transport information */ pjmedia_transport *member_tp; /**< Underlying transport. */ /* SRTP usage policy of peer. This field is updated when media is starting. * This is useful when SRTP is in optional mode and peer is using mandatory * mode, so when local is about to reinvite/update, it should offer * RTP/SAVP instead of offering RTP/AVP. */ pjmedia_srtp_use peer_use; /* When probation counter > 0, it means SRTP is in probation state, * and it may restart when srtp_unprotect() returns err_status_replay_* */ unsigned probation_cnt; } transport_srtp; /* * This callback is called by transport when incoming rtp is received */ static void srtp_rtp_cb( void *user_data, void *pkt, pj_ssize_t size); /* * This callback is called by transport when incoming rtcp is received */ static void srtp_rtcp_cb( void *user_data, void *pkt, pj_ssize_t size); /* * These are media transport operations. */ static pj_status_t transport_get_info (pjmedia_transport *tp, pjmedia_transport_info *info); static pj_status_t transport_attach (pjmedia_transport *tp, void *user_data, const pj_sockaddr_t *rem_addr, const pj_sockaddr_t *rem_rtcp, unsigned addr_len, void (*rtp_cb)(void*, void*, pj_ssize_t), void (*rtcp_cb)(void*, void*, pj_ssize_t)); static void transport_detach (pjmedia_transport *tp, void *strm); static pj_status_t transport_send_rtp( pjmedia_transport *tp, const void *pkt, pj_size_t size); static pj_status_t transport_send_rtcp(pjmedia_transport *tp, const void *pkt, pj_size_t size); static pj_status_t transport_send_rtcp2(pjmedia_transport *tp, const pj_sockaddr_t *addr, unsigned addr_len, const void *pkt, pj_size_t size); static pj_status_t transport_media_create(pjmedia_transport *tp, pj_pool_t *sdp_pool, unsigned options, const pjmedia_sdp_session *sdp_remote, unsigned media_index); static pj_status_t transport_encode_sdp(pjmedia_transport *tp, pj_pool_t *sdp_pool, pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *sdp_remote, unsigned media_index); static pj_status_t transport_media_start (pjmedia_transport *tp, pj_pool_t *pool, const pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *sdp_remote, unsigned media_index); static pj_status_t transport_media_stop(pjmedia_transport *tp); static pj_status_t transport_simulate_lost(pjmedia_transport *tp, pjmedia_dir dir, unsigned pct_lost); static pj_status_t transport_destroy (pjmedia_transport *tp); static pjmedia_transport_op transport_srtp_op = { &transport_get_info, &transport_attach, &transport_detach, &transport_send_rtp, &transport_send_rtcp, &transport_send_rtcp2, &transport_media_create, &transport_encode_sdp, &transport_media_start, &transport_media_stop, &transport_simulate_lost, &transport_destroy }; /* This function may also be used by other module, e.g: pjmedia/errno.c, * it should have C compatible declaration. */ PJ_BEGIN_DECL const char* get_libsrtp_errstr(int err); PJ_END_DECL const char* get_libsrtp_errstr(int err) { #if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) static char *liberr[] = { "ok", /* err_status_ok = 0 */ "unspecified failure", /* err_status_fail = 1 */ "unsupported parameter", /* err_status_bad_param = 2 */ "couldn't allocate memory", /* err_status_alloc_fail = 3 */ "couldn't deallocate properly", /* err_status_dealloc_fail = 4 */ "couldn't initialize", /* err_status_init_fail = 5 */ "can't process as much data as requested", /* err_status_terminus = 6 */ "authentication failure", /* err_status_auth_fail = 7 */ "cipher failure", /* err_status_cipher_fail = 8 */ "replay check failed (bad index)", /* err_status_replay_fail = 9 */ "replay check failed (index too old)", /* err_status_replay_old = 10 */ "algorithm failed test routine", /* err_status_algo_fail = 11 */ "unsupported operation", /* err_status_no_such_op = 12 */ "no appropriate context found", /* err_status_no_ctx = 13 */ "unable to perform desired validation", /* err_status_cant_check = 14 */ "can't use key any more", /* err_status_key_expired = 15 */ "error in use of socket", /* err_status_socket_err = 16 */ "error in use POSIX signals", /* err_status_signal_err = 17 */ "nonce check failed", /* err_status_nonce_bad = 18 */ "couldn't read data", /* err_status_read_fail = 19 */ "couldn't write data", /* err_status_write_fail = 20 */ "error pasring data", /* err_status_parse_err = 21 */ "error encoding data", /* err_status_encode_err = 22 */ "error while using semaphores", /* err_status_semaphore_err = 23 */ "error while using pfkey" /* err_status_pfkey_err = 24 */ }; if (err >= 0 && err < (int)PJ_ARRAY_SIZE(liberr)) { return liberr[err]; } else { static char msg[32]; pj_ansi_snprintf(msg, sizeof(msg), "Unknown libsrtp error %d", err); return msg; } #else static char msg[32]; pj_ansi_snprintf(msg, sizeof(msg), "libsrtp error %d", err); return msg; #endif } static pj_bool_t libsrtp_initialized; static void pjmedia_srtp_deinit_lib(pjmedia_endpt *endpt); PJ_DEF(pj_status_t) pjmedia_srtp_init_lib(pjmedia_endpt *endpt) { #if PJMEDIA_LIBSRTP_AUTO_INIT_DEINIT if (libsrtp_initialized == PJ_FALSE) { err_status_t err; err = srtp_init(); if (err != err_status_ok) { PJ_LOG(4, (THIS_FILE, "Failed to initialize libsrtp: %s", get_libsrtp_errstr(err))); return PJMEDIA_ERRNO_FROM_LIBSRTP(err); } if (pjmedia_endpt_atexit(endpt, pjmedia_srtp_deinit_lib) != PJ_SUCCESS) { /* There will be memory leak when it fails to schedule libsrtp * deinitialization, however the memory leak could be harmless, * since in modern OS's memory used by an application is released * when the application terminates. */ PJ_LOG(4, (THIS_FILE, "Failed to register libsrtp deinit.")); } libsrtp_initialized = PJ_TRUE; } #else PJ_UNUSED_ARG(endpt); #endif return PJ_SUCCESS; } static void pjmedia_srtp_deinit_lib(pjmedia_endpt *endpt) { err_status_t err; /* Note that currently this SRTP init/deinit is not equipped with * reference counter, it should be safe as normally there is only * one single instance of media endpoint and even if it isn't, the * pjmedia_transport_srtp_create() will invoke SRTP init (the only * drawback should be the delay described by #788). */ PJ_UNUSED_ARG(endpt); #if defined(PJMEDIA_EXTERNAL_SRTP) && (PJMEDIA_EXTERNAL_SRTP != 0) # if defined(PJMEDIA_SRTP_HAS_DEINIT) && PJMEDIA_SRTP_HAS_DEINIT!=0 err = srtp_deinit(); # elif defined(PJMEDIA_SRTP_HAS_SHUTDOWN) && PJMEDIA_SRTP_HAS_SHUTDOWN!=0 err = srtp_shutdown(); # else err = err_status_ok; # endif #else err = srtp_deinit(); #endif if (err != err_status_ok) { PJ_LOG(4, (THIS_FILE, "Failed to deinitialize libsrtp: %s", get_libsrtp_errstr(err))); } libsrtp_initialized = PJ_FALSE; } static int get_crypto_idx(const pj_str_t* crypto_name) { int i; int cs_cnt = sizeof(crypto_suites)/sizeof(crypto_suites[0]); /* treat unspecified crypto_name as crypto 'NULL' */ if (crypto_name->slen == 0) return 0; for (i=0; ikey, &c2->key); if (r != 0) return r; r = pj_stricmp(&c1->name, &c2->name); if (r != 0) return r; return (c1->flags != c2->flags); } static pj_bool_t srtp_crypto_empty(const pjmedia_srtp_crypto* c) { return (c->name.slen==0 || c->key.slen==0); } PJ_DEF(void) pjmedia_srtp_setting_default(pjmedia_srtp_setting *opt) { unsigned i; pj_assert(opt); pj_bzero(opt, sizeof(pjmedia_srtp_setting)); opt->close_member_tp = PJ_TRUE; opt->use = PJMEDIA_SRTP_OPTIONAL; /* Copy default crypto-suites, but skip crypto 'NULL' */ opt->crypto_count = sizeof(crypto_suites)/sizeof(crypto_suites[0]) - 1; for (i=0; icrypto_count; ++i) opt->crypto[i].name = pj_str(crypto_suites[i+1].name); } /* * Create an SRTP media transport. */ PJ_DEF(pj_status_t) pjmedia_transport_srtp_create( pjmedia_endpt *endpt, pjmedia_transport *tp, const pjmedia_srtp_setting *opt, pjmedia_transport **p_tp) { pj_pool_t *pool; transport_srtp *srtp; pj_status_t status; unsigned i; PJ_ASSERT_RETURN(endpt && tp && p_tp, PJ_EINVAL); /* Check crypto availability */ if (opt && opt->crypto_count == 0 && opt->use == PJMEDIA_SRTP_MANDATORY) return PJMEDIA_SRTP_ESDPREQCRYPTO; /* Check crypto */ if (opt && opt->use != PJMEDIA_SRTP_DISABLED) { for (i=0; i < opt->crypto_count; ++i) { int cs_idx = get_crypto_idx(&opt->crypto[i].name); /* check crypto name */ if (cs_idx == -1) return PJMEDIA_SRTP_ENOTSUPCRYPTO; /* check key length */ if (opt->crypto[i].key.slen && opt->crypto[i].key.slen < (pj_ssize_t)crypto_suites[cs_idx].cipher_key_len) return PJMEDIA_SRTP_EINKEYLEN; } } /* Init libsrtp. */ status = pjmedia_srtp_init_lib(endpt); if (status != PJ_SUCCESS) return status; pool = pjmedia_endpt_create_pool(endpt, "srtp%p", 1000, 1000); srtp = PJ_POOL_ZALLOC_T(pool, transport_srtp); srtp->pool = pool; srtp->session_inited = PJ_FALSE; srtp->bypass_srtp = PJ_FALSE; srtp->probation_cnt = PROBATION_CNT_INIT; if (opt) { srtp->setting = *opt; if (opt->use == PJMEDIA_SRTP_DISABLED) srtp->setting.crypto_count = 0; for (i=0; i < srtp->setting.crypto_count; ++i) { int cs_idx = get_crypto_idx(&opt->crypto[i].name); pj_str_t tmp_key = opt->crypto[i].key; /* re-set crypto */ srtp->setting.crypto[i].name = pj_str(crypto_suites[cs_idx].name); /* cut key length */ if (tmp_key.slen) tmp_key.slen = crypto_suites[cs_idx].cipher_key_len; pj_strdup(pool, &srtp->setting.crypto[i].key, &tmp_key); } } else { pjmedia_srtp_setting_default(&srtp->setting); } status = pj_lock_create_recursive_mutex(pool, pool->obj_name, &srtp->mutex); if (status != PJ_SUCCESS) { pj_pool_release(pool); return status; } /* Initialize base pjmedia_transport */ pj_memcpy(srtp->base.name, pool->obj_name, PJ_MAX_OBJ_NAME); if (tp) srtp->base.type = tp->type; else srtp->base.type = PJMEDIA_TRANSPORT_TYPE_UDP; srtp->base.op = &transport_srtp_op; /* Set underlying transport */ srtp->member_tp = tp; /* Initialize peer's SRTP usage mode. */ srtp->peer_use = srtp->setting.use; /* Done */ *p_tp = &srtp->base; return PJ_SUCCESS; } /* * Initialize and start SRTP session with the given parameters. */ PJ_DEF(pj_status_t) pjmedia_transport_srtp_start( pjmedia_transport *tp, const pjmedia_srtp_crypto *tx, const pjmedia_srtp_crypto *rx) { transport_srtp *srtp = (transport_srtp*) tp; srtp_policy_t tx_; srtp_policy_t rx_; err_status_t err; int cr_tx_idx = 0; int au_tx_idx = 0; int cr_rx_idx = 0; int au_rx_idx = 0; pj_status_t status = PJ_SUCCESS; PJ_ASSERT_RETURN(tp && tx && rx, PJ_EINVAL); pj_lock_acquire(srtp->mutex); if (srtp->session_inited) { pjmedia_transport_srtp_stop(tp); } /* Get encryption and authentication method */ cr_tx_idx = au_tx_idx = get_crypto_idx(&tx->name); if (tx->flags & PJMEDIA_SRTP_NO_ENCRYPTION) cr_tx_idx = 0; if (tx->flags & PJMEDIA_SRTP_NO_AUTHENTICATION) au_tx_idx = 0; cr_rx_idx = au_rx_idx = get_crypto_idx(&rx->name); if (rx->flags & PJMEDIA_SRTP_NO_ENCRYPTION) cr_rx_idx = 0; if (rx->flags & PJMEDIA_SRTP_NO_AUTHENTICATION) au_rx_idx = 0; /* Check whether the crypto-suite requested is supported */ if (cr_tx_idx == -1 || cr_rx_idx == -1 || au_tx_idx == -1 || au_rx_idx == -1) { status = PJMEDIA_SRTP_ENOTSUPCRYPTO; goto on_return; } /* If all options points to 'NULL' method, just bypass SRTP */ if (cr_tx_idx == 0 && cr_rx_idx == 0 && au_tx_idx == 0 && au_rx_idx == 0) { srtp->bypass_srtp = PJ_TRUE; goto on_return; } /* Check key length */ if (tx->key.slen != (pj_ssize_t)crypto_suites[cr_tx_idx].cipher_key_len || rx->key.slen != (pj_ssize_t)crypto_suites[cr_rx_idx].cipher_key_len) { status = PJMEDIA_SRTP_EINKEYLEN; goto on_return; } /* Init transmit direction */ pj_bzero(&tx_, sizeof(srtp_policy_t)); pj_memmove(srtp->tx_key, tx->key.ptr, tx->key.slen); if (cr_tx_idx && au_tx_idx) tx_.rtp.sec_serv = sec_serv_conf_and_auth; else if (cr_tx_idx) tx_.rtp.sec_serv = sec_serv_conf; else if (au_tx_idx) tx_.rtp.sec_serv = sec_serv_auth; else tx_.rtp.sec_serv = sec_serv_none; tx_.key = (uint8_t*)srtp->tx_key; tx_.ssrc.type = ssrc_any_outbound; tx_.ssrc.value = 0; tx_.rtp.cipher_type = crypto_suites[cr_tx_idx].cipher_type; tx_.rtp.cipher_key_len = crypto_suites[cr_tx_idx].cipher_key_len; tx_.rtp.auth_type = crypto_suites[au_tx_idx].auth_type; tx_.rtp.auth_key_len = crypto_suites[au_tx_idx].auth_key_len; tx_.rtp.auth_tag_len = crypto_suites[au_tx_idx].srtp_auth_tag_len; tx_.rtcp = tx_.rtp; tx_.rtcp.auth_tag_len = crypto_suites[au_tx_idx].srtcp_auth_tag_len; tx_.next = NULL; err = srtp_create(&srtp->srtp_tx_ctx, &tx_); if (err != err_status_ok) { status = PJMEDIA_ERRNO_FROM_LIBSRTP(err); goto on_return; } srtp->tx_policy = *tx; pj_strset(&srtp->tx_policy.key, srtp->tx_key, tx->key.slen); srtp->tx_policy.name=pj_str(crypto_suites[get_crypto_idx(&tx->name)].name); /* Init receive direction */ pj_bzero(&rx_, sizeof(srtp_policy_t)); pj_memmove(srtp->rx_key, rx->key.ptr, rx->key.slen); if (cr_rx_idx && au_rx_idx) rx_.rtp.sec_serv = sec_serv_conf_and_auth; else if (cr_rx_idx) rx_.rtp.sec_serv = sec_serv_conf; else if (au_rx_idx) rx_.rtp.sec_serv = sec_serv_auth; else rx_.rtp.sec_serv = sec_serv_none; rx_.key = (uint8_t*)srtp->rx_key; rx_.ssrc.type = ssrc_any_inbound; rx_.ssrc.value = 0; rx_.rtp.sec_serv = crypto_suites[cr_rx_idx].service; rx_.rtp.cipher_type = crypto_suites[cr_rx_idx].cipher_type; rx_.rtp.cipher_key_len = crypto_suites[cr_rx_idx].cipher_key_len; rx_.rtp.auth_type = crypto_suites[au_rx_idx].auth_type; rx_.rtp.auth_key_len = crypto_suites[au_rx_idx].auth_key_len; rx_.rtp.auth_tag_len = crypto_suites[au_rx_idx].srtp_auth_tag_len; rx_.rtcp = rx_.rtp; rx_.rtcp.auth_tag_len = crypto_suites[au_rx_idx].srtcp_auth_tag_len; rx_.next = NULL; err = srtp_create(&srtp->srtp_rx_ctx, &rx_); if (err != err_status_ok) { srtp_dealloc(srtp->srtp_tx_ctx); status = PJMEDIA_ERRNO_FROM_LIBSRTP(err); goto on_return; } srtp->rx_policy = *rx; pj_strset(&srtp->rx_policy.key, srtp->rx_key, rx->key.slen); srtp->rx_policy.name=pj_str(crypto_suites[get_crypto_idx(&rx->name)].name); /* Declare SRTP session initialized */ srtp->session_inited = PJ_TRUE; /* Logging stuffs */ #if PJ_LOG_MAX_LEVEL >= 5 { char b64[PJ_BASE256_TO_BASE64_LEN(MAX_KEY_LEN)]; int b64_len; /* TX crypto and key */ b64_len = sizeof(b64); status = pj_base64_encode((pj_uint8_t*)tx->key.ptr, tx->key.slen, b64, &b64_len); if (status != PJ_SUCCESS) b64_len = pj_ansi_sprintf(b64, "--key too long--"); else b64[b64_len] = '\0'; PJ_LOG(5, (srtp->pool->obj_name, "TX: %s key=%s", srtp->tx_policy.name.ptr, b64)); if (srtp->tx_policy.flags) { PJ_LOG(5,(srtp->pool->obj_name, "TX: disable%s%s", (cr_tx_idx?"":" enc"), (au_tx_idx?"":" auth"))); } /* RX crypto and key */ b64_len = sizeof(b64); status = pj_base64_encode((pj_uint8_t*)rx->key.ptr, rx->key.slen, b64, &b64_len); if (status != PJ_SUCCESS) b64_len = pj_ansi_sprintf(b64, "--key too long--"); else b64[b64_len] = '\0'; PJ_LOG(5, (srtp->pool->obj_name, "RX: %s key=%s", srtp->rx_policy.name.ptr, b64)); if (srtp->rx_policy.flags) { PJ_LOG(5,(srtp->pool->obj_name,"RX: disable%s%s", (cr_rx_idx?"":" enc"), (au_rx_idx?"":" auth"))); } } #endif on_return: pj_lock_release(srtp->mutex); return status; } /* * Stop SRTP session. */ PJ_DEF(pj_status_t) pjmedia_transport_srtp_stop(pjmedia_transport *srtp) { transport_srtp *p_srtp = (transport_srtp*) srtp; err_status_t err; PJ_ASSERT_RETURN(srtp, PJ_EINVAL); pj_lock_acquire(p_srtp->mutex); if (!p_srtp->session_inited) { pj_lock_release(p_srtp->mutex); return PJ_SUCCESS; } err = srtp_dealloc(p_srtp->srtp_rx_ctx); if (err != err_status_ok) { PJ_LOG(4, (p_srtp->pool->obj_name, "Failed to dealloc RX SRTP context: %s", get_libsrtp_errstr(err))); } err = srtp_dealloc(p_srtp->srtp_tx_ctx); if (err != err_status_ok) { PJ_LOG(4, (p_srtp->pool->obj_name, "Failed to dealloc TX SRTP context: %s", get_libsrtp_errstr(err))); } p_srtp->session_inited = PJ_FALSE; pj_bzero(&p_srtp->rx_policy, sizeof(p_srtp->rx_policy)); pj_bzero(&p_srtp->tx_policy, sizeof(p_srtp->tx_policy)); pj_lock_release(p_srtp->mutex); return PJ_SUCCESS; } PJ_DEF(pjmedia_transport *) pjmedia_transport_srtp_get_member( pjmedia_transport *tp) { transport_srtp *srtp = (transport_srtp*) tp; PJ_ASSERT_RETURN(tp, NULL); return srtp->member_tp; } static pj_status_t transport_get_info(pjmedia_transport *tp, pjmedia_transport_info *info) { transport_srtp *srtp = (transport_srtp*) tp; pjmedia_srtp_info srtp_info; int spc_info_idx; PJ_ASSERT_RETURN(tp && info, PJ_EINVAL); PJ_ASSERT_RETURN(info->specific_info_cnt < PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXCNT, PJ_ETOOMANY); PJ_ASSERT_RETURN(sizeof(pjmedia_srtp_info) <= PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXSIZE, PJ_ENOMEM); srtp_info.active = srtp->session_inited; srtp_info.rx_policy = srtp->rx_policy; srtp_info.tx_policy = srtp->tx_policy; srtp_info.use = srtp->setting.use; srtp_info.peer_use = srtp->peer_use; spc_info_idx = info->specific_info_cnt++; info->spc_info[spc_info_idx].type = PJMEDIA_TRANSPORT_TYPE_SRTP; info->spc_info[spc_info_idx].cbsize = sizeof(srtp_info); pj_memcpy(&info->spc_info[spc_info_idx].buffer, &srtp_info, sizeof(srtp_info)); return pjmedia_transport_get_info(srtp->member_tp, info); } static pj_status_t transport_attach(pjmedia_transport *tp, void *user_data, const pj_sockaddr_t *rem_addr, const pj_sockaddr_t *rem_rtcp, unsigned addr_len, void (*rtp_cb) (void*, void*, pj_ssize_t), void (*rtcp_cb)(void*, void*, pj_ssize_t)) { transport_srtp *srtp = (transport_srtp*) tp; pj_status_t status; PJ_ASSERT_RETURN(tp && rem_addr && addr_len, PJ_EINVAL); /* Save the callbacks */ pj_lock_acquire(srtp->mutex); srtp->rtp_cb = rtp_cb; srtp->rtcp_cb = rtcp_cb; srtp->user_data = user_data; pj_lock_release(srtp->mutex); /* Attach itself to transport */ status = pjmedia_transport_attach(srtp->member_tp, srtp, rem_addr, rem_rtcp, addr_len, &srtp_rtp_cb, &srtp_rtcp_cb); if (status != PJ_SUCCESS) { pj_lock_acquire(srtp->mutex); srtp->rtp_cb = NULL; srtp->rtcp_cb = NULL; srtp->user_data = NULL; pj_lock_release(srtp->mutex); return status; } return PJ_SUCCESS; } static void transport_detach(pjmedia_transport *tp, void *strm) { transport_srtp *srtp = (transport_srtp*) tp; PJ_UNUSED_ARG(strm); PJ_ASSERT_ON_FAIL(tp, return); if (srtp->member_tp) { pjmedia_transport_detach(srtp->member_tp, srtp); } /* Clear up application infos from transport */ pj_lock_acquire(srtp->mutex); srtp->rtp_cb = NULL; srtp->rtcp_cb = NULL; srtp->user_data = NULL; pj_lock_release(srtp->mutex); } static pj_status_t transport_send_rtp( pjmedia_transport *tp, const void *pkt, pj_size_t size) { pj_status_t status; transport_srtp *srtp = (transport_srtp*) tp; int len = (int)size; err_status_t err; if (srtp->bypass_srtp) return pjmedia_transport_send_rtp(srtp->member_tp, pkt, size); if (size > sizeof(srtp->rtp_tx_buffer) - 10) return PJ_ETOOBIG; pj_memcpy(srtp->rtp_tx_buffer, pkt, size); pj_lock_acquire(srtp->mutex); if (!srtp->session_inited) { pj_lock_release(srtp->mutex); return PJ_EINVALIDOP; } err = srtp_protect(srtp->srtp_tx_ctx, srtp->rtp_tx_buffer, &len); pj_lock_release(srtp->mutex); if (err == err_status_ok) { status = pjmedia_transport_send_rtp(srtp->member_tp, srtp->rtp_tx_buffer, len); } else { status = PJMEDIA_ERRNO_FROM_LIBSRTP(err); } return status; } static pj_status_t transport_send_rtcp(pjmedia_transport *tp, const void *pkt, pj_size_t size) { return transport_send_rtcp2(tp, NULL, 0, pkt, size); } static pj_status_t transport_send_rtcp2(pjmedia_transport *tp, const pj_sockaddr_t *addr, unsigned addr_len, const void *pkt, pj_size_t size) { pj_status_t status; transport_srtp *srtp = (transport_srtp*) tp; int len = (int)size; err_status_t err; if (srtp->bypass_srtp) { return pjmedia_transport_send_rtcp2(srtp->member_tp, addr, addr_len, pkt, size); } if (size > sizeof(srtp->rtcp_tx_buffer) - 10) return PJ_ETOOBIG; pj_memcpy(srtp->rtcp_tx_buffer, pkt, size); pj_lock_acquire(srtp->mutex); if (!srtp->session_inited) { pj_lock_release(srtp->mutex); return PJ_EINVALIDOP; } err = srtp_protect_rtcp(srtp->srtp_tx_ctx, srtp->rtcp_tx_buffer, &len); pj_lock_release(srtp->mutex); if (err == err_status_ok) { status = pjmedia_transport_send_rtcp2(srtp->member_tp, addr, addr_len, srtp->rtcp_tx_buffer, len); } else { status = PJMEDIA_ERRNO_FROM_LIBSRTP(err); } return status; } static pj_status_t transport_simulate_lost(pjmedia_transport *tp, pjmedia_dir dir, unsigned pct_lost) { transport_srtp *srtp = (transport_srtp *) tp; PJ_ASSERT_RETURN(tp, PJ_EINVAL); return pjmedia_transport_simulate_lost(srtp->member_tp, dir, pct_lost); } static pj_status_t transport_destroy (pjmedia_transport *tp) { transport_srtp *srtp = (transport_srtp *) tp; pj_status_t status; PJ_ASSERT_RETURN(tp, PJ_EINVAL); if (srtp->setting.close_member_tp && srtp->member_tp) { pjmedia_transport_close(srtp->member_tp); } status = pjmedia_transport_srtp_stop(tp); /* In case mutex is being acquired by other thread */ pj_lock_acquire(srtp->mutex); pj_lock_release(srtp->mutex); pj_lock_destroy(srtp->mutex); pj_pool_release(srtp->pool); return status; } /* * This callback is called by transport when incoming rtp is received */ static void srtp_rtp_cb( void *user_data, void *pkt, pj_ssize_t size) { transport_srtp *srtp = (transport_srtp *) user_data; int len = size; err_status_t err; void (*cb)(void*, void*, pj_ssize_t) = NULL; void *cb_data = NULL; if (srtp->bypass_srtp) { srtp->rtp_cb(srtp->user_data, pkt, size); return; } if (size < 0) { return; } /* Make sure buffer is 32bit aligned */ PJ_ASSERT_ON_FAIL( (((pj_ssize_t)pkt) & 0x03)==0, return ); if (srtp->probation_cnt > 0) --srtp->probation_cnt; pj_lock_acquire(srtp->mutex); if (!srtp->session_inited) { pj_lock_release(srtp->mutex); return; } err = srtp_unprotect(srtp->srtp_rx_ctx, (pj_uint8_t*)pkt, &len); if (srtp->probation_cnt > 0 && (err == err_status_replay_old || err == err_status_replay_fail)) { /* Handle such condition that stream is updated (RTP seq is reinited * & SRTP is restarted), but some old packets are still coming * so SRTP is learning wrong RTP seq. While the newly inited RTP seq * comes, SRTP thinks the RTP seq is replayed, so srtp_unprotect() * will return err_status_replay_*. Restarting SRTP can resolve this. */ pjmedia_srtp_crypto tx, rx; pj_status_t status; tx = srtp->tx_policy; rx = srtp->rx_policy; status = pjmedia_transport_srtp_start((pjmedia_transport*)srtp, &tx, &rx); if (status != PJ_SUCCESS) { PJ_LOG(5,(srtp->pool->obj_name, "Failed to restart SRTP, err=%s", get_libsrtp_errstr(err))); } else if (!srtp->bypass_srtp) { err = srtp_unprotect(srtp->srtp_rx_ctx, (pj_uint8_t*)pkt, &len); } } if (err != err_status_ok) { PJ_LOG(5,(srtp->pool->obj_name, "Failed to unprotect SRTP, pkt size=%d, err=%s", size, get_libsrtp_errstr(err))); } else { cb = srtp->rtp_cb; cb_data = srtp->user_data; } pj_lock_release(srtp->mutex); if (cb) { (*cb)(cb_data, pkt, len); } } /* * This callback is called by transport when incoming rtcp is received */ static void srtp_rtcp_cb( void *user_data, void *pkt, pj_ssize_t size) { transport_srtp *srtp = (transport_srtp *) user_data; int len = size; err_status_t err; void (*cb)(void*, void*, pj_ssize_t) = NULL; void *cb_data = NULL; if (srtp->bypass_srtp) { srtp->rtcp_cb(srtp->user_data, pkt, size); return; } if (size < 0) { return; } /* Make sure buffer is 32bit aligned */ PJ_ASSERT_ON_FAIL( (((pj_ssize_t)pkt) & 0x03)==0, return ); pj_lock_acquire(srtp->mutex); if (!srtp->session_inited) { pj_lock_release(srtp->mutex); return; } err = srtp_unprotect_rtcp(srtp->srtp_rx_ctx, (pj_uint8_t*)pkt, &len); if (err != err_status_ok) { PJ_LOG(5,(srtp->pool->obj_name, "Failed to unprotect SRTCP, pkt size=%d, err=%s", size, get_libsrtp_errstr(err))); } else { cb = srtp->rtcp_cb; cb_data = srtp->user_data; } pj_lock_release(srtp->mutex); if (cb) { (*cb)(cb_data, pkt, len); } } /* Generate crypto attribute, including crypto key. * If crypto-suite chosen is crypto NULL, just return PJ_SUCCESS, * and set buffer_len = 0. */ static pj_status_t generate_crypto_attr_value(pj_pool_t *pool, char *buffer, int *buffer_len, pjmedia_srtp_crypto *crypto, int tag) { pj_status_t status; int cs_idx = get_crypto_idx(&crypto->name); char b64_key[PJ_BASE256_TO_BASE64_LEN(MAX_KEY_LEN)+1]; int b64_key_len = sizeof(b64_key); int print_len; if (cs_idx == -1) return PJMEDIA_SRTP_ENOTSUPCRYPTO; /* Crypto-suite NULL. */ if (cs_idx == 0) { *buffer_len = 0; return PJ_SUCCESS; } /* Generate key if not specified. */ if (crypto->key.slen == 0) { pj_bool_t key_ok; char key[MAX_KEY_LEN]; err_status_t err; unsigned i; PJ_ASSERT_RETURN(MAX_KEY_LEN >= crypto_suites[cs_idx].cipher_key_len, PJ_ETOOSMALL); do { key_ok = PJ_TRUE; #if defined(PJ_HAS_SSL_SOCK) && (PJ_HAS_SSL_SOCK != 0) /* Include OpenSSL libraries for MSVC */ # ifdef _MSC_VER # pragma comment( lib, "libeay32") # pragma comment( lib, "ssleay32") # endif err = RAND_bytes((unsigned char*)key, crypto_suites[cs_idx].cipher_key_len); if (err != 1) { PJ_LOG(5,(THIS_FILE, "Failed generating random key")); return PJMEDIA_ERRNO_FROM_LIBSRTP(1); } #else err = crypto_get_random((unsigned char*)key, crypto_suites[cs_idx].cipher_key_len); if (err != err_status_ok) { PJ_LOG(5,(THIS_FILE, "Failed generating random key: %s", get_libsrtp_errstr(err))); return PJMEDIA_ERRNO_FROM_LIBSRTP(err); } #endif for (i=0; ikey.ptr = (char*) pj_pool_zalloc(pool, crypto_suites[cs_idx].cipher_key_len); pj_memcpy(crypto->key.ptr, key, crypto_suites[cs_idx].cipher_key_len); crypto->key.slen = crypto_suites[cs_idx].cipher_key_len; } if (crypto->key.slen != (pj_ssize_t)crypto_suites[cs_idx].cipher_key_len) return PJMEDIA_SRTP_EINKEYLEN; /* Key transmitted via SDP should be base64 encoded. */ status = pj_base64_encode((pj_uint8_t*)crypto->key.ptr, crypto->key.slen, b64_key, &b64_key_len); if (status != PJ_SUCCESS) { PJ_LOG(5,(THIS_FILE, "Failed encoding plain key to base64")); return status; } b64_key[b64_key_len] = '\0'; PJ_ASSERT_RETURN(*buffer_len >= (crypto->name.slen + \ b64_key_len + 16), PJ_ETOOSMALL); /* Print the crypto attribute value. */ print_len = pj_ansi_snprintf(buffer, *buffer_len, "%d %s inline:%s", tag, crypto_suites[cs_idx].name, b64_key); if (print_len < 1 || print_len >= *buffer_len) return PJ_ETOOSMALL; *buffer_len = print_len; return PJ_SUCCESS; } /* Parse crypto attribute line */ static pj_status_t parse_attr_crypto(pj_pool_t *pool, const pjmedia_sdp_attr *attr, pjmedia_srtp_crypto *crypto, int *tag) { pj_str_t input; char *token; pj_size_t token_len; pj_str_t tmp; pj_status_t status; int itmp; pj_bzero(crypto, sizeof(*crypto)); pj_strdup_with_null(pool, &input, &attr->value); /* Tag */ token = strtok(input.ptr, " "); if (!token) { PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting tag")); return PJMEDIA_SDP_EINATTR; } token_len = pj_ansi_strlen(token); /* Tag must not use leading zeroes. */ if (token_len > 1 && *token == '0') return PJMEDIA_SDP_EINATTR; /* Tag must be decimal, i.e: contains only digit '0'-'9'. */ for (itmp = 0; itmp < token_len; ++itmp) if (!pj_isdigit(token[itmp])) return PJMEDIA_SDP_EINATTR; /* Get tag value. */ *tag = atoi(token); /* Crypto-suite */ token = strtok(NULL, " "); if (!token) { PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting crypto suite")); return PJMEDIA_SDP_EINATTR; } crypto->name = pj_str(token); /* Key method */ token = strtok(NULL, ":"); if (!token) { PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting key method")); return PJMEDIA_SDP_EINATTR; } if (pj_ansi_stricmp(token, "inline")) { PJ_LOG(4,(THIS_FILE, "Attribute crypto key method '%s' not supported!", token)); return PJMEDIA_SDP_EINATTR; } /* Key */ token = strtok(NULL, "| "); if (!token) { PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting key")); return PJMEDIA_SDP_EINATTR; } tmp = pj_str(token); if (PJ_BASE64_TO_BASE256_LEN(tmp.slen) > MAX_KEY_LEN) { PJ_LOG(4,(THIS_FILE, "Key too long")); return PJMEDIA_SRTP_EINKEYLEN; } /* Decode key */ crypto->key.ptr = (char*) pj_pool_zalloc(pool, MAX_KEY_LEN); itmp = MAX_KEY_LEN; status = pj_base64_decode(&tmp, (pj_uint8_t*)crypto->key.ptr, &itmp); if (status != PJ_SUCCESS) { PJ_LOG(4,(THIS_FILE, "Failed decoding crypto key from base64")); return status; } crypto->key.slen = itmp; return PJ_SUCCESS; } static pj_status_t transport_media_create(pjmedia_transport *tp, pj_pool_t *sdp_pool, unsigned options, const pjmedia_sdp_session *sdp_remote, unsigned media_index) { struct transport_srtp *srtp = (struct transport_srtp*) tp; unsigned member_tp_option; PJ_ASSERT_RETURN(tp, PJ_EINVAL); pj_bzero(&srtp->rx_policy_neg, sizeof(srtp->rx_policy_neg)); pj_bzero(&srtp->tx_policy_neg, sizeof(srtp->tx_policy_neg)); srtp->media_option = options; member_tp_option = options | PJMEDIA_TPMED_NO_TRANSPORT_CHECKING; srtp->offerer_side = sdp_remote == NULL; /* Validations */ if (srtp->offerer_side) { if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) goto BYPASS_SRTP; } else { pjmedia_sdp_media *m_rem; m_rem = sdp_remote->media[media_index]; /* Nothing to do on inactive media stream */ if (pjmedia_sdp_media_find_attr(m_rem, &ID_INACTIVE, NULL)) goto BYPASS_SRTP; /* Validate remote media transport based on SRTP usage option. */ switch (srtp->setting.use) { case PJMEDIA_SRTP_DISABLED: if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) == 0) return PJMEDIA_SRTP_ESDPINTRANSPORT; goto BYPASS_SRTP; case PJMEDIA_SRTP_OPTIONAL: break; case PJMEDIA_SRTP_MANDATORY: if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) != 0) return PJMEDIA_SRTP_ESDPINTRANSPORT; break; } } goto PROPAGATE_MEDIA_CREATE; BYPASS_SRTP: srtp->bypass_srtp = PJ_TRUE; member_tp_option &= ~PJMEDIA_TPMED_NO_TRANSPORT_CHECKING; PROPAGATE_MEDIA_CREATE: return pjmedia_transport_media_create(srtp->member_tp, sdp_pool, member_tp_option, sdp_remote, media_index); } static pj_status_t transport_encode_sdp(pjmedia_transport *tp, pj_pool_t *sdp_pool, pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *sdp_remote, unsigned media_index) { struct transport_srtp *srtp = (struct transport_srtp*) tp; pjmedia_sdp_media *m_rem, *m_loc; enum { MAXLEN = 512 }; char buffer[MAXLEN]; int buffer_len; pj_status_t status; pjmedia_sdp_attr *attr; pj_str_t attr_value; unsigned i, j; PJ_ASSERT_RETURN(tp && sdp_pool && sdp_local, PJ_EINVAL); pj_bzero(&srtp->rx_policy_neg, sizeof(srtp->rx_policy_neg)); pj_bzero(&srtp->tx_policy_neg, sizeof(srtp->tx_policy_neg)); srtp->offerer_side = sdp_remote == NULL; m_rem = sdp_remote ? sdp_remote->media[media_index] : NULL; m_loc = sdp_local->media[media_index]; /* Bypass if media transport is not RTP/AVP or RTP/SAVP */ if (pj_stricmp(&m_loc->desc.transport, &ID_RTP_AVP) != 0 && pj_stricmp(&m_loc->desc.transport, &ID_RTP_SAVP) != 0) goto BYPASS_SRTP; /* Do nothing if we are in bypass */ if (srtp->bypass_srtp) goto BYPASS_SRTP; /* If the media is inactive, do nothing. */ /* No, we still need to process SRTP offer/answer even if the media is * marked as inactive, because the transport is still alive in this * case (e.g. for keep-alive). See: * http://trac.pjsip.org/repos/ticket/1079 */ /* if (pjmedia_sdp_media_find_attr(m_loc, &ID_INACTIVE, NULL) || (m_rem && pjmedia_sdp_media_find_attr(m_rem, &ID_INACTIVE, NULL))) goto BYPASS_SRTP; */ /* Check remote media transport & set local media transport * based on SRTP usage option. */ if (srtp->offerer_side) { /* Generate transport */ switch (srtp->setting.use) { case PJMEDIA_SRTP_DISABLED: goto BYPASS_SRTP; case PJMEDIA_SRTP_OPTIONAL: m_loc->desc.transport = (srtp->peer_use == PJMEDIA_SRTP_MANDATORY)? ID_RTP_SAVP : ID_RTP_AVP; break; case PJMEDIA_SRTP_MANDATORY: m_loc->desc.transport = ID_RTP_SAVP; break; } /* Generate crypto attribute if not yet */ if (pjmedia_sdp_media_find_attr(m_loc, &ID_CRYPTO, NULL) == NULL) { /* Offer only current active crypto if any, otherwise offer all * crypto-suites in the setting. */ for (i=0; isetting.crypto_count; ++i) { if (srtp->tx_policy.name.slen && pj_stricmp(&srtp->tx_policy.name, &srtp->setting.crypto[i].name) != 0) { continue; } buffer_len = MAXLEN; status = generate_crypto_attr_value(srtp->pool, buffer, &buffer_len, &srtp->setting.crypto[i], i+1); if (status != PJ_SUCCESS) return status; /* If buffer_len==0, just skip the crypto attribute. */ if (buffer_len) { pj_strset(&attr_value, buffer, buffer_len); attr = pjmedia_sdp_attr_create(srtp->pool, ID_CRYPTO.ptr, &attr_value); m_loc->attr[m_loc->attr_count++] = attr; } } } } else { /* Answerer side */ pj_assert(sdp_remote && m_rem); /* Generate transport */ switch (srtp->setting.use) { case PJMEDIA_SRTP_DISABLED: if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) == 0) return PJMEDIA_SRTP_ESDPINTRANSPORT; goto BYPASS_SRTP; case PJMEDIA_SRTP_OPTIONAL: m_loc->desc.transport = m_rem->desc.transport; break; case PJMEDIA_SRTP_MANDATORY: if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) != 0) return PJMEDIA_SRTP_ESDPINTRANSPORT; m_loc->desc.transport = ID_RTP_SAVP; break; } /* Generate crypto attribute if not yet */ if (pjmedia_sdp_media_find_attr(m_loc, &ID_CRYPTO, NULL) == NULL) { pjmedia_srtp_crypto tmp_rx_crypto; pj_bool_t has_crypto_attr = PJ_FALSE; int matched_idx = -1; int chosen_tag = 0; int tags[64]; /* assume no more than 64 crypto attrs in a media */ unsigned cr_attr_count = 0; /* Find supported crypto-suite, get the tag, and assign policy_local */ for (i=0; iattr_count; ++i) { if (pj_stricmp(&m_rem->attr[i]->name, &ID_CRYPTO) != 0) continue; has_crypto_attr = PJ_TRUE; status = parse_attr_crypto(srtp->pool, m_rem->attr[i], &tmp_rx_crypto, &tags[cr_attr_count]); if (status != PJ_SUCCESS) return status; /* Check duplicated tag */ for (j=0; jsetting.crypto_count; ++j) if (pj_stricmp(&tmp_rx_crypto.name, &srtp->setting.crypto[j].name) == 0) { int cs_idx = get_crypto_idx(&tmp_rx_crypto.name); if (cs_idx == -1) return PJMEDIA_SRTP_ENOTSUPCRYPTO; /* Force to use test key */ /* bad keys for snom: */ //char *hex_test_key = "58b29c5c8f42308120ce857e439f2d" // "7810a8b10ad0b1446be5470faea496"; //char *hex_test_key = "20a26aac7ba062d356ff52b61e3993" // "ccb78078f12c64db94b9c294927fd0"; //pj_str_t *test_key = &srtp->setting.crypto[j].key; //char *raw_test_key = pj_pool_zalloc(srtp->pool, 64); //hex_string_to_octet_string( // raw_test_key, // hex_test_key, // strlen(hex_test_key)); //pj_strset(test_key, raw_test_key, // crypto_suites[cs_idx].cipher_key_len); /* EO Force to use test key */ if (tmp_rx_crypto.key.slen != (int)crypto_suites[cs_idx].cipher_key_len) return PJMEDIA_SRTP_EINKEYLEN; srtp->rx_policy_neg = tmp_rx_crypto; chosen_tag = tags[cr_attr_count]; matched_idx = j; break; } } cr_attr_count++; } /* Check crypto negotiation result */ switch (srtp->setting.use) { case PJMEDIA_SRTP_DISABLED: pj_assert(!"Should never reach here"); break; case PJMEDIA_SRTP_OPTIONAL: /* bypass SRTP when no crypto-attr and remote uses RTP/AVP */ if (!has_crypto_attr && pj_stricmp(&m_rem->desc.transport, &ID_RTP_AVP) == 0) goto BYPASS_SRTP; /* bypass SRTP when nothing match and remote uses RTP/AVP */ else if (matched_idx == -1 && pj_stricmp(&m_rem->desc.transport, &ID_RTP_AVP) == 0) goto BYPASS_SRTP; break; case PJMEDIA_SRTP_MANDATORY: /* Do nothing, intentional */ break; } /* No crypto attr */ if (!has_crypto_attr) { DEACTIVATE_MEDIA(sdp_pool, m_loc); return PJMEDIA_SRTP_ESDPREQCRYPTO; } /* No crypto match */ if (matched_idx == -1) { DEACTIVATE_MEDIA(sdp_pool, m_loc); return PJMEDIA_SRTP_ENOTSUPCRYPTO; } /* we have to generate crypto answer, * with srtp->tx_policy_neg matched the offer * and rem_tag contains matched offer tag. */ buffer_len = MAXLEN; status = generate_crypto_attr_value(srtp->pool, buffer, &buffer_len, &srtp->setting.crypto[matched_idx], chosen_tag); if (status != PJ_SUCCESS) return status; srtp->tx_policy_neg = srtp->setting.crypto[matched_idx]; /* If buffer_len==0, just skip the crypto attribute. */ if (buffer_len) { pj_strset(&attr_value, buffer, buffer_len); attr = pjmedia_sdp_attr_create(sdp_pool, ID_CRYPTO.ptr, &attr_value); m_loc->attr[m_loc->attr_count++] = attr; } /* At this point, we get valid rx_policy_neg & tx_policy_neg. */ } } goto PROPAGATE_MEDIA_CREATE; BYPASS_SRTP: /* Do not update this flag here as actually the media session hasn't been * updated. */ //srtp->bypass_srtp = PJ_TRUE; PROPAGATE_MEDIA_CREATE: return pjmedia_transport_encode_sdp(srtp->member_tp, sdp_pool, sdp_local, sdp_remote, media_index); } static pj_status_t transport_media_start(pjmedia_transport *tp, pj_pool_t *pool, const pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *sdp_remote, unsigned media_index) { struct transport_srtp *srtp = (struct transport_srtp*) tp; pjmedia_sdp_media *m_rem, *m_loc; pj_status_t status; unsigned i; PJ_ASSERT_RETURN(tp && pool && sdp_local && sdp_remote, PJ_EINVAL); m_rem = sdp_remote->media[media_index]; m_loc = sdp_local->media[media_index]; if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) == 0) srtp->peer_use = PJMEDIA_SRTP_MANDATORY; else srtp->peer_use = PJMEDIA_SRTP_OPTIONAL; /* For answerer side, this function will just have to start SRTP */ /* Check remote media transport & set local media transport * based on SRTP usage option. */ if (srtp->offerer_side) { if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) { if (pjmedia_sdp_media_find_attr(m_rem, &ID_CRYPTO, NULL)) { DEACTIVATE_MEDIA(pool, m_loc); return PJMEDIA_SRTP_ESDPINCRYPTO; } goto BYPASS_SRTP; } else if (srtp->setting.use == PJMEDIA_SRTP_OPTIONAL) { // Regardless the answer's transport type (RTP/AVP or RTP/SAVP), // the answer must be processed through in optional mode. // Please note that at this point transport type is ensured to be // RTP/AVP or RTP/SAVP, see transport_media_create() //if (pj_stricmp(&m_rem->desc.transport, &m_loc->desc.transport)) { //DEACTIVATE_MEDIA(pool, m_loc); //return PJMEDIA_SDP_EINPROTO; //} } else if (srtp->setting.use == PJMEDIA_SRTP_MANDATORY) { if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP)) { DEACTIVATE_MEDIA(pool, m_loc); return PJMEDIA_SDP_EINPROTO; } } } if (srtp->offerer_side) { /* find supported crypto-suite, get the tag, and assign policy_local */ pjmedia_srtp_crypto tmp_tx_crypto; pj_bool_t has_crypto_attr = PJ_FALSE; int rem_tag; for (i=0; iattr_count; ++i) { if (pj_stricmp(&m_rem->attr[i]->name, &ID_CRYPTO) != 0) continue; /* more than one crypto attribute in media answer */ if (has_crypto_attr) { DEACTIVATE_MEDIA(pool, m_loc); return PJMEDIA_SRTP_ESDPAMBIGUEANS; } has_crypto_attr = PJ_TRUE; status = parse_attr_crypto(srtp->pool, m_rem->attr[i], &tmp_tx_crypto, &rem_tag); if (status != PJ_SUCCESS) return status; /* our offer tag is always ordered by setting */ if (rem_tag < 1 || rem_tag > (int)srtp->setting.crypto_count) { DEACTIVATE_MEDIA(pool, m_loc); return PJMEDIA_SRTP_ESDPINCRYPTOTAG; } /* match the crypto name */ if (pj_stricmp(&tmp_tx_crypto.name, &srtp->setting.crypto[rem_tag-1].name) != 0) { DEACTIVATE_MEDIA(pool, m_loc); return PJMEDIA_SRTP_ECRYPTONOTMATCH; } srtp->tx_policy_neg = srtp->setting.crypto[rem_tag-1]; srtp->rx_policy_neg = tmp_tx_crypto; } if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) { /* should never reach here */ goto BYPASS_SRTP; } else if (srtp->setting.use == PJMEDIA_SRTP_OPTIONAL) { if (!has_crypto_attr) goto BYPASS_SRTP; } else if (srtp->setting.use == PJMEDIA_SRTP_MANDATORY) { if (!has_crypto_attr) { DEACTIVATE_MEDIA(pool, m_loc); return PJMEDIA_SRTP_ESDPREQCRYPTO; } } /* At this point, we get valid rx_policy_neg & tx_policy_neg. */ } /* Make sure we have the SRTP policies */ if (srtp_crypto_empty(&srtp->tx_policy_neg) || srtp_crypto_empty(&srtp->rx_policy_neg)) { goto BYPASS_SRTP; } /* Reset probation counts */ srtp->probation_cnt = PROBATION_CNT_INIT; /* Got policy_local & policy_remote, let's initalize the SRTP */ /* Ticket #1075: media_start() is called whenever media description * gets updated, e.g: call hold, however we should restart SRTP only * when the SRTP policy settings are updated. */ if (srtp_crypto_cmp(&srtp->tx_policy_neg, &srtp->tx_policy) || srtp_crypto_cmp(&srtp->rx_policy_neg, &srtp->rx_policy)) { status = pjmedia_transport_srtp_start(tp, &srtp->tx_policy_neg, &srtp->rx_policy_neg); if (status != PJ_SUCCESS) return status; } srtp->bypass_srtp = PJ_FALSE; goto PROPAGATE_MEDIA_START; BYPASS_SRTP: srtp->bypass_srtp = PJ_TRUE; srtp->peer_use = PJMEDIA_SRTP_DISABLED; if (srtp->session_inited) { pjmedia_transport_srtp_stop(tp); } PROPAGATE_MEDIA_START: return pjmedia_transport_media_start(srtp->member_tp, pool, sdp_local, sdp_remote, media_index); } static pj_status_t transport_media_stop(pjmedia_transport *tp) { struct transport_srtp *srtp = (struct transport_srtp*) tp; pj_status_t status; PJ_ASSERT_RETURN(tp, PJ_EINVAL); status = pjmedia_transport_media_stop(srtp->member_tp); if (status != PJ_SUCCESS) PJ_LOG(4, (srtp->pool->obj_name, "SRTP failed stop underlying media transport.")); return pjmedia_transport_srtp_stop(tp); } /* Utility */ PJ_DEF(pj_status_t) pjmedia_transport_srtp_decrypt_pkt(pjmedia_transport *tp, pj_bool_t is_rtp, void *pkt, int *pkt_len) { transport_srtp *srtp = (transport_srtp *)tp; err_status_t err; if (srtp->bypass_srtp) return PJ_SUCCESS; PJ_ASSERT_RETURN(tp && pkt && (*pkt_len>0), PJ_EINVAL); PJ_ASSERT_RETURN(srtp->session_inited, PJ_EINVALIDOP); /* Make sure buffer is 32bit aligned */ PJ_ASSERT_ON_FAIL( (((pj_ssize_t)pkt) & 0x03)==0, return PJ_EINVAL); pj_lock_acquire(srtp->mutex); if (!srtp->session_inited) { pj_lock_release(srtp->mutex); return PJ_EINVALIDOP; } if (is_rtp) err = srtp_unprotect(srtp->srtp_rx_ctx, pkt, pkt_len); else err = srtp_unprotect_rtcp(srtp->srtp_rx_ctx, pkt, pkt_len); if (err != err_status_ok) { PJ_LOG(5,(srtp->pool->obj_name, "Failed to unprotect SRTP, pkt size=%d, err=%s", *pkt_len, get_libsrtp_errstr(err))); } pj_lock_release(srtp->mutex); return (err==err_status_ok) ? PJ_SUCCESS : PJMEDIA_ERRNO_FROM_LIBSRTP(err); } #endif diff --git a/deps/pjsip/pjmedia/src/pjmedia/vid_codec_util.c b/deps/pjsip/pjmedia/src/pjmedia/vid_codec_util.c index 3f72b1a7..67d4d8f2 100644 --- a/deps/pjsip/pjmedia/src/pjmedia/vid_codec_util.c +++ b/deps/pjsip/pjmedia/src/pjmedia/vid_codec_util.c @@ -1,760 +1,760 @@ -/* $Id: vid_codec_util.c 4362 2013-02-21 14:51:56Z nanang $ */ +/* $Id: vid_codec_util.c 5046 2015-04-06 06:21:41Z nanang $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) #define THIS_FILE "vid_codec_util.c" /* If this is set to non-zero, H.264 custom negotiation will require * "profile-level-id" and "packetization-mode" to be exact match to * get a successful negotiation. Note that flexible answer (updating * SDP answer to match remote offer) is always active regardless the * value of this macro. */ #define H264_STRICT_SDP_NEGO 0 /* Default frame rate, if not specified */ #define DEFAULT_H264_FPS_NUM 10 #define DEFAULT_H264_FPS_DENUM 1 /* Default aspect ratio, if not specified */ #define DEFAULT_H264_RATIO_NUM 4 #define DEFAULT_H264_RATIO_DENUM 3 /* ITU resolution definition */ struct mpi_resolution_t { pj_str_t name; pjmedia_rect_size size; } mpi_resolutions [] = { {{"CIF",3}, {352,288}}, {{"QCIF",4}, {176,144}}, {{"SQCIF",5}, {88,72}}, {{"CIF4",4}, {704,576}}, {{"CIF16",5}, {1408,1142}}, }; #define CALC_H264_MB_NUM(size) (((size.w+15)/16)*((size.h+15)/16)) #define CALC_H264_MBPS(size,fps) CALC_H264_MB_NUM(size)*fps.num/fps.denum /* Parse fmtp value for custom resolution, e.g: "CUSTOM=800,600,2" */ static pj_status_t parse_custom_res_fmtp(const pj_str_t *fmtp_val, pjmedia_rect_size *size, unsigned *mpi) { const char *p, *p_end; pj_str_t token; unsigned long val[3] = {0}; unsigned i = 0; p = token.ptr = fmtp_val->ptr; p_end = p + fmtp_val->slen; while (p<=p_end && i32) return PJ_EINVAL; size->w = val[0]; size->h = val[1]; *mpi = val[2]; return PJ_SUCCESS; } /* H263 fmtp parser */ PJ_DEF(pj_status_t) pjmedia_vid_codec_parse_h263_fmtp( const pjmedia_codec_fmtp *fmtp, pjmedia_vid_codec_h263_fmtp *h263_fmtp) { const pj_str_t CUSTOM = {"CUSTOM", 6}; unsigned i; pj_bzero(h263_fmtp, sizeof(*h263_fmtp)); for (i=0; icnt; ++i) { unsigned j; pj_bool_t parsed = PJ_FALSE; if (h263_fmtp->mpi_cnt >= PJ_ARRAY_SIZE(h263_fmtp->mpi)) { pj_assert(!"Too small MPI array in H263 fmtp"); continue; } /* Standard size MPIs */ for (j=0; jparam[i].name, &mpi_resolutions[j].name)==0) { unsigned mpi; mpi = pj_strtoul(&fmtp->param[i].val); if (mpi<1 || mpi>32) return PJMEDIA_SDP_EINFMTP; h263_fmtp->mpi[h263_fmtp->mpi_cnt].size = mpi_resolutions[j].size; h263_fmtp->mpi[h263_fmtp->mpi_cnt].val = mpi; ++h263_fmtp->mpi_cnt; parsed = PJ_TRUE; } } if (parsed) continue; /* Custom size MPIs */ if (pj_stricmp(&fmtp->param[i].name, &CUSTOM)==0) { pjmedia_rect_size size; unsigned mpi; pj_status_t status; status = parse_custom_res_fmtp(&fmtp->param[i].val, &size, &mpi); if (status != PJ_SUCCESS) return PJMEDIA_SDP_EINFMTP; h263_fmtp->mpi[h263_fmtp->mpi_cnt].size = size; h263_fmtp->mpi[h263_fmtp->mpi_cnt].val = mpi; ++h263_fmtp->mpi_cnt; } } return PJ_SUCCESS; } static unsigned fps_to_mpi(const pjmedia_ratio *fps) { unsigned mpi; /* Original formula = (fps->denum * 30000) / (fps->num * 1001) */ mpi = (fps->denum*30000 + fps->num*1001/2) / (fps->num*1001); /* Normalize, should be in the range of 1-32 */ if (mpi > 32) mpi = 32; if (mpi < 1) mpi = 1; return mpi; }; PJ_DEF(pj_status_t) pjmedia_vid_codec_h263_apply_fmtp( pjmedia_vid_codec_param *param) { if (param->dir & PJMEDIA_DIR_ENCODING) { pjmedia_vid_codec_h263_fmtp fmtp_loc, fmtp_rem; pjmedia_rect_size size = {0}; unsigned mpi = 0; pjmedia_video_format_detail *vfd; pj_status_t status; vfd = pjmedia_format_get_video_format_detail(¶m->enc_fmt, PJ_TRUE); /* Get local param */ // Local param should be fetched from "param->enc_fmt" instead of // "param->dec_fmtp". //status = pjmedia_vid_codec_parse_h263_fmtp(¶m->dec_fmtp, // &fmtp_loc); //if (status != PJ_SUCCESS) // return status; fmtp_loc.mpi_cnt = 1; fmtp_loc.mpi[0].size = vfd->size; fmtp_loc.mpi[0].val = fps_to_mpi(&vfd->fps); /* Get remote param */ status = pjmedia_vid_codec_parse_h263_fmtp(¶m->enc_fmtp, &fmtp_rem); if (status != PJ_SUCCESS) return status; /* Negotiate size & MPI setting */ if (fmtp_rem.mpi_cnt == 0) { /* Remote doesn't specify MPI setting, send QCIF=1 */ size.w = 176; size.h = 144; mpi = 1; //} else if (fmtp_loc.mpi_cnt == 0) { // /* Local MPI setting not set, just use remote preference. */ // size = fmtp_rem.mpi[0].size; // mpi = fmtp_rem.mpi[0].val; } else { /* Both have preferences, let's try to match them */ unsigned i, j; pj_bool_t matched = PJ_FALSE; pj_uint32_t min_diff = 0xFFFFFFFF; pj_uint32_t loc_sq, rem_sq, diff; /* Find the exact size match or the closest size, then choose * the highest MPI among the match/closest pair. */ for (i = 0; i < fmtp_rem.mpi_cnt && !matched; ++i) { rem_sq = fmtp_rem.mpi[i].size.w * fmtp_rem.mpi[i].size.h; for (j = 0; j < fmtp_loc.mpi_cnt; ++j) { /* See if we got exact match */ if (fmtp_rem.mpi[i].size.w == fmtp_loc.mpi[j].size.w && fmtp_rem.mpi[i].size.h == fmtp_loc.mpi[j].size.h) { size = fmtp_rem.mpi[i].size; mpi = PJ_MAX(fmtp_rem.mpi[i].val, fmtp_loc.mpi[j].val); matched = PJ_TRUE; break; } /* Otherwise keep looking for the closest match */ loc_sq = fmtp_loc.mpi[j].size.w * fmtp_loc.mpi[j].size.h; diff = loc_sq>rem_sq? (loc_sq-rem_sq):(rem_sq-loc_sq); if (diff < min_diff) { size = rem_sqsize = size; vfd->fps.num = 30000; vfd->fps.denum = 1001 * mpi; } if (param->dir & PJMEDIA_DIR_DECODING) { /* Here we just want to find the highest resolution and the lowest MPI * we support and set it as the decoder param. */ pjmedia_vid_codec_h263_fmtp fmtp; pjmedia_video_format_detail *vfd; pj_status_t status; status = pjmedia_vid_codec_parse_h263_fmtp(¶m->dec_fmtp, &fmtp); if (status != PJ_SUCCESS) return status; vfd = pjmedia_format_get_video_format_detail(¶m->dec_fmt, PJ_TRUE); if (fmtp.mpi_cnt == 0) { /* No resolution specified, lets just assume 4CIF=1! */ vfd->size.w = 704; vfd->size.h = 576; vfd->fps.num = 30000; vfd->fps.denum = 1001; } else { unsigned i, max_size = 0, max_size_idx = 0, min_mpi = 32; /* Get the largest size and the lowest MPI */ for (i = 0; i < fmtp.mpi_cnt; ++i) { if (fmtp.mpi[i].size.w * fmtp.mpi[i].size.h > max_size) { max_size = fmtp.mpi[i].size.w * fmtp.mpi[i].size.h; max_size_idx = i; } if (fmtp.mpi[i].val < min_mpi) min_mpi = fmtp.mpi[i].val; } vfd->size = fmtp.mpi[max_size_idx].size; vfd->fps.num = 30000; vfd->fps.denum = 1001 * min_mpi; } } return PJ_SUCCESS; } /* Declaration of H.264 level info */ typedef struct h264_level_info_t { unsigned id; /* Level id. */ unsigned max_mbps; /* Max macroblocks per second. */ unsigned max_mb; /* Max macroblocks. */ unsigned max_br; /* Max bitrate (kbps). */ } h264_level_info_t; /* Init H264 parameters based on profile-level-id */ static pj_status_t init_h264_profile(const pj_str_t *profile, pjmedia_vid_codec_h264_fmtp *fmtp) { const h264_level_info_t level_info[] = { { 10, 1485, 99, 64 }, { 9, 1485, 99, 128 }, /*< level 1b */ { 11, 3000, 396, 192 }, { 12, 6000, 396, 384 }, { 13, 11880, 396, 768 }, { 20, 11880, 396, 2000 }, { 21, 19800, 792, 4000 }, { 22, 20250, 1620, 4000 }, { 30, 40500, 1620, 10000 }, { 31, 108000, 3600, 14000 }, { 32, 216000, 5120, 20000 }, { 40, 245760, 8192, 20000 }, { 41, 245760, 8192, 50000 }, { 42, 522240, 8704, 50000 }, { 50, 589824, 22080, 135000 }, { 51, 983040, 36864, 240000 }, }; unsigned i, tmp; pj_str_t endst; const h264_level_info_t *li = NULL; if (profile->slen != 6) return PJMEDIA_SDP_EINFMTP; tmp = pj_strtoul2(profile, &endst, 16); if (endst.slen) return PJMEDIA_SDP_EINFMTP; fmtp->profile_idc = (pj_uint8_t)((tmp >> 16) & 0xFF); fmtp->profile_iop = (pj_uint8_t)((tmp >> 8) & 0xFF); fmtp->level = (pj_uint8_t)(tmp & 0xFF); for (i = 0; i < PJ_ARRAY_SIZE(level_info); ++i) { if (level_info[i].id == fmtp->level) { li = &level_info[i]; break; } } if (li == NULL) return PJMEDIA_SDP_EINFMTP; /* Init profile level spec */ if (fmtp->max_br == 0) fmtp->max_br = li->max_br; if (fmtp->max_mbps == 0) fmtp->max_mbps = li->max_mbps; if (fmtp->max_fs == 0) fmtp->max_fs = li->max_mb; return PJ_SUCCESS; } /* H264 fmtp parser */ PJ_DEF(pj_status_t) pjmedia_vid_codec_h264_parse_fmtp( const pjmedia_codec_fmtp *fmtp, pjmedia_vid_codec_h264_fmtp *h264_fmtp) { const pj_str_t PROFILE_LEVEL_ID = {"profile-level-id", 16}; const pj_str_t MAX_MBPS = {"max-mbps", 8}; const pj_str_t MAX_FS = {"max-fs", 6}; const pj_str_t MAX_CPB = {"max-cpb", 7}; const pj_str_t MAX_DPB = {"max-dpb", 7}; const pj_str_t MAX_BR = {"max-br", 6}; const pj_str_t PACKETIZATION_MODE = {"packetization-mode", 18}; const pj_str_t SPROP_PARAMETER_SETS = {"sprop-parameter-sets", 20}; unsigned i; pj_status_t status; pj_bzero(h264_fmtp, sizeof(*h264_fmtp)); for (i=0; icnt; ++i) { unsigned tmp; if (pj_stricmp(&fmtp->param[i].name, &PROFILE_LEVEL_ID)==0) { /* Init H264 parameters based on level, if not set yet */ status = init_h264_profile(&fmtp->param[i].val, h264_fmtp); if (status != PJ_SUCCESS) return status; } else if (pj_stricmp(&fmtp->param[i].name, &PACKETIZATION_MODE)==0) { tmp = pj_strtoul(&fmtp->param[i].val); if (tmp <= 2) h264_fmtp->packetization_mode = (pj_uint8_t)tmp; else return PJMEDIA_SDP_EINFMTP; } else if (pj_stricmp(&fmtp->param[i].name, &MAX_MBPS)==0) { tmp = pj_strtoul(&fmtp->param[i].val); h264_fmtp->max_mbps = PJ_MAX(tmp, h264_fmtp->max_mbps); } else if (pj_stricmp(&fmtp->param[i].name, &MAX_FS)==0) { tmp = pj_strtoul(&fmtp->param[i].val); h264_fmtp->max_fs = PJ_MAX(tmp, h264_fmtp->max_fs); } else if (pj_stricmp(&fmtp->param[i].name, &MAX_CPB)==0) { tmp = pj_strtoul(&fmtp->param[i].val); h264_fmtp->max_cpb = PJ_MAX(tmp, h264_fmtp->max_cpb); } else if (pj_stricmp(&fmtp->param[i].name, &MAX_DPB)==0) { tmp = pj_strtoul(&fmtp->param[i].val); h264_fmtp->max_dpb = PJ_MAX(tmp, h264_fmtp->max_dpb); } else if (pj_stricmp(&fmtp->param[i].name, &MAX_BR)==0) { tmp = pj_strtoul(&fmtp->param[i].val); h264_fmtp->max_br = PJ_MAX(tmp, h264_fmtp->max_br); } else if (pj_stricmp(&fmtp->param[i].name, &SPROP_PARAMETER_SETS)==0) { pj_str_t sps_st; sps_st = fmtp->param[i].val; while (sps_st.slen) { pj_str_t tmp_st; int tmp_len; const pj_uint8_t start_code[3] = {0, 0, 1}; char *p; pj_uint8_t *nal; pj_status_t status; /* Find field separator ',' */ tmp_st = sps_st; p = pj_strchr(&sps_st, ','); if (p) { tmp_st.slen = p - sps_st.ptr; sps_st.ptr = p+1; sps_st.slen -= (tmp_st.slen+1); } else { sps_st.slen = 0; } /* Decode field and build NAL unit for this param */ nal = &h264_fmtp->sprop_param_sets[ h264_fmtp->sprop_param_sets_len]; tmp_len = PJ_ARRAY_SIZE(h264_fmtp->sprop_param_sets) - (int)h264_fmtp->sprop_param_sets_len - PJ_ARRAY_SIZE(start_code); status = pj_base64_decode(&tmp_st, nal + PJ_ARRAY_SIZE(start_code), &tmp_len); if (status != PJ_SUCCESS) return PJMEDIA_SDP_EINFMTP; tmp_len += PJ_ARRAY_SIZE(start_code); pj_memcpy(nal, start_code, PJ_ARRAY_SIZE(start_code)); h264_fmtp->sprop_param_sets_len += tmp_len; } } } /* When profile-level-id is not specified, use default value "42000A" */ if (h264_fmtp->profile_idc == 0) { const pj_str_t DEF_PROFILE = {"42000A", 6}; status = init_h264_profile(&DEF_PROFILE, h264_fmtp); if (status != PJ_SUCCESS) return status; } return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_vid_codec_h264_match_sdp(pj_pool_t *pool, pjmedia_sdp_media *offer, unsigned o_fmt_idx, pjmedia_sdp_media *answer, unsigned a_fmt_idx, unsigned option) { const pj_str_t PROFILE_LEVEL_ID = {"profile-level-id", 16}; const pj_str_t PACKETIZATION_MODE = {"packetization-mode", 18}; pjmedia_codec_fmtp o_fmtp_raw, a_fmtp_raw; pjmedia_vid_codec_h264_fmtp o_fmtp, a_fmtp; pj_status_t status; PJ_UNUSED_ARG(pool); /* Parse offer */ status = pjmedia_stream_info_parse_fmtp( NULL, offer, pj_strtoul(&offer->desc.fmt[o_fmt_idx]), &o_fmtp_raw); if (status != PJ_SUCCESS) return status; status = pjmedia_vid_codec_h264_parse_fmtp(&o_fmtp_raw, &o_fmtp); if (status != PJ_SUCCESS) return status; /* Parse answer */ status = pjmedia_stream_info_parse_fmtp( NULL, answer, pj_strtoul(&answer->desc.fmt[a_fmt_idx]), &a_fmtp_raw); if (status != PJ_SUCCESS) return status; status = pjmedia_vid_codec_h264_parse_fmtp(&a_fmtp_raw, &a_fmtp); if (status != PJ_SUCCESS) return status; if (option & PJMEDIA_SDP_NEG_FMT_MATCH_ALLOW_MODIFY_ANSWER) { unsigned i; /* Flexible negotiation, adjust our answer to the offer. * Apply Postel's Principle (TM) in it's full glory. */ if (a_fmtp.profile_idc != o_fmtp.profile_idc) a_fmtp.profile_idc = o_fmtp.profile_idc; if (a_fmtp.profile_iop != o_fmtp.profile_iop) a_fmtp.profile_iop = o_fmtp.profile_iop; if (a_fmtp.packetization_mode >= o_fmtp.packetization_mode) a_fmtp.packetization_mode = o_fmtp.packetization_mode; /* Match them now */ #if H264_STRICT_SDP_NEGO if (a_fmtp.profile_idc != o_fmtp.profile_idc || a_fmtp.profile_iop != o_fmtp.profile_iop || a_fmtp.packetization_mode != o_fmtp.packetization_mode) { return PJMEDIA_SDP_EFORMATNOTEQUAL; } #else if (a_fmtp.profile_idc != o_fmtp.profile_idc) { return PJMEDIA_SDP_EFORMATNOTEQUAL; } #endif /* Update the answer */ for (i = 0; i < a_fmtp_raw.cnt; ++i) { if (pj_stricmp(&a_fmtp_raw.param[i].name, &PROFILE_LEVEL_ID) == 0) { char *p = a_fmtp_raw.param[i].val.ptr; pj_val_to_hex_digit(a_fmtp.profile_idc, p); p += 2; pj_val_to_hex_digit(a_fmtp.profile_iop, p); } else if (pj_stricmp(&a_fmtp_raw.param[i].name, &PACKETIZATION_MODE) == 0) { char *p = a_fmtp_raw.param[i].val.ptr; *p = '0' + a_fmtp.packetization_mode; } } } else { #if H264_STRICT_SDP_NEGO /* Strict negotiation */ if (a_fmtp.profile_idc != o_fmtp.profile_idc || a_fmtp.profile_iop != o_fmtp.profile_iop || a_fmtp.packetization_mode != o_fmtp.packetization_mode) { return PJMEDIA_SDP_EFORMATNOTEQUAL; } #else /* Permissive negotiation */ if (a_fmtp.profile_idc != o_fmtp.profile_idc) { return PJMEDIA_SDP_EFORMATNOTEQUAL; } #endif } return PJ_SUCCESS; } /* Find greatest common divisor (GCD) */ static unsigned gcd (unsigned a, unsigned b) { unsigned c; while (b) { c = a % b; a = b; b = c; } return a; } /* Find highest resolution possible for the specified H264 fmtp and * aspect ratio. */ static pj_status_t find_highest_res(pjmedia_vid_codec_h264_fmtp *fmtp, const pjmedia_ratio *fps, const pjmedia_ratio *ratio, pjmedia_rect_size *size, pj_bool_t is_decoding) { pjmedia_ratio def_ratio = { DEFAULT_H264_RATIO_NUM, DEFAULT_H264_RATIO_DENUM }; pjmedia_ratio def_fps = { DEFAULT_H264_FPS_NUM, DEFAULT_H264_FPS_DENUM }; pjmedia_ratio asp_ratio, the_fps; unsigned max_fs, g, scale; pj_assert(size); /* Get the ratio, or just use default if not provided. */ if (ratio && ratio->num && ratio->denum) { asp_ratio = *ratio; } else { asp_ratio = def_ratio; } /* Normalize the aspect ratio */ g = gcd(asp_ratio.num, asp_ratio.denum); asp_ratio.num /= g; asp_ratio.denum /= g; /* Get the frame rate, or just use default if not provided. */ if (fps && fps->num && fps->denum) { the_fps = *fps; } else { the_fps = def_fps; } /* Calculate maximum size (in macroblocks) */ max_fs = fmtp->max_mbps * the_fps.denum / the_fps.num; max_fs = PJ_MIN(max_fs, fmtp->max_fs); /* Check if the specified ratio is using big numbers * (not normalizable), override it! */ if ((int)max_fs < asp_ratio.num * asp_ratio.denum) { if ((int)max_fs >= def_ratio.num * def_ratio.denum) asp_ratio = def_ratio; else asp_ratio.num = asp_ratio.denum = 1; } /* Calculate the scale factor for size */ scale = pj_isqrt(max_fs / asp_ratio.denum / asp_ratio.num); /* Calculate the size, note that the frame size is in macroblock units */ size->w = asp_ratio.num * scale * 16; size->h = asp_ratio.denum * scale * 16; /* #1769: for decoding, size is usually used for allocating buffer, * so we need to make sure that frame size is not less than max_fs. */ if (is_decoding && ((size->w * size->h) >> 8) < max_fs) { /* Size is less than max_fs, recalculate using ratio 1:1 and * round up the scale. */ scale = pj_isqrt(max_fs) + 1; size->w = size->h = scale * 16; } return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_vid_codec_h264_apply_fmtp( pjmedia_vid_codec_param *param) { if (param->dir & PJMEDIA_DIR_ENCODING) { pjmedia_vid_codec_h264_fmtp fmtp; pjmedia_video_format_detail *vfd; pj_status_t status; /* Get remote param */ status = pjmedia_vid_codec_h264_parse_fmtp(¶m->enc_fmtp, &fmtp); if (status != PJ_SUCCESS) return status; /* Adjust fps, size, and bitrate to conform to H.264 level * specified by remote SDP fmtp. */ vfd = pjmedia_format_get_video_format_detail(¶m->enc_fmt, PJ_TRUE); if (vfd->fps.num == 0 || vfd->fps.denum == 0) { vfd->fps.num = DEFAULT_H264_FPS_NUM; vfd->fps.denum = DEFAULT_H264_FPS_DENUM; } if (vfd->size.w && vfd->size.h) { unsigned mb, mbps; /* Scale down the resolution if it exceeds profile spec */ mb = CALC_H264_MB_NUM(vfd->size); mbps = CALC_H264_MBPS(vfd->size, vfd->fps); if (mb > fmtp.max_fs || mbps > fmtp.max_mbps) { pjmedia_ratio r; r.num = vfd->size.w; r.denum = vfd->size.h; find_highest_res(&fmtp, &vfd->fps, &r, &vfd->size, PJ_FALSE); } } else { /* When not specified, just use the highest res possible*/ pjmedia_ratio r; r.num = vfd->size.w; r.denum = vfd->size.h; find_highest_res(&fmtp, &vfd->fps, &r, &vfd->size, PJ_FALSE); } /* Encoding bitrate must not be higher than H264 level spec */ if (vfd->avg_bps > fmtp.max_br * 1000) vfd->avg_bps = fmtp.max_br * 1000; if (vfd->max_bps > fmtp.max_br * 1000) vfd->max_bps = fmtp.max_br * 1000; } if (param->dir & PJMEDIA_DIR_DECODING) { /* Here we just want to find the highest resolution possible from the * fmtp and set it as the decoder param. */ pjmedia_vid_codec_h264_fmtp fmtp; pjmedia_video_format_detail *vfd; pjmedia_ratio r; pjmedia_rect_size highest_size; pj_status_t status; status = pjmedia_vid_codec_h264_parse_fmtp(¶m->dec_fmtp, &fmtp); if (status != PJ_SUCCESS) return status; vfd = pjmedia_format_get_video_format_detail(¶m->dec_fmt, PJ_TRUE); if (vfd->fps.num == 0 || vfd->fps.denum == 0) { vfd->fps.num = DEFAULT_H264_FPS_NUM; vfd->fps.denum = DEFAULT_H264_FPS_DENUM; } /* Normalize decoding resolution, i.e: it must not be lower than * the H264 profile level setting used, as this may be used by * app to allocate buffer. */ r.num = vfd->size.w; r.denum = vfd->size.h; find_highest_res(&fmtp, &vfd->fps, &r, &highest_size, PJ_TRUE); if (vfd->size.w * vfd->size.h < highest_size.w * highest_size.h) vfd->size = highest_size; /* Normalize decoding bitrate based on H264 level spec */ if (vfd->avg_bps < fmtp.max_br * 1000) vfd->avg_bps = fmtp.max_br * 1000; if (vfd->max_bps < fmtp.max_br * 1000) vfd->max_bps = fmtp.max_br * 1000; } return PJ_SUCCESS; } #endif /* PJMEDIA_HAS_VIDEO */ diff --git a/deps/pjsip/pjmedia/src/pjmedia/vid_port.c b/deps/pjsip/pjmedia/src/pjmedia/vid_port.c index 8c8aaa08..3276e52a 100644 --- a/deps/pjsip/pjmedia/src/pjmedia/vid_port.c +++ b/deps/pjsip/pjmedia/src/pjmedia/vid_port.c @@ -1,1321 +1,1321 @@ -/* $Id: vid_port.c 4290 2012-11-01 03:06:33Z ming $ */ +/* $Id: vid_port.c 5149 2015-08-06 07:10:33Z nanang $ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) #define SIGNATURE PJMEDIA_SIG_VID_PORT #define THIS_FILE "vid_port.c" /* Enable/disable test of finding closest format algo */ #define ENABLE_TEST_FIND_FMT 0 /** * Enable this to trace the format matching process. */ #if 0 # define TRACE_FIND_FMT(args) PJ_LOG(5,args) #else # define TRACE_FIND_FMT(args) #endif /** * We use nearest width and aspect ratio to find match between the requested * format and the supported format. Specify this to determine the array size * of the supported formats with the nearest width. From this array, we will * find the one with lowest diff_ratio. Setting this to 1 will thus skip * the aspect ratio calculation. */ #ifndef PJMEDIA_VID_PORT_MATCH_WIDTH_ARRAY_SIZE # define PJMEDIA_VID_PORT_MATCH_WIDTH_ARRAY_SIZE 3 #endif typedef struct vid_pasv_port vid_pasv_port; enum role { ROLE_NONE, ROLE_ACTIVE, ROLE_PASSIVE }; enum fmt_match { FMT_MATCH, FMT_SAME_COLOR_SPACE, FMT_DIFF_COLOR_SPACE }; struct pjmedia_vid_port { pj_pool_t *pool; pj_str_t dev_name; pjmedia_dir dir; // pjmedia_rect_size cap_size; pjmedia_vid_dev_stream *strm; pjmedia_vid_dev_cb strm_cb; void *strm_cb_data; enum role role, stream_role; vid_pasv_port *pasv_port; pjmedia_port *client_port; pj_bool_t destroy_client_port; struct { pjmedia_converter *conv; void *conv_buf; pj_size_t conv_buf_size; pjmedia_conversion_param conv_param; unsigned usec_ctr; unsigned usec_src, usec_dst; } conv; pjmedia_clock *clock; pjmedia_clock_src clocksrc; struct sync_clock_src_t { pjmedia_clock_src *sync_clocksrc; pj_int32_t sync_delta; unsigned max_sync_ticks; unsigned nsync_frame; unsigned nsync_progress; } sync_clocksrc; pjmedia_frame *frm_buf; pj_size_t frm_buf_size; pj_mutex_t *frm_mutex; }; struct vid_pasv_port { pjmedia_port base; pjmedia_vid_port *vp; }; struct fmt_prop { pj_uint32_t id; pjmedia_rect_size size; pjmedia_ratio fps; }; static pj_status_t vidstream_cap_cb(pjmedia_vid_dev_stream *stream, void *user_data, pjmedia_frame *frame); static pj_status_t vidstream_render_cb(pjmedia_vid_dev_stream *stream, void *user_data, pjmedia_frame *frame); static pj_status_t vidstream_event_cb(pjmedia_event *event, void *user_data); static pj_status_t client_port_event_cb(pjmedia_event *event, void *user_data); static void enc_clock_cb(const pj_timestamp *ts, void *user_data); static void dec_clock_cb(const pj_timestamp *ts, void *user_data); static pj_status_t vid_pasv_port_put_frame(struct pjmedia_port *this_port, pjmedia_frame *frame); static pj_status_t vid_pasv_port_get_frame(struct pjmedia_port *this_port, pjmedia_frame *frame); PJ_DEF(void) pjmedia_vid_port_param_default(pjmedia_vid_port_param *prm) { pj_bzero(prm, sizeof(*prm)); prm->active = PJ_TRUE; } static const char *vid_dir_name(pjmedia_dir dir) { switch (dir) { case PJMEDIA_DIR_CAPTURE: return "capture"; case PJMEDIA_DIR_RENDER: return "render"; default: return "??"; } } static pj_status_t create_converter(pjmedia_vid_port *vp) { if (vp->conv.conv) { pjmedia_converter_destroy(vp->conv.conv); vp->conv.conv = NULL; } /* Instantiate converter if necessary */ if (vp->conv.conv_param.src.id != vp->conv.conv_param.dst.id || (vp->conv.conv_param.src.det.vid.size.w != vp->conv.conv_param.dst.det.vid.size.w) || (vp->conv.conv_param.src.det.vid.size.h != vp->conv.conv_param.dst.det.vid.size.h)) { pj_status_t status; /* Yes, we need converter */ status = pjmedia_converter_create(NULL, vp->pool, &vp->conv.conv_param, &vp->conv.conv); if (status != PJ_SUCCESS) { PJ_PERROR(4,(THIS_FILE, status, "Error creating converter")); return status; } } if (vp->conv.conv || (vp->role==ROLE_ACTIVE && (vp->dir & PJMEDIA_DIR_ENCODING))) { pj_status_t status; const pjmedia_video_format_info *vfi; pjmedia_video_apply_fmt_param vafp; /* Allocate buffer for conversion */ vfi = pjmedia_get_video_format_info(NULL, vp->conv.conv_param.dst.id); if (!vfi) return PJMEDIA_EBADFMT; pj_bzero(&vafp, sizeof(vafp)); vafp.size = vp->conv.conv_param.dst.det.vid.size; status = vfi->apply_fmt(vfi, &vafp); if (status != PJ_SUCCESS) return PJMEDIA_EBADFMT; if (vafp.framebytes > vp->conv.conv_buf_size) { vp->conv.conv_buf = pj_pool_alloc(vp->pool, vafp.framebytes); vp->conv.conv_buf_size = vafp.framebytes; } } vp->conv.usec_ctr = 0; vp->conv.usec_src = PJMEDIA_PTIME(&vp->conv.conv_param.src.det.vid.fps); vp->conv.usec_dst = PJMEDIA_PTIME(&vp->conv.conv_param.dst.det.vid.fps); return PJ_SUCCESS; } static pj_uint32_t match_format_id(pj_uint32_t req_id, pj_uint32_t sup_id) { const pjmedia_video_format_info *req_fmt_info, *sup_fmt_info; if (req_id == sup_id) return FMT_MATCH; req_fmt_info = pjmedia_get_video_format_info( pjmedia_video_format_mgr_instance(), req_id); sup_fmt_info = pjmedia_get_video_format_info( pjmedia_video_format_mgr_instance(), sup_id); if ((req_fmt_info == NULL) || (sup_fmt_info == NULL)) { return FMT_DIFF_COLOR_SPACE; } if (req_fmt_info->color_model == sup_fmt_info->color_model) { return FMT_SAME_COLOR_SPACE; } return FMT_DIFF_COLOR_SPACE; } static pj_uint32_t get_match_format_id(pj_uint32_t req_fmt_id, pjmedia_vid_dev_info *di) { unsigned i, match_idx = 0, match_fmt = FMT_DIFF_COLOR_SPACE+1; /* Find the matching format. If no exact match is found, find * the supported format with the same color space. If no match is found, * use the first supported format on the list. */ for (i = 0; i < di->fmt_cnt; ++i) { unsigned tmp_fmt = match_format_id(req_fmt_id, di->fmt[i].id); if (match_fmt == FMT_MATCH) return req_fmt_id; if (tmp_fmt < match_fmt) { match_idx = i; match_fmt = tmp_fmt; } } return di->fmt[match_idx].id; } /** * Find the closest supported format from the specific requested format. * The algo is to find a supported size with the matching format id, width and * lowest diff_ratio. * --- * For format id matching, the priority is: * 1. Find exact match * 2. Find format with the same color space * 3. Use the first supported format. * --- * For ratio matching: * Find the lowest difference of the aspect ratio between the requested and * the supported format. */ static struct fmt_prop find_closest_fmt(pj_uint32_t req_fmt_id, pjmedia_rect_size *req_fmt_size, pjmedia_ratio *req_fmt_fps, pjmedia_vid_dev_info *di) { unsigned i, match_idx = 0; pj_uint32_t match_fmt_id; float req_ratio, min_diff_ratio = 0.0; struct fmt_prop ret_prop; pj_bool_t found_exact_match = PJ_FALSE; #define GET_DIFF(x, y) ((x) > (y)? (x-y) : (y-x)) /* This will contain the supported format with lowest width difference */ pjmedia_rect_size nearest_width[PJMEDIA_VID_PORT_MATCH_WIDTH_ARRAY_SIZE]; /* Initialize the list. */ for (i=0;ifmt_cnt;++i) { pjmedia_video_format_detail *vfd; unsigned diff_width1, diff_width2; /* Ignore supported format with different format id. */ if (di->fmt[i].id != match_fmt_id) continue; vfd = pjmedia_format_get_video_format_detail(&di->fmt[i], PJ_TRUE); /* Exact match found. */ if ((vfd->size.w == req_fmt_size->w) && (vfd->size.h == req_fmt_size->h)) { nearest_width[0] = vfd->size; found_exact_match = PJ_TRUE; break; } diff_width1 = GET_DIFF(vfd->size.w, req_fmt_size->w); diff_width2 = GET_DIFF(nearest_width[0].w, req_fmt_size->w); /* Fill the nearest width list. */ if (diff_width1 <= diff_width2) { int k = 1; pjmedia_rect_size tmp_size = vfd->size; while(((GET_DIFF(tmp_size.w, req_fmt_size->w) < (GET_DIFF(nearest_width[k].w, req_fmt_size->w))) && (k < PJ_ARRAY_SIZE(nearest_width)))) { nearest_width[k-1] = nearest_width[k]; ++k; } nearest_width[k-1] = tmp_size; } } /* No need to calculate ratio if exact match is found. */ if (!found_exact_match) { pj_bool_t found_match = PJ_FALSE; /* We have the list of supported format with nearest width. Now get the * best ratio. */ req_ratio = (float)req_fmt_size->w / (float)req_fmt_size->h; for (i=0;isize.w, vid_fd->size.h, vid_fd->fps.num, vid_fd->fps.denum)); } for (i = 0; i < PJ_ARRAY_SIZE(find_id); i++) { for (j = 0; j < PJ_ARRAY_SIZE(find_fps); j++) { for (k = 0; k < PJ_ARRAY_SIZE(find_size); k++) { struct fmt_prop match_prop; pjmedia_fourcc_name(find_id[i], fmt_name); TRACE_FIND_FMT((THIS_FILE, "Trying to find closest match " "id:%s size:%dx%d fps:%d/%d", fmt_name, find_size[k].w, find_size[k].h, find_fps[j].num, find_fps[j].denum)); match_prop = find_closest_fmt(find_id[i], &find_size[k], &find_fps[j], di); if ((match_prop.id == find_id[i]) && (match_prop.size.w == find_size[k].w) && (match_prop.size.h == find_size[k].h) && (match_prop.fps.num / match_prop.fps.denum == find_fps[j].num * find_fps[j].denum)) { TRACE_FIND_FMT((THIS_FILE, "Exact Match found!!")); } else { pjmedia_fourcc_name(match_prop.id, fmt_name); TRACE_FIND_FMT((THIS_FILE, "Closest format = "\ "id:%s size:%dx%d fps:%d/%d", fmt_name, match_prop.size.w, match_prop.size.h, match_prop.fps.num, match_prop.fps.denum)); } } } } } #endif PJ_DEF(pj_status_t) pjmedia_vid_port_create( pj_pool_t *pool, const pjmedia_vid_port_param *prm, pjmedia_vid_port **p_vid_port) { pjmedia_vid_port *vp; pjmedia_video_format_detail *vfd; char dev_name[64]; char fmt_name[5]; pjmedia_vid_dev_cb vid_cb; pj_bool_t need_frame_buf = PJ_FALSE; pj_status_t status; unsigned ptime_usec; pjmedia_vid_dev_param vparam; pjmedia_vid_dev_info di; PJ_ASSERT_RETURN(pool && prm && p_vid_port, PJ_EINVAL); PJ_ASSERT_RETURN(prm->vidparam.fmt.type == PJMEDIA_TYPE_VIDEO && prm->vidparam.dir != PJMEDIA_DIR_NONE && prm->vidparam.dir != PJMEDIA_DIR_CAPTURE_RENDER, PJ_EINVAL); /* Retrieve the video format detail */ vfd = pjmedia_format_get_video_format_detail(&prm->vidparam.fmt, PJ_TRUE); if (!vfd) return PJ_EINVAL; PJ_ASSERT_RETURN(vfd->fps.num, PJ_EINVAL); /* Allocate videoport */ vp = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_port); vp->pool = pj_pool_create(pool->factory, "video port", 500, 500, NULL); vp->role = prm->active ? ROLE_ACTIVE : ROLE_PASSIVE; vp->dir = prm->vidparam.dir; // vp->cap_size = vfd->size; vparam = prm->vidparam; dev_name[0] = '\0'; /* Get device info */ if (vp->dir & PJMEDIA_DIR_CAPTURE) status = pjmedia_vid_dev_get_info(prm->vidparam.cap_id, &di); else status = pjmedia_vid_dev_get_info(prm->vidparam.rend_id, &di); if (status != PJ_SUCCESS) return status; pj_ansi_snprintf(dev_name, sizeof(dev_name), "%s [%s]", di.name, di.driver); pjmedia_fourcc_name(vparam.fmt.id, fmt_name); PJ_LOG(4,(THIS_FILE, "Opening device %s for %s: format=%s, size=%dx%d @%d:%d fps", dev_name, vid_dir_name(prm->vidparam.dir), fmt_name, vfd->size.w, vfd->size.h, vfd->fps.num, vfd->fps.denum)); if (di.dir == PJMEDIA_DIR_RENDER) { /* Find the matching format. If no exact match is found, find * the supported format with the same color space. If no match is found, * use the first supported format on the list. */ pj_assert(di.fmt_cnt != 0); vparam.fmt.id = get_match_format_id(prm->vidparam.fmt.id, &di); } else { struct fmt_prop match_prop; if (di.fmt_cnt == 0) { status = PJMEDIA_EVID_SYSERR; PJ_PERROR(4,(THIS_FILE, status, "Device has no supported format")); return status; } #if ENABLE_TEST_FIND_FMT test_find_closest_fmt(&di); #endif match_prop = find_closest_fmt(prm->vidparam.fmt.id, &vfd->size, &vfd->fps, &di); if ((match_prop.id != prm->vidparam.fmt.id) || (match_prop.size.w != vfd->size.w) || (match_prop.size.h != vfd->size.h)) { vparam.fmt.id = match_prop.id; vparam.fmt.det.vid.size = match_prop.size; } } pj_strdup2_with_null(pool, &vp->dev_name, di.name); vp->stream_role = di.has_callback ? ROLE_ACTIVE : ROLE_PASSIVE; ptime_usec = PJMEDIA_PTIME(&vfd->fps); pjmedia_clock_src_init(&vp->clocksrc, PJMEDIA_TYPE_VIDEO, prm->vidparam.clock_rate, ptime_usec); vp->sync_clocksrc.max_sync_ticks = PJMEDIA_CLOCK_SYNC_MAX_RESYNC_DURATION * 1000 / vp->clocksrc.ptime_usec; /* Create the video stream */ pj_bzero(&vid_cb, sizeof(vid_cb)); vid_cb.capture_cb = &vidstream_cap_cb; vid_cb.render_cb = &vidstream_render_cb; status = pjmedia_vid_dev_stream_create(&vparam, &vid_cb, vp, &vp->strm); if (status != PJ_SUCCESS) goto on_error; PJ_LOG(4,(THIS_FILE, "Device %s opened: format=%s, size=%dx%d @%d:%d fps", dev_name, fmt_name, vparam.fmt.det.vid.size.w, vparam.fmt.det.vid.size.h, vparam.fmt.det.vid.fps.num, vparam.fmt.det.vid.fps.denum)); /* Subscribe to device's events */ pjmedia_event_subscribe(NULL, &vidstream_event_cb, vp, vp->strm); if (vp->dir & PJMEDIA_DIR_CAPTURE) { pjmedia_format_copy(&vp->conv.conv_param.src, &vparam.fmt); pjmedia_format_copy(&vp->conv.conv_param.dst, &prm->vidparam.fmt); } else { pjmedia_format_copy(&vp->conv.conv_param.src, &prm->vidparam.fmt); pjmedia_format_copy(&vp->conv.conv_param.dst, &vparam.fmt); } status = create_converter(vp); if (status != PJ_SUCCESS) goto on_error; if (vp->role==ROLE_ACTIVE && ((vp->dir & PJMEDIA_DIR_ENCODING) || vp->stream_role==ROLE_PASSIVE)) { pjmedia_clock_param param; /* Active role is wanted, but our device is passive, so create * master clocks to run the media flow. For encoding direction, * we also want to create our own clock since the device's clock * may run at a different rate. */ need_frame_buf = PJ_TRUE; param.usec_interval = PJMEDIA_PTIME(&vfd->fps); param.clock_rate = prm->vidparam.clock_rate; status = pjmedia_clock_create2(pool, ¶m, PJMEDIA_CLOCK_NO_HIGHEST_PRIO, (vp->dir & PJMEDIA_DIR_ENCODING) ? &enc_clock_cb: &dec_clock_cb, vp, &vp->clock); if (status != PJ_SUCCESS) goto on_error; } else if (vp->role==ROLE_PASSIVE) { vid_pasv_port *pp; /* Always need to create media port for passive role */ vp->pasv_port = pp = PJ_POOL_ZALLOC_T(pool, vid_pasv_port); pp->vp = vp; pp->base.get_frame = &vid_pasv_port_get_frame; pp->base.put_frame = &vid_pasv_port_put_frame; pjmedia_port_info_init2(&pp->base.info, &vp->dev_name, PJMEDIA_SIG_VID_PORT, prm->vidparam.dir, &prm->vidparam.fmt); need_frame_buf = PJ_TRUE; } if (need_frame_buf) { const pjmedia_video_format_info *vfi; pjmedia_video_apply_fmt_param vafp; vfi = pjmedia_get_video_format_info(NULL, vparam.fmt.id); if (!vfi) { status = PJ_ENOTFOUND; goto on_error; } pj_bzero(&vafp, sizeof(vafp)); vafp.size = vparam.fmt.det.vid.size; status = vfi->apply_fmt(vfi, &vafp); if (status != PJ_SUCCESS) goto on_error; vp->frm_buf = PJ_POOL_ZALLOC_T(pool, pjmedia_frame); vp->frm_buf_size = vafp.framebytes; vp->frm_buf->buf = pj_pool_alloc(pool, vafp.framebytes); vp->frm_buf->size = vp->frm_buf_size; vp->frm_buf->type = PJMEDIA_FRAME_TYPE_NONE; status = pj_mutex_create_simple(pool, vp->dev_name.ptr, &vp->frm_mutex); if (status != PJ_SUCCESS) goto on_error; } *p_vid_port = vp; return PJ_SUCCESS; on_error: pjmedia_vid_port_destroy(vp); return status; } PJ_DEF(void) pjmedia_vid_port_set_cb(pjmedia_vid_port *vid_port, const pjmedia_vid_dev_cb *cb, void *user_data) { pj_assert(vid_port && cb); pj_memcpy(&vid_port->strm_cb, cb, sizeof(*cb)); vid_port->strm_cb_data = user_data; } PJ_DEF(pjmedia_vid_dev_stream*) pjmedia_vid_port_get_stream(pjmedia_vid_port *vp) { PJ_ASSERT_RETURN(vp, NULL); return vp->strm; } PJ_DEF(pjmedia_port*) pjmedia_vid_port_get_passive_port(pjmedia_vid_port *vp) { PJ_ASSERT_RETURN(vp && vp->role==ROLE_PASSIVE, NULL); return &vp->pasv_port->base; } PJ_DEF(pjmedia_clock_src *) pjmedia_vid_port_get_clock_src( pjmedia_vid_port *vid_port ) { PJ_ASSERT_RETURN(vid_port, NULL); return &vid_port->clocksrc; } PJ_DECL(pj_status_t) pjmedia_vid_port_set_clock_src( pjmedia_vid_port *vid_port, pjmedia_clock_src *clocksrc) { PJ_ASSERT_RETURN(vid_port && clocksrc, PJ_EINVAL); vid_port->sync_clocksrc.sync_clocksrc = clocksrc; vid_port->sync_clocksrc.sync_delta = pjmedia_clock_src_get_time_msec(&vid_port->clocksrc) - pjmedia_clock_src_get_time_msec(clocksrc); return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_vid_port_connect(pjmedia_vid_port *vp, pjmedia_port *port, pj_bool_t destroy) { PJ_ASSERT_RETURN(vp && vp->role==ROLE_ACTIVE, PJ_EINVAL); vp->destroy_client_port = destroy; vp->client_port = port; /* Subscribe to client port's events */ pjmedia_event_subscribe(NULL, &client_port_event_cb, vp, vp->client_port); return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_vid_port_disconnect(pjmedia_vid_port *vp) { PJ_ASSERT_RETURN(vp && vp->role==ROLE_ACTIVE, PJ_EINVAL); pjmedia_event_unsubscribe(NULL, &client_port_event_cb, vp, vp->client_port); vp->client_port = NULL; return PJ_SUCCESS; } PJ_DEF(pjmedia_port*) pjmedia_vid_port_get_connected_port(pjmedia_vid_port *vp) { PJ_ASSERT_RETURN(vp && vp->role==ROLE_ACTIVE, NULL); return vp->client_port; } PJ_DEF(pj_status_t) pjmedia_vid_port_start(pjmedia_vid_port *vp) { pj_status_t status; PJ_ASSERT_RETURN(vp, PJ_EINVAL); status = pjmedia_vid_dev_stream_start(vp->strm); if (status != PJ_SUCCESS) goto on_error; if (vp->clock) { status = pjmedia_clock_start(vp->clock); if (status != PJ_SUCCESS) goto on_error; } return PJ_SUCCESS; on_error: pjmedia_vid_port_stop(vp); return status; } PJ_DEF(pj_bool_t) pjmedia_vid_port_is_running(pjmedia_vid_port *vp) { return pjmedia_vid_dev_stream_is_running(vp->strm); } PJ_DEF(pj_status_t) pjmedia_vid_port_stop(pjmedia_vid_port *vp) { pj_status_t status; PJ_ASSERT_RETURN(vp, PJ_EINVAL); if (vp->clock) { status = pjmedia_clock_stop(vp->clock); } status = pjmedia_vid_dev_stream_stop(vp->strm); return status; } PJ_DEF(void) pjmedia_vid_port_destroy(pjmedia_vid_port *vp) { PJ_ASSERT_ON_FAIL(vp, return); PJ_LOG(4,(THIS_FILE, "Closing %s..", vp->dev_name.ptr)); if (vp->clock) { pjmedia_clock_destroy(vp->clock); vp->clock = NULL; } if (vp->strm) { pjmedia_event_unsubscribe(NULL, &vidstream_event_cb, vp, vp->strm); pjmedia_vid_dev_stream_destroy(vp->strm); vp->strm = NULL; } if (vp->client_port) { pjmedia_event_unsubscribe(NULL, &client_port_event_cb, vp, vp->client_port); if (vp->destroy_client_port) pjmedia_port_destroy(vp->client_port); vp->client_port = NULL; } if (vp->frm_mutex) { pj_mutex_destroy(vp->frm_mutex); vp->frm_mutex = NULL; } if (vp->conv.conv) { pjmedia_converter_destroy(vp->conv.conv); vp->conv.conv = NULL; } pj_pool_release(vp->pool); } /* static void save_rgb_frame(int width, int height, const pjmedia_frame *frm) { static int counter; FILE *pFile; char szFilename[32]; const pj_uint8_t *pFrame = (const pj_uint8_t*)frm->buf; int y; if (counter > 10) return; // Open file sprintf(szFilename, "frame%02d.ppm", counter++); pFile=fopen(szFilename, "wb"); if(pFile==NULL) return; // Write header fprintf(pFile, "P6\n%d %d\n255\n", width, height); // Write pixel data for(y=0; ytype == PJMEDIA_EVENT_FMT_CHANGED) { const pjmedia_video_format_detail *vfd; const pjmedia_video_format_detail *vfd_cur; pjmedia_vid_dev_param vid_param; pj_status_t status; /* Retrieve the current video format detail */ pjmedia_vid_dev_stream_get_param(vp->strm, &vid_param); vfd_cur = pjmedia_format_get_video_format_detail( &vid_param.fmt, PJ_TRUE); if (!vfd_cur) return PJMEDIA_EVID_BADFORMAT; /* Retrieve the new video format detail */ vfd = pjmedia_format_get_video_format_detail( &event->data.fmt_changed.new_fmt, PJ_TRUE); if (!vfd || !vfd->fps.num || !vfd->fps.denum) return PJMEDIA_EVID_BADFORMAT; /* Ticket #1876: if this is a passive renderer and only frame rate is * changing, simply modify the clock. */ if (vp->dir == PJMEDIA_DIR_RENDER && vp->stream_role == ROLE_PASSIVE && vp->role == ROLE_ACTIVE) { pj_bool_t fps_only; pjmedia_video_format_detail tmp_vfd; tmp_vfd = *vfd_cur; tmp_vfd.fps = vfd->fps; fps_only = pj_memcmp(vfd, &tmp_vfd, sizeof(*vfd)) == 0; if (fps_only) { pjmedia_clock_param clock_param; clock_param.usec_interval = PJMEDIA_PTIME(&vfd->fps); clock_param.clock_rate = vid_param.clock_rate; pjmedia_clock_modify(vp->clock, &clock_param); return pjmedia_event_publish(NULL, vp, event, PJMEDIA_EVENT_PUBLISH_POST_EVENT); } } /* Ticket #1827: * Stopping video port should not be necessary here because * it will also try to stop the clock, from inside the clock's * own thread, so it may get stuck. We just stop the video device * stream instead. * pjmedia_vid_port_stop(vp); */ pjmedia_vid_dev_stream_stop(vp->strm); /* Change the destination format to the new format */ pjmedia_format_copy(&vp->conv.conv_param.src, &event->data.fmt_changed.new_fmt); /* Only copy the size here */ vp->conv.conv_param.dst.det.vid.size = event->data.fmt_changed.new_fmt.det.vid.size; status = create_converter(vp); if (status != PJ_SUCCESS) { PJ_PERROR(4,(THIS_FILE, status, "Error recreating converter")); return status; } if (vid_param.fmt.id != vp->conv.conv_param.dst.id || (vid_param.fmt.det.vid.size.h != vp->conv.conv_param.dst.det.vid.size.h) || (vid_param.fmt.det.vid.size.w != vp->conv.conv_param.dst.det.vid.size.w)) { status = pjmedia_vid_dev_stream_set_cap(vp->strm, PJMEDIA_VID_DEV_CAP_FORMAT, &vp->conv.conv_param.dst); if (status != PJ_SUCCESS) { PJ_LOG(3, (THIS_FILE, "failure in changing the format of the " "video device")); PJ_LOG(3, (THIS_FILE, "reverting to its original format: %s", status != PJMEDIA_EVID_ERR ? "success" : "failure")); pjmedia_vid_port_start(vp); return status; } } if (vp->stream_role == ROLE_PASSIVE) { pjmedia_clock_param clock_param; /** * Initially, frm_buf was allocated the biggest * supported size, so we do not need to re-allocate * the buffer here. */ /* Adjust the clock */ clock_param.usec_interval = PJMEDIA_PTIME(&vfd->fps); clock_param.clock_rate = vid_param.clock_rate; pjmedia_clock_modify(vp->clock, &clock_param); } /* pjmedia_vid_port_start(vp); */ pjmedia_vid_dev_stream_start(vp->strm); } /* Republish the event, post the event to the event manager * to avoid deadlock if vidport is trying to stop the clock. */ return pjmedia_event_publish(NULL, vp, event, PJMEDIA_EVENT_PUBLISH_POST_EVENT); } static pj_status_t convert_frame(pjmedia_vid_port *vp, pjmedia_frame *src_frame, pjmedia_frame *dst_frame) { pj_status_t status = PJ_SUCCESS; if (vp->conv.conv) { if (!dst_frame->buf || dst_frame->size < vp->conv.conv_buf_size) { dst_frame->buf = vp->conv.conv_buf; dst_frame->size = vp->conv.conv_buf_size; } status = pjmedia_converter_convert(vp->conv.conv, src_frame, dst_frame); } return status; } /* Copy frame to buffer. */ static void copy_frame_to_buffer(pjmedia_vid_port *vp, pjmedia_frame *frame) { pj_mutex_lock(vp->frm_mutex); pjmedia_frame_copy(vp->frm_buf, frame); pj_mutex_unlock(vp->frm_mutex); } /* Get frame from buffer and convert it if necessary. */ static pj_status_t get_frame_from_buffer(pjmedia_vid_port *vp, pjmedia_frame *frame) { pj_status_t status = PJ_SUCCESS; pj_mutex_lock(vp->frm_mutex); if (vp->conv.conv) status = convert_frame(vp, vp->frm_buf, frame); else pjmedia_frame_copy(frame, vp->frm_buf); pj_mutex_unlock(vp->frm_mutex); return status; } static void enc_clock_cb(const pj_timestamp *ts, void *user_data) { /* We are here because user wants us to be active but the stream is * passive. So get a frame from the stream and push it to user. */ pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data; pjmedia_frame frame_; pj_status_t status = PJ_SUCCESS; pj_assert(vp->role==ROLE_ACTIVE); PJ_UNUSED_ARG(ts); if (!vp->client_port) return; if (vp->stream_role == ROLE_PASSIVE) { while (vp->conv.usec_ctr < vp->conv.usec_dst) { vp->frm_buf->size = vp->frm_buf_size; status = pjmedia_vid_dev_stream_get_frame(vp->strm, vp->frm_buf); vp->conv.usec_ctr += vp->conv.usec_src; } vp->conv.usec_ctr -= vp->conv.usec_dst; if (status != PJ_SUCCESS) return; } //save_rgb_frame(vp->cap_size.w, vp->cap_size.h, vp->frm_buf); frame_.buf = vp->conv.conv_buf; frame_.size = vp->conv.conv_buf_size; status = get_frame_from_buffer(vp, &frame_); if (status != PJ_SUCCESS) return; status = pjmedia_port_put_frame(vp->client_port, &frame_); if (status != PJ_SUCCESS) return; } static void dec_clock_cb(const pj_timestamp *ts, void *user_data) { /* We are here because user wants us to be active but the stream is * passive. So get a frame from the stream and push it to user. */ pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data; pj_status_t status; pjmedia_frame frame; pj_assert(vp->role==ROLE_ACTIVE && vp->stream_role==ROLE_PASSIVE); PJ_UNUSED_ARG(ts); if (!vp->client_port) return; status = vidstream_render_cb(vp->strm, vp, &frame); if (status != PJ_SUCCESS) return; if (frame.size > 0) status = pjmedia_vid_dev_stream_put_frame(vp->strm, &frame); } static pj_status_t vidstream_cap_cb(pjmedia_vid_dev_stream *stream, void *user_data, pjmedia_frame *frame) { pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data; /* We just store the frame in the buffer. For active role, we let * video port's clock to push the frame buffer to the user. * The decoding counterpart for passive role and active stream is * located in vid_pasv_port_put_frame() */ copy_frame_to_buffer(vp, frame); /* This is tricky since the frame is still in its original unconverted * format, which may not be what the application expects. */ if (vp->strm_cb.capture_cb) return (*vp->strm_cb.capture_cb)(stream, vp->strm_cb_data, frame); return PJ_SUCCESS; } static pj_status_t vidstream_render_cb(pjmedia_vid_dev_stream *stream, void *user_data, pjmedia_frame *frame) { pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data; pj_status_t status = PJ_SUCCESS; pj_bzero(frame, sizeof(pjmedia_frame)); if (vp->role==ROLE_ACTIVE) { unsigned frame_ts = vp->clocksrc.clock_rate / 1000 * vp->clocksrc.ptime_usec / 1000; if (!vp->client_port) return status; if (vp->sync_clocksrc.sync_clocksrc) { pjmedia_clock_src *src = vp->sync_clocksrc.sync_clocksrc; pj_int32_t diff; unsigned nsync_frame; /* Synchronization */ /* Calculate the time difference (in ms) with the sync source */ diff = pjmedia_clock_src_get_time_msec(&vp->clocksrc) - pjmedia_clock_src_get_time_msec(src) - vp->sync_clocksrc.sync_delta; /* Check whether sync source made a large jump */ if (diff < 0 && -diff > PJMEDIA_CLOCK_SYNC_MAX_SYNC_MSEC) { pjmedia_clock_src_update(&vp->clocksrc, NULL); vp->sync_clocksrc.sync_delta = pjmedia_clock_src_get_time_msec(src) - pjmedia_clock_src_get_time_msec(&vp->clocksrc); vp->sync_clocksrc.nsync_frame = 0; return status; } /* Calculate the difference (in frames) with the sync source */ nsync_frame = abs(diff) * 1000 / vp->clocksrc.ptime_usec; if (nsync_frame == 0) { /* Nothing to sync */ vp->sync_clocksrc.nsync_frame = 0; } else { pj_int32_t init_sync_frame = nsync_frame; /* Check whether it's a new sync or whether we need to reset * the sync */ if (vp->sync_clocksrc.nsync_frame == 0 || (vp->sync_clocksrc.nsync_frame > 0 && nsync_frame > vp->sync_clocksrc.nsync_frame)) { vp->sync_clocksrc.nsync_frame = nsync_frame; vp->sync_clocksrc.nsync_progress = 0; } else { init_sync_frame = vp->sync_clocksrc.nsync_frame; } if (diff >= 0) { unsigned skip_mod; /* We are too fast */ if (vp->sync_clocksrc.max_sync_ticks > 0) { skip_mod = init_sync_frame / vp->sync_clocksrc.max_sync_ticks + 2; } else skip_mod = init_sync_frame + 2; PJ_LOG(5, (THIS_FILE, "synchronization: early by %d ms", diff)); /* We'll play a frame every skip_mod-th tick instead of * a complete pause */ if (++vp->sync_clocksrc.nsync_progress % skip_mod > 0) { pjmedia_clock_src_update(&vp->clocksrc, NULL); return status; } } else { unsigned i, ndrop = init_sync_frame; /* We are too late, drop the frame */ if (vp->sync_clocksrc.max_sync_ticks > 0) { ndrop /= vp->sync_clocksrc.max_sync_ticks; ndrop++; } PJ_LOG(5, (THIS_FILE, "synchronization: late, " "dropping %d frame(s)", ndrop)); if (ndrop >= nsync_frame) { vp->sync_clocksrc.nsync_frame = 0; ndrop = nsync_frame; } else vp->sync_clocksrc.nsync_progress += ndrop; for (i = 0; i < ndrop; i++) { vp->frm_buf->size = vp->frm_buf_size; status = pjmedia_port_get_frame(vp->client_port, vp->frm_buf); if (status != PJ_SUCCESS) { pjmedia_clock_src_update(&vp->clocksrc, NULL); return status; } pj_add_timestamp32(&vp->clocksrc.timestamp, frame_ts); } } } } vp->frm_buf->size = vp->frm_buf_size; status = pjmedia_port_get_frame(vp->client_port, vp->frm_buf); if (status != PJ_SUCCESS) { pjmedia_clock_src_update(&vp->clocksrc, NULL); return status; } pj_add_timestamp32(&vp->clocksrc.timestamp, frame_ts); pjmedia_clock_src_update(&vp->clocksrc, NULL); status = convert_frame(vp, vp->frm_buf, frame); if (status != PJ_SUCCESS) return status; if (!vp->conv.conv) pj_memcpy(frame, vp->frm_buf, sizeof(*frame)); } else { /* The stream is active while we are passive so we need to get the * frame from the buffer. * The encoding counterpart is located in vid_pasv_port_get_frame() */ get_frame_from_buffer(vp, frame); } if (vp->strm_cb.render_cb) return (*vp->strm_cb.render_cb)(stream, vp->strm_cb_data, frame); return PJ_SUCCESS; } static pj_status_t vid_pasv_port_put_frame(struct pjmedia_port *this_port, pjmedia_frame *frame) { struct vid_pasv_port *vpp = (struct vid_pasv_port*)this_port; pjmedia_vid_port *vp = vpp->vp; if (vp->stream_role==ROLE_PASSIVE) { /* We are passive and the stream is passive. * The encoding counterpart is in vid_pasv_port_get_frame(). */ pj_status_t status; pjmedia_frame frame_; pj_bzero(&frame_, sizeof(frame_)); status = convert_frame(vp, frame, &frame_); if (status != PJ_SUCCESS) return status; return pjmedia_vid_dev_stream_put_frame(vp->strm, (vp->conv.conv? &frame_: frame)); } else { /* We are passive while the stream is active so we just store the * frame in the buffer. * The encoding counterpart is located in vidstream_cap_cb() */ copy_frame_to_buffer(vp, frame); } return PJ_SUCCESS; } static pj_status_t vid_pasv_port_get_frame(struct pjmedia_port *this_port, pjmedia_frame *frame) { struct vid_pasv_port *vpp = (struct vid_pasv_port*)this_port; pjmedia_vid_port *vp = vpp->vp; pj_status_t status = PJ_SUCCESS; if (vp->stream_role==ROLE_PASSIVE) { /* We are passive and the stream is passive. * The decoding counterpart is in vid_pasv_port_put_frame(). */ status = pjmedia_vid_dev_stream_get_frame(vp->strm, (vp->conv.conv? vp->frm_buf: frame)); if (status != PJ_SUCCESS) return status; status = convert_frame(vp, vp->frm_buf, frame); } else { /* The stream is active while we are passive so we need to get the * frame from the buffer. * The decoding counterpart is located in vidstream_rend_cb() */ get_frame_from_buffer(vp, frame); } return status; } #endif /* PJMEDIA_HAS_VIDEO */ diff --git a/deps/pjsip/pjmedia/src/pjmedia/vid_stream.c b/deps/pjsip/pjmedia/src/pjmedia/vid_stream.c index f972448c..e8b25062 100644 --- a/deps/pjsip/pjmedia/src/pjmedia/vid_stream.c +++ b/deps/pjsip/pjmedia/src/pjmedia/vid_stream.c @@ -1,2090 +1,2090 @@ -/* $Id: vid_stream.c 4197 2012-07-05 07:26:29Z nanang $ */ +/* $Id: vid_stream.c 5234 2016-01-15 03:32:26Z nanang $ */ /* * Copyright (C) 2011 Teluu Inc. (http://www.teluu.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* memcpy() */ #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) #define THIS_FILE "vid_stream.c" #define ERRLEVEL 1 #define LOGERR_(expr) stream_perror expr #define TRC_(expr) PJ_LOG(5,expr) #define SIGNATURE PJMEDIA_SIG_PORT_VID_STREAM #define TRACE_RC 0 /* Tracing jitter buffer operations in a stream session to a CSV file. * The trace will contain JB operation timestamp, frame info, RTP info, and * the JB state right after the operation. */ #define TRACE_JB 0 /* Enable/disable trace. */ #define TRACE_JB_PATH_PREFIX "" /* Optional path/prefix for the CSV filename. */ #if TRACE_JB # include # define TRACE_JB_INVALID_FD ((pj_oshandle_t)-1) # define TRACE_JB_OPENED(s) (s->trace_jb_fd != TRACE_JB_INVALID_FD) #endif #ifndef PJMEDIA_VSTREAM_SIZE # define PJMEDIA_VSTREAM_SIZE 1000 #endif #ifndef PJMEDIA_VSTREAM_INC # define PJMEDIA_VSTREAM_INC 1000 #endif /* Due to network MTU limitation, a picture bitstream may be splitted into * several chunks for RTP delivery. The chunk number may vary depend on the * picture resolution and MTU. This constant specifies the minimum chunk * number to be allocated to store a picture bitstream in decoding direction. */ #define MIN_CHUNKS_PER_FRM 30 /* Video stream keep-alive feature is currently disabled. */ #if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA != 0 # undef PJMEDIA_STREAM_ENABLE_KA # define PJMEDIA_STREAM_ENABLE_KA 0 #endif /** * Media channel. */ typedef struct pjmedia_vid_channel { pjmedia_vid_stream *stream; /**< Parent stream. */ pjmedia_dir dir; /**< Channel direction. */ pjmedia_port port; /**< Port interface. */ unsigned pt; /**< Payload type. */ pj_bool_t paused; /**< Paused?. */ void *buf; /**< Output buffer. */ unsigned buf_size; /**< Size of output buffer. */ pjmedia_rtp_session rtp; /**< RTP session. */ } pjmedia_vid_channel; /** * This structure describes media stream. * A media stream is bidirectional media transmission between two endpoints. * It consists of two channels, i.e. encoding and decoding channels. * A media stream corresponds to a single "m=" line in a SDP session * description. */ struct pjmedia_vid_stream { pj_pool_t *own_pool; /**< Internal pool. */ pjmedia_endpt *endpt; /**< Media endpoint. */ pjmedia_vid_codec_mgr *codec_mgr; /**< Codec manager. */ pjmedia_vid_stream_info info; /**< Stream info. */ pjmedia_vid_channel *enc; /**< Encoding channel. */ pjmedia_vid_channel *dec; /**< Decoding channel. */ pjmedia_dir dir; /**< Stream direction. */ void *user_data; /**< User data. */ pj_str_t name; /**< Stream name */ pj_str_t cname; /**< SDES CNAME */ pjmedia_transport *transport; /**< Stream transport. */ unsigned send_err_cnt; /**< Send error count. */ pj_mutex_t *jb_mutex; pjmedia_jbuf *jb; /**< Jitter buffer. */ char jb_last_frm; /**< Last frame type from jb */ unsigned jb_last_frm_cnt;/**< Last JB frame type counter*/ pjmedia_rtcp_session rtcp; /**< RTCP for incoming RTP. */ pj_uint32_t rtcp_last_tx; /**< RTCP tx time in timestamp */ pj_uint32_t rtcp_interval; /**< Interval, in timestamp. */ pj_bool_t initial_rr; /**< Initial RTCP RR sent */ pj_bool_t rtcp_sdes_bye_disabled;/**< Send RTCP SDES/BYE?*/ void *out_rtcp_pkt; /**< Outgoing RTCP packet. */ unsigned out_rtcp_pkt_size; /**< Outgoing RTCP packet size. */ unsigned dec_max_size; /**< Size of decoded/raw picture*/ pjmedia_ratio dec_max_fps; /**< Max fps of decoding dir. */ pjmedia_frame dec_frame; /**< Current decoded frame. */ pjmedia_event fmt_event; /**< Buffered fmt_changed event to avoid deadlock */ pjmedia_event found_keyframe_event; /**< Buffered found keyframe event for delayed republish*/ pjmedia_event miss_keyframe_event; /**< Buffered missing keyframe event for delayed republish*/ pjmedia_event keyframe_req_event; /**< Buffered keyframe request event for delayed republish*/ unsigned frame_size; /**< Size of encoded base frame.*/ unsigned frame_ts_len; /**< Frame length in timestamp. */ unsigned rx_frame_cnt; /**< # of array in rx_frames */ pjmedia_frame *rx_frames; /**< Temp. buffer for incoming frame assembly. */ pj_bool_t force_keyframe;/**< Forced to encode keyframe? */ #if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0 pj_bool_t use_ka; /**< Stream keep-alive with non- codec-VAD mechanism is enabled? */ pj_timestamp last_frm_ts_sent; /**< Timestamp of last sending packet */ #endif #if TRACE_JB pj_oshandle_t trace_jb_fd; /**< Jitter tracing file handle.*/ char *trace_jb_buf; /**< Jitter tracing buffer. */ #endif pjmedia_vid_codec *codec; /**< Codec instance being used. */ pj_uint32_t last_dec_ts; /**< Last decoded timestamp. */ int last_dec_seq; /**< Last decoded sequence. */ pj_status_t rtp_rx_last_err; /**< Last RTP recv() error. */ pj_timestamp ts_freq; /**< Timestamp frequency. */ #if TRACE_RC unsigned rc_total_sleep; unsigned rc_total_pkt; unsigned rc_total_img; pj_timestamp tx_start; pj_timestamp tx_end; #endif }; /* Prototypes */ static pj_status_t decode_frame(pjmedia_vid_stream *stream, pjmedia_frame *frame); /* * Print error. */ static void stream_perror(const char *sender, const char *title, pj_status_t status) { char errmsg[PJ_ERR_MSG_SIZE]; pj_strerror(status, errmsg, sizeof(errmsg)); PJ_LOG(4,(sender, "%s: %s [err:%d]", title, errmsg, status)); } static pj_status_t send_rtcp(pjmedia_vid_stream *stream, pj_bool_t with_sdes, pj_bool_t with_bye); #if TRACE_JB PJ_INLINE(int) trace_jb_print_timestamp(char **buf, pj_ssize_t len) { pj_time_val now; pj_parsed_time ptime; char *p = *buf; if (len < 14) return -1; pj_gettimeofday(&now); pj_time_decode(&now, &ptime); p += pj_utoa_pad(ptime.hour, p, 2, '0'); *p++ = ':'; p += pj_utoa_pad(ptime.min, p, 2, '0'); *p++ = ':'; p += pj_utoa_pad(ptime.sec, p, 2, '0'); *p++ = '.'; p += pj_utoa_pad(ptime.msec, p, 3, '0'); *p++ = ','; *buf = p; return 0; } PJ_INLINE(int) trace_jb_print_state(pjmedia_vid_stream *stream, char **buf, pj_ssize_t len) { char *p = *buf; char *endp = *buf + len; pjmedia_jb_state state; pjmedia_jbuf_get_state(stream->jb, &state); len = pj_ansi_snprintf(p, endp-p, "%d, %d, %d", state.size, state.burst, state.prefetch); if ((len < 0) || (len >= endp-p)) return -1; p += len; *buf = p; return 0; } static void trace_jb_get(pjmedia_vid_stream *stream, pjmedia_jb_frame_type ft, pj_size_t fsize) { char *p = stream->trace_jb_buf; char *endp = stream->trace_jb_buf + PJ_LOG_MAX_SIZE; pj_ssize_t len = 0; const char* ft_st; if (!TRACE_JB_OPENED(stream)) return; /* Print timestamp. */ if (trace_jb_print_timestamp(&p, endp-p)) goto on_insuff_buffer; /* Print frame type and size */ switch(ft) { case PJMEDIA_JB_MISSING_FRAME: ft_st = "missing"; break; case PJMEDIA_JB_NORMAL_FRAME: ft_st = "normal"; break; case PJMEDIA_JB_ZERO_PREFETCH_FRAME: ft_st = "prefetch"; break; case PJMEDIA_JB_ZERO_EMPTY_FRAME: ft_st = "empty"; break; default: ft_st = "unknown"; break; } /* Print operation, size, frame count, frame type */ len = pj_ansi_snprintf(p, endp-p, "GET,%d,1,%s,,,,", fsize, ft_st); if ((len < 0) || (len >= endp-p)) goto on_insuff_buffer; p += len; /* Print JB state */ if (trace_jb_print_state(stream, &p, endp-p)) goto on_insuff_buffer; /* Print end of line */ if (endp-p < 2) goto on_insuff_buffer; *p++ = '\n'; /* Write and flush */ len = p - stream->trace_jb_buf; pj_file_write(stream->trace_jb_fd, stream->trace_jb_buf, &len); pj_file_flush(stream->trace_jb_fd); return; on_insuff_buffer: pj_assert(!"Trace buffer too small, check PJ_LOG_MAX_SIZE!"); } static void trace_jb_put(pjmedia_vid_stream *stream, const pjmedia_rtp_hdr *hdr, unsigned payloadlen, unsigned frame_cnt) { char *p = stream->trace_jb_buf; char *endp = stream->trace_jb_buf + PJ_LOG_MAX_SIZE; pj_ssize_t len = 0; if (!TRACE_JB_OPENED(stream)) return; /* Print timestamp. */ if (trace_jb_print_timestamp(&p, endp-p)) goto on_insuff_buffer; /* Print operation, size, frame count, RTP info */ len = pj_ansi_snprintf(p, endp-p, "PUT,%d,%d,,%d,%d,%d,", payloadlen, frame_cnt, pj_ntohs(hdr->seq), pj_ntohl(hdr->ts), hdr->m); if ((len < 0) || (len >= endp-p)) goto on_insuff_buffer; p += len; /* Print JB state */ if (trace_jb_print_state(stream, &p, endp-p)) goto on_insuff_buffer; /* Print end of line */ if (endp-p < 2) goto on_insuff_buffer; *p++ = '\n'; /* Write and flush */ len = p - stream->trace_jb_buf; pj_file_write(stream->trace_jb_fd, stream->trace_jb_buf, &len); pj_file_flush(stream->trace_jb_fd); return; on_insuff_buffer: pj_assert(!"Trace buffer too small, check PJ_LOG_MAX_SIZE!"); } #endif /* TRACE_JB */ static void dump_port_info(const pjmedia_vid_channel *chan, const char *event_name) { const pjmedia_port_info *pi = &chan->port.info; char fourcc_name[5]; PJ_LOG(4, (pi->name.ptr, " %s format %s: %dx%d %s%s %d/%d(~%d)fps", (chan->dir==PJMEDIA_DIR_DECODING? "Decoding":"Encoding"), event_name, pi->fmt.det.vid.size.w, pi->fmt.det.vid.size.h, pjmedia_fourcc_name(pi->fmt.id, fourcc_name), (chan->dir==PJMEDIA_DIR_ENCODING?"->":"<-"), pi->fmt.det.vid.fps.num, pi->fmt.det.vid.fps.denum, pi->fmt.det.vid.fps.num/pi->fmt.det.vid.fps.denum)); } /* * Handle events from stream components. */ static pj_status_t stream_event_cb(pjmedia_event *event, void *user_data) { pjmedia_vid_stream *stream = (pjmedia_vid_stream*)user_data; if (event->epub == stream->codec) { /* This is codec event */ switch (event->type) { case PJMEDIA_EVENT_FMT_CHANGED: /* Copy the event to avoid deadlock if we publish the event * now. This happens because fmt_event may trigger restart * while we're still holding the jb_mutex. */ pj_memcpy(&stream->fmt_event, event, sizeof(*event)); return PJ_SUCCESS; case PJMEDIA_EVENT_KEYFRAME_FOUND: /* Republish this event later from get_frame(). */ pj_memcpy(&stream->found_keyframe_event, event, sizeof(*event)); return PJ_SUCCESS; case PJMEDIA_EVENT_KEYFRAME_MISSING: /* Republish this event later from get_frame(). */ pj_memcpy(&stream->miss_keyframe_event, event, sizeof(*event)); return PJ_SUCCESS; default: break; } } return pjmedia_event_publish(NULL, stream, event, 0); } #if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA != 0 /* * Send keep-alive packet using non-codec frame. */ static void send_keep_alive_packet(pjmedia_vid_stream *stream) { #if PJMEDIA_STREAM_ENABLE_KA == PJMEDIA_STREAM_KA_EMPTY_RTP /* Keep-alive packet is empty RTP */ pjmedia_vid_channel *channel = stream->enc; pj_status_t status; void *pkt; int pkt_len; TRC_((channel->port.info.name.ptr, "Sending keep-alive (RTCP and empty RTP)")); /* Send RTP */ status = pjmedia_rtp_encode_rtp( &stream->enc->rtp, stream->enc->pt, 0, 1, 0, (const void**)&pkt, &pkt_len); pj_assert(status == PJ_SUCCESS); pj_memcpy(stream->enc->buf, pkt, pkt_len); pjmedia_transport_send_rtp(stream->transport, stream->enc->buf, pkt_len); /* Send RTCP */ send_rtcp(stream, PJ_TRUE, PJ_FALSE); #elif PJMEDIA_STREAM_ENABLE_KA == PJMEDIA_STREAM_KA_USER /* Keep-alive packet is defined in PJMEDIA_STREAM_KA_USER_PKT */ pjmedia_vid_channel *channel = stream->enc; int pkt_len; const pj_str_t str_ka = PJMEDIA_STREAM_KA_USER_PKT; TRC_((channel->port.info.name.ptr, "Sending keep-alive (custom RTP/RTCP packets)")); /* Send to RTP port */ pj_memcpy(stream->enc->buf, str_ka.ptr, str_ka.slen); pkt_len = str_ka.slen; pjmedia_transport_send_rtp(stream->transport, stream->enc->buf, pkt_len); /* Send to RTCP port */ pjmedia_transport_send_rtcp(stream->transport, stream->enc->buf, pkt_len); #else PJ_UNUSED_ARG(stream); #endif } #endif /* defined(PJMEDIA_STREAM_ENABLE_KA) */ static pj_status_t send_rtcp(pjmedia_vid_stream *stream, pj_bool_t with_sdes, pj_bool_t with_bye) { void *sr_rr_pkt; pj_uint8_t *pkt; int len, max_len; pj_status_t status; /* Build RTCP RR/SR packet */ pjmedia_rtcp_build_rtcp(&stream->rtcp, &sr_rr_pkt, &len); if (with_sdes || with_bye) { pkt = (pj_uint8_t*) stream->out_rtcp_pkt; pj_memcpy(pkt, sr_rr_pkt, len); max_len = stream->out_rtcp_pkt_size; } else { pkt = (pj_uint8_t*)sr_rr_pkt; max_len = len; } /* Build RTCP SDES packet */ if (with_sdes) { pjmedia_rtcp_sdes sdes; pj_size_t sdes_len; pj_bzero(&sdes, sizeof(sdes)); sdes.cname = stream->cname; sdes_len = max_len - len; status = pjmedia_rtcp_build_rtcp_sdes(&stream->rtcp, pkt+len, &sdes_len, &sdes); if (status != PJ_SUCCESS) { PJ_PERROR(4,(stream->name.ptr, status, "Error generating RTCP SDES")); } else { len += (int)sdes_len; } } /* Build RTCP BYE packet */ if (with_bye) { pj_size_t bye_len; bye_len = max_len - len; status = pjmedia_rtcp_build_rtcp_bye(&stream->rtcp, pkt+len, &bye_len, NULL); if (status != PJ_SUCCESS) { PJ_PERROR(4,(stream->name.ptr, status, "Error generating RTCP BYE")); } else { len += (int)bye_len; } } /* Send! */ status = pjmedia_transport_send_rtcp(stream->transport, pkt, len); return status; } /** * check_tx_rtcp() * * This function is can be called by either put_frame() or get_frame(), * to transmit periodic RTCP SR/RR report. */ static void check_tx_rtcp(pjmedia_vid_stream *stream, pj_uint32_t timestamp) { /* Note that timestamp may represent local or remote timestamp, * depending on whether this function is called from put_frame() * or get_frame(). */ if (stream->rtcp_last_tx == 0) { stream->rtcp_last_tx = timestamp; } else if (timestamp - stream->rtcp_last_tx >= stream->rtcp_interval) { pj_status_t status; status = send_rtcp(stream, !stream->rtcp_sdes_bye_disabled, PJ_FALSE); if (status != PJ_SUCCESS) { PJ_PERROR(4,(stream->name.ptr, status, "Error sending RTCP")); } stream->rtcp_last_tx = timestamp; } } #if 0 static void dump_bin(const char *buf, unsigned len) { unsigned i; PJ_LOG(3,(THIS_FILE, "begin dump")); for (i=0; idec; const pjmedia_rtp_hdr *hdr; const void *payload; unsigned payloadlen; pjmedia_rtp_status seq_st; pj_status_t status; pj_bool_t pkt_discarded = PJ_FALSE; /* Check for errors */ if (bytes_read < 0) { status = (pj_status_t)-bytes_read; if (status == PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK)) { return; } if (stream->rtp_rx_last_err != status) { char errmsg[PJ_ERR_MSG_SIZE]; pj_strerror(status, errmsg, sizeof(errmsg)); PJ_LOG(4,(channel->port.info.name.ptr, "Unable to receive RTP packet, recv() returned %d: %s", status, errmsg)); stream->rtp_rx_last_err = status; } return; } else { stream->rtp_rx_last_err = PJ_SUCCESS; } /* Ignore keep-alive packets */ if (bytes_read < (pj_ssize_t) sizeof(pjmedia_rtp_hdr)) return; /* Update RTP and RTCP session. */ status = pjmedia_rtp_decode_rtp(&channel->rtp, pkt, (int)bytes_read, &hdr, &payload, &payloadlen); if (status != PJ_SUCCESS) { LOGERR_((channel->port.info.name.ptr, "RTP decode error", status)); stream->rtcp.stat.rx.discard++; return; } /* Ignore the packet if decoder is paused */ if (channel->paused) goto on_return; /* Update RTP session (also checks if RTP session can accept * the incoming packet. */ pjmedia_rtp_session_update2(&channel->rtp, hdr, &seq_st, PJ_TRUE); if (seq_st.status.value) { TRC_ ((channel->port.info.name.ptr, "RTP status: badpt=%d, badssrc=%d, dup=%d, " "outorder=%d, probation=%d, restart=%d", seq_st.status.flag.badpt, seq_st.status.flag.badssrc, seq_st.status.flag.dup, seq_st.status.flag.outorder, seq_st.status.flag.probation, seq_st.status.flag.restart)); if (seq_st.status.flag.badpt) { PJ_LOG(4,(channel->port.info.name.ptr, "Bad RTP pt %d (expecting %d)", hdr->pt, channel->rtp.out_pt)); } if (seq_st.status.flag.badssrc) { PJ_LOG(4,(channel->port.info.name.ptr, "Changed RTP peer SSRC %d (previously %d)", channel->rtp.peer_ssrc, stream->rtcp.peer_ssrc)); stream->rtcp.peer_ssrc = channel->rtp.peer_ssrc; } } /* Skip bad RTP packet */ if (seq_st.status.flag.bad) { pkt_discarded = PJ_TRUE; goto on_return; } /* Ignore if payloadlen is zero */ if (payloadlen == 0) { pkt_discarded = PJ_TRUE; goto on_return; } pj_mutex_lock( stream->jb_mutex ); /* Quickly see if there may be a full picture in the jitter buffer, and * decode them if so. More thorough check will be done in decode_frame(). */ if ((pj_ntohl(hdr->ts) != stream->dec_frame.timestamp.u32.lo) || hdr->m) { if (PJMEDIA_VID_STREAM_SKIP_PACKETS_TO_REDUCE_LATENCY) { /* Always decode whenever we have picture in jb and * overwrite already decoded picture if necessary */ pj_size_t old_size = stream->dec_frame.size; stream->dec_frame.size = stream->dec_max_size; if (decode_frame(stream, &stream->dec_frame) != PJ_SUCCESS) { stream->dec_frame.size = old_size; } } else { /* Only decode if we don't already have decoded one, * unless the jb is full. */ pj_bool_t can_decode = PJ_FALSE; if (pjmedia_jbuf_is_full(stream->jb)) { can_decode = PJ_TRUE; } else if (stream->dec_frame.size == 0) { can_decode = PJ_TRUE; } if (can_decode) { stream->dec_frame.size = stream->dec_max_size; if (decode_frame(stream, &stream->dec_frame) != PJ_SUCCESS) { stream->dec_frame.size = 0; } } } } /* Put "good" packet to jitter buffer, or reset the jitter buffer * when RTP session is restarted. */ if (seq_st.status.flag.restart) { status = pjmedia_jbuf_reset(stream->jb); PJ_LOG(4,(channel->port.info.name.ptr, "Jitter buffer reset")); } else { /* Just put the payload into jitter buffer */ pjmedia_jbuf_put_frame3(stream->jb, payload, payloadlen, 0, pj_ntohs(hdr->seq), pj_ntohl(hdr->ts), NULL); #if TRACE_JB trace_jb_put(stream, hdr, payloadlen, count); #endif } pj_mutex_unlock( stream->jb_mutex ); /* Check if now is the time to transmit RTCP SR/RR report. * We only do this when stream direction is "decoding only", * because otherwise check_tx_rtcp() will be handled by put_frame() */ if (stream->dir == PJMEDIA_DIR_DECODING) { check_tx_rtcp(stream, pj_ntohl(hdr->ts)); } if (status != 0) { LOGERR_((channel->port.info.name.ptr, "Jitter buffer put() error", status)); pkt_discarded = PJ_TRUE; goto on_return; } on_return: /* Update RTCP session */ if (stream->rtcp.peer_ssrc == 0) stream->rtcp.peer_ssrc = channel->rtp.peer_ssrc; pjmedia_rtcp_rx_rtp2(&stream->rtcp, pj_ntohs(hdr->seq), pj_ntohl(hdr->ts), payloadlen, pkt_discarded); /* Send RTCP RR and SDES after we receive some RTP packets */ if (stream->rtcp.received >= 10 && !stream->initial_rr) { status = send_rtcp(stream, !stream->rtcp_sdes_bye_disabled, PJ_FALSE); if (status != PJ_SUCCESS) { PJ_PERROR(4,(stream->name.ptr, status, "Error sending initial RTCP RR")); } else { stream->initial_rr = PJ_TRUE; } } } /* * This callback is called by stream transport on receipt of packets * in the RTCP socket. */ static void on_rx_rtcp( void *data, void *pkt, pj_ssize_t bytes_read) { pjmedia_vid_stream *stream = (pjmedia_vid_stream*) data; /* Check for errors */ if (bytes_read < 0) { if (bytes_read != -PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK)) { LOGERR_((stream->cname.ptr, "RTCP recv() error", (pj_status_t)-bytes_read)); } return; } pjmedia_rtcp_rx_rtcp(&stream->rtcp, pkt, bytes_read); /* XXX: posting some event from the RTCP session might be a better option */ if (stream->rtcp.keyframe_requested) { pjmedia_event event; pjmedia_event_init(&event, PJMEDIA_EVENT_KEYFRAME_REQUESTED, NULL, stream); pj_memcpy(&stream->keyframe_req_event, &event, sizeof(event)); } } static pj_status_t put_frame(pjmedia_port *port, pjmedia_frame *frame) { pjmedia_vid_stream *stream = (pjmedia_vid_stream*) port->port_data.pdata; pjmedia_vid_channel *channel = stream->enc; pj_status_t status = 0; pjmedia_frame frame_out; unsigned rtp_ts_len; void *rtphdr; int rtphdrlen; pj_bool_t has_more_data = PJ_FALSE; pj_size_t total_sent = 0; pjmedia_vid_encode_opt enc_opt; unsigned pkt_cnt = 0; pj_timestamp initial_time; #if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA != 0 /* If the interval since last sending packet is greater than * PJMEDIA_STREAM_KA_INTERVAL, send keep-alive packet. */ if (stream->use_ka) { pj_uint32_t dtx_duration; dtx_duration = pj_timestamp_diff32(&stream->last_frm_ts_sent, &frame->timestamp); /* Video stream keep-alive feature is currently disabled. */ /* if (dtx_duration > PJMEDIA_STREAM_KA_INTERVAL * PJMEDIA_PIA_SRATE(&channel->port.info)) { send_keep_alive_packet(stream); stream->last_frm_ts_sent = frame->timestamp; } */ } #endif /* Don't do anything if stream is paused */ if (channel->paused) { return PJ_SUCCESS; } /* Get frame length in timestamp unit */ rtp_ts_len = stream->frame_ts_len; /* Init frame_out buffer. */ frame_out.buf = ((char*)channel->buf) + sizeof(pjmedia_rtp_hdr); frame_out.size = 0; /* Init encoding option */ pj_bzero(&enc_opt, sizeof(enc_opt)); if (stream->force_keyframe) { /* Force encoder to generate keyframe */ enc_opt.force_keyframe = PJ_TRUE; stream->force_keyframe = PJ_FALSE; TRC_((channel->port.info.name.ptr, "Forcing encoder to generate keyframe")); } /* Encode! */ status = pjmedia_vid_codec_encode_begin(stream->codec, &enc_opt, frame, channel->buf_size - sizeof(pjmedia_rtp_hdr), &frame_out, &has_more_data); if (status != PJ_SUCCESS) { LOGERR_((channel->port.info.name.ptr, "Codec encode_begin() error", status)); /* Update RTP timestamp */ pjmedia_rtp_encode_rtp(&channel->rtp, channel->pt, 1, 0, rtp_ts_len, (const void**)&rtphdr, &rtphdrlen); return status; } pj_get_timestamp(&initial_time); /* Loop while we have frame to send */ for (;;) { status = pjmedia_rtp_encode_rtp(&channel->rtp, channel->pt, (has_more_data == PJ_FALSE ? 1 : 0), (int)frame_out.size, rtp_ts_len, (const void**)&rtphdr, &rtphdrlen); if (status != PJ_SUCCESS) { LOGERR_((channel->port.info.name.ptr, "RTP encode_rtp() error", status)); return status; } /* When the payload length is zero, we should not send anything, * but proceed the rest normally. */ if (frame_out.size != 0) { /* Copy RTP header to the beginning of packet */ pj_memcpy(channel->buf, rtphdr, sizeof(pjmedia_rtp_hdr)); /* Send the RTP packet to the transport. */ status = pjmedia_transport_send_rtp(stream->transport, (char*)channel->buf, frame_out.size + sizeof(pjmedia_rtp_hdr)); if (status != PJ_SUCCESS) { enum { COUNT_TO_REPORT = 20 }; if (stream->send_err_cnt++ == 0) { LOGERR_((channel->port.info.name.ptr, "Transport send_rtp() error", status)); } if (stream->send_err_cnt > COUNT_TO_REPORT) stream->send_err_cnt = 0; /* Ignore this error */ } pjmedia_rtcp_tx_rtp(&stream->rtcp, (unsigned)frame_out.size); total_sent += frame_out.size; pkt_cnt++; } if (!has_more_data) break; /* Next packets use same timestamp */ rtp_ts_len = 0; frame_out.size = 0; /* Encode more! */ status = pjmedia_vid_codec_encode_more(stream->codec, channel->buf_size - sizeof(pjmedia_rtp_hdr), &frame_out, &has_more_data); if (status != PJ_SUCCESS) { LOGERR_((channel->port.info.name.ptr, "Codec encode_more() error", status)); /* Ignore this error (?) */ break; } /* Send rate control */ if (stream->info.rc_cfg.method==PJMEDIA_VID_STREAM_RC_SIMPLE_BLOCKING) { pj_timestamp now, next_send_ts, total_send_ts; total_send_ts.u64 = total_sent * stream->ts_freq.u64 * 8 / stream->info.rc_cfg.bandwidth; next_send_ts = initial_time; pj_add_timestamp(&next_send_ts, &total_send_ts); pj_get_timestamp(&now); if (pj_cmp_timestamp(&now, &next_send_ts) < 0) { unsigned ms_sleep; ms_sleep = pj_elapsed_msec(&now, &next_send_ts); if (ms_sleep > 10) ms_sleep = 10; pj_thread_sleep(ms_sleep); } } } #if TRACE_RC /* Trace log for rate control */ { pj_timestamp end_time; unsigned total_sleep; pj_get_timestamp(&end_time); total_sleep = pj_elapsed_msec(&initial_time, &end_time); PJ_LOG(5, (stream->name.ptr, "total pkt=%d size=%d sleep=%d", pkt_cnt, total_sent, total_sleep)); if (stream->tx_start.u64 == 0) stream->tx_start = initial_time; stream->tx_end = end_time; stream->rc_total_pkt += pkt_cnt; stream->rc_total_sleep += total_sleep; stream->rc_total_img++; } #endif /* Check if now is the time to transmit RTCP SR/RR report. * We only do this when stream direction is not "decoding only", because * when it is, check_tx_rtcp() will be handled by get_frame(). */ if (stream->dir != PJMEDIA_DIR_DECODING) { check_tx_rtcp(stream, pj_ntohl(channel->rtp.out_hdr.ts)); } /* Do nothing if we have nothing to transmit */ if (total_sent == 0) { return PJ_SUCCESS; } /* Update stat */ if (pkt_cnt) { stream->rtcp.stat.rtp_tx_last_ts = pj_ntohl(stream->enc->rtp.out_hdr.ts); stream->rtcp.stat.rtp_tx_last_seq = pj_ntohs(stream->enc->rtp.out_hdr.seq); } #if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0 /* Update timestamp of last sending packet. */ stream->last_frm_ts_sent = frame->timestamp; #endif return PJ_SUCCESS; } /* Decode one image from jitter buffer */ static pj_status_t decode_frame(pjmedia_vid_stream *stream, pjmedia_frame *frame) { pjmedia_vid_channel *channel = stream->dec; pj_uint32_t last_ts = 0; int frm_first_seq = 0, frm_last_seq = 0; pj_bool_t got_frame = PJ_FALSE; unsigned cnt; pj_status_t status; /* Repeat get payload from the jitter buffer until all payloads with same * timestamp are collected. */ /* Check if we got a decodable frame */ for (cnt=0; ; ++cnt) { char ptype; pj_uint32_t ts; int seq; /* Peek frame from jitter buffer. */ pjmedia_jbuf_peek_frame(stream->jb, cnt, NULL, NULL, &ptype, NULL, &ts, &seq); if (ptype == PJMEDIA_JB_NORMAL_FRAME) { if (last_ts == 0) { last_ts = ts; frm_first_seq = seq; } if (ts != last_ts) { got_frame = PJ_TRUE; break; } frm_last_seq = seq; } else if (ptype == PJMEDIA_JB_ZERO_EMPTY_FRAME) { /* No more packet in the jitter buffer */ break; } } if (got_frame) { unsigned i; /* Generate frame bitstream from the payload */ if (cnt > stream->rx_frame_cnt) { PJ_LOG(1,(channel->port.info.name.ptr, "Discarding %u frames because array is full!", cnt - stream->rx_frame_cnt)); pjmedia_jbuf_remove_frame(stream->jb, cnt - stream->rx_frame_cnt); cnt = stream->rx_frame_cnt; } for (i = 0; i < cnt; ++i) { char ptype; stream->rx_frames[i].type = PJMEDIA_FRAME_TYPE_VIDEO; stream->rx_frames[i].timestamp.u64 = last_ts; stream->rx_frames[i].bit_info = 0; /* We use jbuf_peek_frame() as it will returns the pointer of * the payload (no buffer and memcpy needed), just as we need. */ pjmedia_jbuf_peek_frame(stream->jb, i, (const void**)&stream->rx_frames[i].buf, &stream->rx_frames[i].size, &ptype, NULL, NULL, NULL); if (ptype != PJMEDIA_JB_NORMAL_FRAME) { /* Packet lost, must set payload to NULL and keep going */ stream->rx_frames[i].buf = NULL; stream->rx_frames[i].size = 0; stream->rx_frames[i].type = PJMEDIA_FRAME_TYPE_NONE; continue; } } /* Decode */ status = pjmedia_vid_codec_decode(stream->codec, cnt, stream->rx_frames, (unsigned)frame->size, frame); if (status != PJ_SUCCESS) { LOGERR_((channel->port.info.name.ptr, "codec decode() error", status)); frame->type = PJMEDIA_FRAME_TYPE_NONE; frame->size = 0; } pjmedia_jbuf_remove_frame(stream->jb, cnt); } /* Learn remote frame rate after successful decoding */ if (frame->type == PJMEDIA_FRAME_TYPE_VIDEO && frame->size) { /* Only check remote frame rate when timestamp is not wrapping and * sequence is increased by 1. */ if (last_ts > stream->last_dec_ts && frm_first_seq - stream->last_dec_seq == 1) { pj_uint32_t ts_diff; pjmedia_video_format_detail *vfd; ts_diff = last_ts - stream->last_dec_ts; vfd = pjmedia_format_get_video_format_detail( &channel->port.info.fmt, PJ_TRUE); if (stream->info.codec_info.clock_rate * vfd->fps.denum != vfd->fps.num * ts_diff) { /* Frame rate changed, update decoding port info */ if (stream->info.codec_info.clock_rate % ts_diff == 0) { vfd->fps.num = stream->info.codec_info.clock_rate/ts_diff; vfd->fps.denum = 1; } else { vfd->fps.num = stream->info.codec_info.clock_rate; vfd->fps.denum = ts_diff; } /* Update stream info */ stream->info.codec_param->dec_fmt.det.vid.fps = vfd->fps; /* Publish PJMEDIA_EVENT_FMT_CHANGED event if frame rate * increased and not exceeding 60fps. */ if (vfd->fps.num/vfd->fps.denum <= 60.0 && vfd->fps.num * stream->dec_max_fps.denum > stream->dec_max_fps.num * vfd->fps.denum) { /*printf("FPS CHANGED: %d/%d -> %d/%d\n", stream->dec_max_fps.num, stream->dec_max_fps.denum, vfd->fps.num, vfd->fps.denum);*/ pjmedia_event *event = &stream->fmt_event; /* Update max fps of decoding dir */ stream->dec_max_fps = vfd->fps; /* Use the buffered format changed event: * - just update the framerate if there is pending event, * - otherwise, init the whole event. */ if (stream->fmt_event.type != PJMEDIA_EVENT_NONE) { event->data.fmt_changed.new_fmt.det.vid.fps = vfd->fps; } else { pjmedia_event_init(event, PJMEDIA_EVENT_FMT_CHANGED, &frame->timestamp, stream); event->data.fmt_changed.dir = PJMEDIA_DIR_DECODING; pj_memcpy(&event->data.fmt_changed.new_fmt, &stream->info.codec_param->dec_fmt, sizeof(pjmedia_format)); } } } } /* Update last frame seq and timestamp */ stream->last_dec_seq = frm_last_seq; stream->last_dec_ts = last_ts; } return got_frame ? PJ_SUCCESS : PJ_ENOTFOUND; } static pj_status_t get_frame(pjmedia_port *port, pjmedia_frame *frame) { pjmedia_vid_stream *stream = (pjmedia_vid_stream*) port->port_data.pdata; pjmedia_vid_channel *channel = stream->dec; /* Return no frame is channel is paused */ if (channel->paused) { frame->type = PJMEDIA_FRAME_TYPE_NONE; frame->size = 0; return PJ_SUCCESS; } /* Report pending events. Do not publish the event while holding the * jb_mutex as that would lead to deadlock. It should be safe to * operate on fmt_event without the mutex because format change normally * would only occur once during the start of the media. */ if (stream->fmt_event.type != PJMEDIA_EVENT_NONE) { pjmedia_event_fmt_changed_data *fmt_chg_data; fmt_chg_data = &stream->fmt_event.data.fmt_changed; /* Update stream info and decoding channel port info */ if (fmt_chg_data->dir == PJMEDIA_DIR_DECODING) { pjmedia_format_copy(&stream->info.codec_param->dec_fmt, &fmt_chg_data->new_fmt); pjmedia_format_copy(&stream->dec->port.info.fmt, &fmt_chg_data->new_fmt); /* Override the framerate to be 1.5x higher in the event * for the renderer. */ #if 0 fmt_chg_data->new_fmt.det.vid.fps.num *= 3; fmt_chg_data->new_fmt.det.vid.fps.num /= 2; #endif } else { pjmedia_format_copy(&stream->info.codec_param->enc_fmt, &fmt_chg_data->new_fmt); pjmedia_format_copy(&stream->enc->port.info.fmt, &fmt_chg_data->new_fmt); } dump_port_info(fmt_chg_data->dir==PJMEDIA_DIR_DECODING ? stream->dec : stream->enc, "changed"); pjmedia_event_publish(NULL, port, &stream->fmt_event, 0); stream->fmt_event.type = PJMEDIA_EVENT_NONE; } if (stream->found_keyframe_event.type != PJMEDIA_EVENT_NONE) { pjmedia_event_publish(NULL, port, &stream->found_keyframe_event, PJMEDIA_EVENT_PUBLISH_POST_EVENT); stream->found_keyframe_event.type = PJMEDIA_EVENT_NONE; } if (stream->miss_keyframe_event.type != PJMEDIA_EVENT_NONE) { pjmedia_event_publish(NULL, port, &stream->miss_keyframe_event, PJMEDIA_EVENT_PUBLISH_POST_EVENT); stream->miss_keyframe_event.type = PJMEDIA_EVENT_NONE; } if (stream->keyframe_req_event.type != PJMEDIA_EVENT_NONE) { pjmedia_event_publish(NULL, port, &stream->keyframe_req_event, PJMEDIA_EVENT_PUBLISH_POST_EVENT); stream->keyframe_req_event.type = PJMEDIA_EVENT_NONE; } pj_mutex_lock( stream->jb_mutex ); if (stream->dec_frame.size == 0) { /* Don't have frame in buffer, try to decode one */ if (decode_frame(stream, frame) != PJ_SUCCESS) { frame->type = PJMEDIA_FRAME_TYPE_NONE; frame->size = 0; } } else { if (frame->size < stream->dec_frame.size) { PJ_LOG(4,(stream->dec->port.info.name.ptr, "Error: not enough buffer for decoded frame " "(supplied=%d, required=%d)", (int)frame->size, (int)stream->dec_frame.size)); frame->type = PJMEDIA_FRAME_TYPE_NONE; frame->size = 0; } else { frame->type = stream->dec_frame.type; frame->timestamp = stream->dec_frame.timestamp; frame->size = stream->dec_frame.size; pj_memcpy(frame->buf, stream->dec_frame.buf, frame->size); } stream->dec_frame.size = 0; } pj_mutex_unlock( stream->jb_mutex ); return PJ_SUCCESS; } /* * Create media channel. */ static pj_status_t create_channel( pj_pool_t *pool, pjmedia_vid_stream *stream, pjmedia_dir dir, unsigned pt, const pjmedia_vid_stream_info *info, pjmedia_vid_channel **p_channel) { enum { M = 32 }; pjmedia_vid_channel *channel; pj_status_t status; unsigned min_out_pkt_size; pj_str_t name; const char *type_name; pjmedia_format *fmt; char fourcc_name[5]; pjmedia_port_info *pi; pj_assert(info->type == PJMEDIA_TYPE_VIDEO); pj_assert(dir == PJMEDIA_DIR_DECODING || dir == PJMEDIA_DIR_ENCODING); /* Allocate memory for channel descriptor */ channel = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_channel); PJ_ASSERT_RETURN(channel != NULL, PJ_ENOMEM); /* Init vars */ if (dir==PJMEDIA_DIR_DECODING) { type_name = "vstdec"; fmt = &info->codec_param->dec_fmt; } else { type_name = "vstenc"; fmt = &info->codec_param->enc_fmt; } name.ptr = (char*) pj_pool_alloc(pool, M); name.slen = pj_ansi_snprintf(name.ptr, M, "%s%p", type_name, stream); pi = &channel->port.info; /* Init channel info. */ channel->stream = stream; channel->dir = dir; channel->paused = 1; channel->pt = pt; /* Allocate buffer for outgoing packet. */ if (dir == PJMEDIA_DIR_ENCODING) { channel->buf_size = sizeof(pjmedia_rtp_hdr) + stream->frame_size; /* It should big enough to hold (minimally) RTCP SR with an SDES. */ min_out_pkt_size = sizeof(pjmedia_rtcp_sr_pkt) + sizeof(pjmedia_rtcp_common) + (4 + (unsigned)stream->cname.slen) + 32; if (channel->buf_size < min_out_pkt_size) channel->buf_size = min_out_pkt_size; channel->buf = pj_pool_alloc(pool, channel->buf_size); PJ_ASSERT_RETURN(channel->buf != NULL, PJ_ENOMEM); } /* Create RTP and RTCP sessions: */ if (info->rtp_seq_ts_set == 0) { status = pjmedia_rtp_session_init(&channel->rtp, pt, info->ssrc); } else { pjmedia_rtp_session_setting settings; settings.flags = (pj_uint8_t)((info->rtp_seq_ts_set << 2) | 3); settings.default_pt = pt; settings.sender_ssrc = info->ssrc; settings.seq = info->rtp_seq; settings.ts = info->rtp_ts; status = pjmedia_rtp_session_init2(&channel->rtp, settings); } if (status != PJ_SUCCESS) return status; /* Init port. */ pjmedia_port_info_init2(pi, &name, SIGNATURE, dir, fmt); if (dir == PJMEDIA_DIR_DECODING) { channel->port.get_frame = &get_frame; } else { pi->fmt.id = info->codec_param->dec_fmt.id; channel->port.put_frame = &put_frame; } /* Init port. */ channel->port.port_data.pdata = stream; PJ_LOG(5, (name.ptr, "%s channel created %dx%d %s%s%.*s %d/%d(~%d)fps", (dir==PJMEDIA_DIR_ENCODING?"Encoding":"Decoding"), pi->fmt.det.vid.size.w, pi->fmt.det.vid.size.h, pjmedia_fourcc_name(pi->fmt.id, fourcc_name), (dir==PJMEDIA_DIR_ENCODING?"->":"<-"), info->codec_info.encoding_name.slen, info->codec_info.encoding_name.ptr, pi->fmt.det.vid.fps.num, pi->fmt.det.vid.fps.denum, pi->fmt.det.vid.fps.num/pi->fmt.det.vid.fps.denum)); /* Done. */ *p_channel = channel; return PJ_SUCCESS; } /* * Create stream. */ PJ_DEF(pj_status_t) pjmedia_vid_stream_create( pjmedia_endpt *endpt, pj_pool_t *pool, pjmedia_vid_stream_info *info, pjmedia_transport *tp, void *user_data, pjmedia_vid_stream **p_stream) { enum { M = 32 }; pj_pool_t *own_pool = NULL; pjmedia_vid_stream *stream; unsigned jb_init, jb_max, jb_min_pre, jb_max_pre; int frm_ptime, chunks_per_frm; pjmedia_video_format_detail *vfd_enc, *vfd_dec; char *p; pj_status_t status; if (!pool) { own_pool = pjmedia_endpt_create_pool( endpt, "vstrm%p", PJMEDIA_VSTREAM_SIZE, PJMEDIA_VSTREAM_INC); PJ_ASSERT_RETURN(own_pool != NULL, PJ_ENOMEM); pool = own_pool; } /* Allocate stream */ stream = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_stream); PJ_ASSERT_RETURN(stream != NULL, PJ_ENOMEM); stream->own_pool = own_pool; /* Get codec manager */ stream->codec_mgr = pjmedia_vid_codec_mgr_instance(); PJ_ASSERT_RETURN(stream->codec_mgr, PJMEDIA_CODEC_EFAILED); /* Init stream/port name */ stream->name.ptr = (char*) pj_pool_alloc(pool, M); stream->name.slen = pj_ansi_snprintf(stream->name.ptr, M, "vstrm%p", stream); /* Create and initialize codec: */ status = pjmedia_vid_codec_mgr_alloc_codec(stream->codec_mgr, &info->codec_info, &stream->codec); if (status != PJ_SUCCESS) return status; /* Get codec param: */ if (!info->codec_param) { pjmedia_vid_codec_param def_param; status = pjmedia_vid_codec_mgr_get_default_param(stream->codec_mgr, &info->codec_info, &def_param); if (status != PJ_SUCCESS) return status; info->codec_param = pjmedia_vid_codec_param_clone(pool, &def_param); pj_assert(info->codec_param); } /* Init codec param and adjust MTU */ info->codec_param->dir = info->dir; info->codec_param->enc_mtu -= (sizeof(pjmedia_rtp_hdr) + PJMEDIA_STREAM_RESV_PAYLOAD_LEN); if (info->codec_param->enc_mtu > PJMEDIA_MAX_MTU) info->codec_param->enc_mtu = PJMEDIA_MAX_MTU; /* Packet size estimation for decoding direction */ vfd_enc = pjmedia_format_get_video_format_detail( &info->codec_param->enc_fmt, PJ_TRUE); vfd_dec = pjmedia_format_get_video_format_detail( &info->codec_param->dec_fmt, PJ_TRUE); /* Init stream: */ stream->endpt = endpt; stream->dir = info->dir; stream->user_data = user_data; stream->rtcp_interval = (PJMEDIA_RTCP_INTERVAL-500 + (pj_rand()%1000)) * info->codec_info.clock_rate / 1000; stream->rtcp_sdes_bye_disabled = info->rtcp_sdes_bye_disabled; stream->jb_last_frm = PJMEDIA_JB_NORMAL_FRAME; #if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0 stream->use_ka = info->use_ka; #endif /* Build random RTCP CNAME. CNAME has user@host format */ stream->cname.ptr = p = (char*) pj_pool_alloc(pool, 20); pj_create_random_string(p, 5); p += 5; *p++ = '@'; *p++ = 'p'; *p++ = 'j'; pj_create_random_string(p, 6); p += 6; *p++ = '.'; *p++ = 'o'; *p++ = 'r'; *p++ = 'g'; stream->cname.slen = p - stream->cname.ptr; /* Create mutex to protect jitter buffer: */ status = pj_mutex_create_simple(pool, NULL, &stream->jb_mutex); if (status != PJ_SUCCESS) return status; /* Init and open the codec. */ status = pjmedia_vid_codec_init(stream->codec, pool); if (status != PJ_SUCCESS) return status; status = pjmedia_vid_codec_open(stream->codec, info->codec_param); if (status != PJ_SUCCESS) return status; /* Subscribe to codec events */ pjmedia_event_subscribe(NULL, &stream_event_cb, stream, stream->codec); /* Estimate the maximum frame size */ stream->frame_size = vfd_enc->size.w * vfd_enc->size.h * 4; #if 0 stream->frame_size = vfd_enc->max_bps/8 * vfd_enc->fps.denum / vfd_enc->fps.num; /* As the maximum frame_size is not represented directly by maximum bps * (which includes intra and predicted frames), let's increase the * frame size value for safety. */ stream->frame_size <<= 4; #endif /* Validate the frame size */ if (stream->frame_size == 0 || stream->frame_size > PJMEDIA_MAX_VIDEO_ENC_FRAME_SIZE) { stream->frame_size = PJMEDIA_MAX_VIDEO_ENC_FRAME_SIZE; } /* Get frame length in timestamp unit */ stream->frame_ts_len = info->codec_info.clock_rate * vfd_enc->fps.denum / vfd_enc->fps.num; /* Initialize send rate states */ pj_get_timestamp_freq(&stream->ts_freq); if (info->rc_cfg.bandwidth == 0) info->rc_cfg.bandwidth = vfd_enc->max_bps; /* For simple blocking, need to have bandwidth large enough, otherwise * we can slow down the transmission too much */ if (info->rc_cfg.method==PJMEDIA_VID_STREAM_RC_SIMPLE_BLOCKING && info->rc_cfg.bandwidth < vfd_enc->avg_bps * 3) { info->rc_cfg.bandwidth = vfd_enc->avg_bps * 3; } /* Override the initial framerate in the decoding direction. This initial * value will be used by the renderer to configure its clock, and setting * it to a bit higher value can avoid the possibility of high latency * caused by clock drift (remote encoder clock runs slightly faster than * local renderer clock) or video setup lag. Note that the actual framerate * will be continuously calculated based on the incoming RTP timestamps. */ #if 0 vfd_dec->fps.num = vfd_dec->fps.num * 3 / 2; #endif stream->dec_max_fps = vfd_dec->fps; /* Create decoder channel */ status = create_channel( pool, stream, PJMEDIA_DIR_DECODING, info->rx_pt, info, &stream->dec); if (status != PJ_SUCCESS) return status; /* Create encoder channel */ status = create_channel( pool, stream, PJMEDIA_DIR_ENCODING, info->tx_pt, info, &stream->enc); if (status != PJ_SUCCESS) return status; /* Create temporary buffer for immediate decoding */ stream->dec_max_size = vfd_dec->size.w * vfd_dec->size.h * 4; stream->dec_frame.buf = pj_pool_alloc(pool, stream->dec_max_size); /* Init jitter buffer parameters: */ frm_ptime = 1000 * vfd_enc->fps.denum / vfd_enc->fps.num; chunks_per_frm = stream->frame_size / PJMEDIA_MAX_MRU; if (chunks_per_frm < MIN_CHUNKS_PER_FRM) chunks_per_frm = MIN_CHUNKS_PER_FRM; /* JB max count, default 500ms */ if (info->jb_max >= frm_ptime) jb_max = info->jb_max * chunks_per_frm / frm_ptime; else jb_max = 500 * chunks_per_frm / frm_ptime; /* JB min prefetch, default 1 frame */ if (info->jb_min_pre >= frm_ptime) jb_min_pre = info->jb_min_pre * chunks_per_frm / frm_ptime; else jb_min_pre = 1; /* JB max prefetch, default 4/5 JB max count */ if (info->jb_max_pre >= frm_ptime) jb_max_pre = info->jb_max_pre * chunks_per_frm / frm_ptime; else jb_max_pre = jb_max * 4 / 5; /* JB init prefetch, default 0 */ if (info->jb_init >= frm_ptime) jb_init = info->jb_init * chunks_per_frm / frm_ptime; else jb_init = 0; /* Allocate array for temporary storage for assembly of incoming * frames. Add more just in case. */ stream->rx_frame_cnt = chunks_per_frm * 2; stream->rx_frames = pj_pool_calloc(pool, stream->rx_frame_cnt, sizeof(stream->rx_frames[0])); /* Create jitter buffer */ status = pjmedia_jbuf_create(pool, &stream->dec->port.info.name, PJMEDIA_MAX_MRU, 1000 * vfd_enc->fps.denum / vfd_enc->fps.num, jb_max, &stream->jb); if (status != PJ_SUCCESS) return status; /* Set up jitter buffer */ pjmedia_jbuf_set_adaptive(stream->jb, jb_init, jb_min_pre, jb_max_pre); pjmedia_jbuf_set_discard(stream->jb, PJMEDIA_JB_DISCARD_NONE); /* Init RTCP session: */ { pjmedia_rtcp_session_setting rtcp_setting; pjmedia_rtcp_session_setting_default(&rtcp_setting); rtcp_setting.name = stream->name.ptr; rtcp_setting.ssrc = info->ssrc; rtcp_setting.rtp_ts_base = pj_ntohl(stream->enc->rtp.out_hdr.ts); rtcp_setting.clock_rate = info->codec_info.clock_rate; rtcp_setting.samples_per_frame = 1; pjmedia_rtcp_init2(&stream->rtcp, &rtcp_setting); } /* Allocate outgoing RTCP buffer, should be enough to hold SR/RR, SDES, * BYE, and XR. */ stream->out_rtcp_pkt_size = sizeof(pjmedia_rtcp_sr_pkt) + sizeof(pjmedia_rtcp_common) + (4 + (unsigned)stream->cname.slen) + 32; if (stream->out_rtcp_pkt_size > PJMEDIA_MAX_MTU) stream->out_rtcp_pkt_size = PJMEDIA_MAX_MTU; stream->out_rtcp_pkt = pj_pool_alloc(pool, stream->out_rtcp_pkt_size); /* Only attach transport when stream is ready. */ status = pjmedia_transport_attach(tp, stream, &info->rem_addr, &info->rem_rtcp, pj_sockaddr_get_len(&info->rem_addr), &on_rx_rtp, &on_rx_rtcp); if (status != PJ_SUCCESS) return status; stream->transport = tp; /* Send RTCP SDES */ if (!stream->rtcp_sdes_bye_disabled) { pjmedia_vid_stream_send_rtcp_sdes(stream); } #if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0 /* NAT hole punching by sending KA packet via RTP transport. */ if (stream->use_ka) send_keep_alive_packet(stream); #endif #if TRACE_JB { char trace_name[PJ_MAXPATH]; pj_ssize_t len; pj_ansi_snprintf(trace_name, sizeof(trace_name), TRACE_JB_PATH_PREFIX "%s.csv", channel->port.info.name.ptr); status = pj_file_open(pool, trace_name, PJ_O_RDWR, &stream->trace_jb_fd); if (status != PJ_SUCCESS) { stream->trace_jb_fd = TRACE_JB_INVALID_FD; PJ_LOG(3,(THIS_FILE, "Failed creating RTP trace file '%s'", trace_name)); } else { stream->trace_jb_buf = (char*)pj_pool_alloc(pool, PJ_LOG_MAX_SIZE); /* Print column header */ len = pj_ansi_snprintf(stream->trace_jb_buf, PJ_LOG_MAX_SIZE, "Time, Operation, Size, Frame Count, " "Frame type, RTP Seq, RTP TS, RTP M, " "JB size, JB burst level, JB prefetch\n"); if (len < 1 || len >= PJ_LOG_MAX_SIZE) len = PJ_LOG_MAX_SIZE - 1; pj_file_write(stream->trace_jb_fd, stream->trace_jb_buf, &len); pj_file_flush(stream->trace_jb_fd); } } #endif /* Save the stream info */ pj_memcpy(&stream->info, info, sizeof(*info)); stream->info.codec_param = pjmedia_vid_codec_param_clone( pool, info->codec_param); /* Success! */ *p_stream = stream; PJ_LOG(5,(THIS_FILE, "Video stream %s created", stream->name.ptr)); return PJ_SUCCESS; } /* * Destroy stream. */ PJ_DEF(pj_status_t) pjmedia_vid_stream_destroy( pjmedia_vid_stream *stream ) { PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); #if TRACE_RC { unsigned total_time; total_time = pj_elapsed_msec(&stream->tx_start, &stream->tx_end); PJ_LOG(5, (stream->name.ptr, "RC stat: pkt_cnt=%.2f/image, sleep=%.2fms/s, fps=%.2f", stream->rc_total_pkt*1.0/stream->rc_total_img, stream->rc_total_sleep*1000.0/total_time, stream->rc_total_img*1000.0/total_time)); } #endif /* Send RTCP BYE (also SDES) */ if (!stream->rtcp_sdes_bye_disabled) { send_rtcp(stream, PJ_TRUE, PJ_TRUE); } /* Detach from transport * MUST NOT hold stream mutex while detaching from transport, as * it may cause deadlock. See ticket #460 for the details. */ if (stream->transport) { pjmedia_transport_detach(stream->transport, stream); stream->transport = NULL; } /* This function may be called when stream is partly initialized. */ if (stream->jb_mutex) pj_mutex_lock(stream->jb_mutex); /* Free codec. */ if (stream->codec) { pjmedia_event_unsubscribe(NULL, &stream_event_cb, stream, stream->codec); pjmedia_vid_codec_close(stream->codec); pjmedia_vid_codec_mgr_dealloc_codec(stream->codec_mgr, stream->codec); stream->codec = NULL; } /* Free mutex */ if (stream->jb_mutex) { pj_mutex_destroy(stream->jb_mutex); stream->jb_mutex = NULL; } /* Destroy jitter buffer */ if (stream->jb) { pjmedia_jbuf_destroy(stream->jb); stream->jb = NULL; } #if TRACE_JB if (TRACE_JB_OPENED(stream)) { pj_file_close(stream->trace_jb_fd); stream->trace_jb_fd = TRACE_JB_INVALID_FD; } #endif if (stream->own_pool) { pj_pool_t *pool = stream->own_pool; stream->own_pool = NULL; pj_pool_release(pool); } return PJ_SUCCESS; } /* * Get the port interface. */ PJ_DEF(pj_status_t) pjmedia_vid_stream_get_port(pjmedia_vid_stream *stream, pjmedia_dir dir, pjmedia_port **p_port ) { PJ_ASSERT_RETURN(dir==PJMEDIA_DIR_ENCODING || dir==PJMEDIA_DIR_DECODING, PJ_EINVAL); if (dir == PJMEDIA_DIR_ENCODING) *p_port = &stream->enc->port; else *p_port = &stream->dec->port; return PJ_SUCCESS; } /* * Get the transport object */ PJ_DEF(pjmedia_transport*) pjmedia_vid_stream_get_transport( pjmedia_vid_stream *st) { return st->transport; } /* * Get stream statistics. */ PJ_DEF(pj_status_t) pjmedia_vid_stream_get_stat( const pjmedia_vid_stream *stream, pjmedia_rtcp_stat *stat) { PJ_ASSERT_RETURN(stream && stat, PJ_EINVAL); pj_memcpy(stat, &stream->rtcp.stat, sizeof(pjmedia_rtcp_stat)); return PJ_SUCCESS; } /* * Reset the stream statistics in the middle of a stream session. */ PJ_DEF(pj_status_t) pjmedia_vid_stream_reset_stat(pjmedia_vid_stream *stream) { PJ_ASSERT_RETURN(stream, PJ_EINVAL); pjmedia_rtcp_init_stat(&stream->rtcp.stat); return PJ_SUCCESS; } /* * Get jitter buffer state. */ PJ_DEF(pj_status_t) pjmedia_vid_stream_get_stat_jbuf( const pjmedia_vid_stream *stream, pjmedia_jb_state *state) { PJ_ASSERT_RETURN(stream && state, PJ_EINVAL); return pjmedia_jbuf_get_state(stream->jb, state); } /* * Get the stream info. */ PJ_DEF(pj_status_t) pjmedia_vid_stream_get_info( const pjmedia_vid_stream *stream, pjmedia_vid_stream_info *info) { PJ_ASSERT_RETURN(stream && info, PJ_EINVAL); pj_memcpy(info, &stream->info, sizeof(*info)); return PJ_SUCCESS; } /* * Start stream. */ PJ_DEF(pj_status_t) pjmedia_vid_stream_start(pjmedia_vid_stream *stream) { PJ_ASSERT_RETURN(stream && stream->enc && stream->dec, PJ_EINVALIDOP); if (stream->enc && (stream->dir & PJMEDIA_DIR_ENCODING)) { stream->enc->paused = 0; //pjmedia_snd_stream_start(stream->enc->snd_stream); PJ_LOG(4,(stream->enc->port.info.name.ptr, "Encoder stream started")); } else { PJ_LOG(4,(stream->enc->port.info.name.ptr, "Encoder stream paused")); } if (stream->dec && (stream->dir & PJMEDIA_DIR_DECODING)) { stream->dec->paused = 0; //pjmedia_snd_stream_start(stream->dec->snd_stream); PJ_LOG(4,(stream->dec->port.info.name.ptr, "Decoder stream started")); } else { PJ_LOG(4,(stream->dec->port.info.name.ptr, "Decoder stream paused")); } return PJ_SUCCESS; } /* * Check status. */ PJ_DEF(pj_bool_t) pjmedia_vid_stream_is_running(pjmedia_vid_stream *stream, pjmedia_dir dir) { pj_bool_t is_running = PJ_TRUE; PJ_ASSERT_RETURN(stream, PJ_FALSE); if (dir & PJMEDIA_DIR_ENCODING) { is_running &= (stream->enc && !stream->enc->paused); } if (dir & PJMEDIA_DIR_DECODING) { is_running &= (stream->dec && !stream->dec->paused); } return is_running; } /* * Pause stream. */ PJ_DEF(pj_status_t) pjmedia_vid_stream_pause(pjmedia_vid_stream *stream, pjmedia_dir dir) { PJ_ASSERT_RETURN(stream, PJ_EINVAL); if ((dir & PJMEDIA_DIR_ENCODING) && stream->enc) { stream->enc->paused = 1; PJ_LOG(4,(stream->enc->port.info.name.ptr, "Encoder stream paused")); } if ((dir & PJMEDIA_DIR_DECODING) && stream->dec) { stream->dec->paused = 1; /* Also reset jitter buffer */ pj_mutex_lock( stream->jb_mutex ); pjmedia_jbuf_reset(stream->jb); pj_mutex_unlock( stream->jb_mutex ); PJ_LOG(4,(stream->dec->port.info.name.ptr, "Decoder stream paused")); } return PJ_SUCCESS; } /* * Resume stream */ PJ_DEF(pj_status_t) pjmedia_vid_stream_resume(pjmedia_vid_stream *stream, pjmedia_dir dir) { PJ_ASSERT_RETURN(stream, PJ_EINVAL); if ((dir & PJMEDIA_DIR_ENCODING) && stream->enc) { stream->enc->paused = 0; PJ_LOG(4,(stream->enc->port.info.name.ptr, "Encoder stream resumed")); } if ((dir & PJMEDIA_DIR_DECODING) && stream->dec) { stream->dec->paused = 0; PJ_LOG(4,(stream->dec->port.info.name.ptr, "Decoder stream resumed")); } return PJ_SUCCESS; } /* * Force stream to send video keyframe. */ PJ_DEF(pj_status_t) pjmedia_vid_stream_send_keyframe( pjmedia_vid_stream *stream) { PJ_ASSERT_RETURN(stream, PJ_EINVAL); if (!pjmedia_vid_stream_is_running(stream, PJMEDIA_DIR_ENCODING)) return PJ_EINVALIDOP; stream->force_keyframe = PJ_TRUE; return PJ_SUCCESS; } /* * Send RTCP SDES. */ PJ_DEF(pj_status_t) pjmedia_vid_stream_send_rtcp_sdes( pjmedia_vid_stream *stream) { PJ_ASSERT_RETURN(stream, PJ_EINVAL); return send_rtcp(stream, PJ_TRUE, PJ_FALSE); } /* * Send RTCP BYE. */ PJ_DEF(pj_status_t) pjmedia_vid_stream_send_rtcp_bye( pjmedia_vid_stream *stream) { PJ_ASSERT_RETURN(stream, PJ_EINVAL); if (stream->enc && stream->transport) { return send_rtcp(stream, PJ_TRUE, PJ_TRUE); } return PJ_SUCCESS; } /* * Send RTCP PLI. */ PJ_DEF(pj_status_t) pjmedia_vid_stream_send_rtcp_pli( pjmedia_vid_stream *stream) { PJ_ASSERT_RETURN(stream, PJ_EINVAL); if (stream->enc && stream->transport) { void *sr_rr_pkt; pj_uint8_t *pkt; int len, max_len; pj_status_t status; pj_size_t pli_len; /* Build RTCP RR/SR packet */ pjmedia_rtcp_build_rtcp(&stream->rtcp, &sr_rr_pkt, &len); pkt = (pj_uint8_t*) stream->out_rtcp_pkt; pj_memcpy(pkt, sr_rr_pkt, len); max_len = stream->out_rtcp_pkt_size; /* Build RTCP PLI packet */ pli_len = max_len - len; status = pjmedia_rtcp_build_rtcp_pli(&stream->rtcp, pkt+len, &pli_len); if (status != PJ_SUCCESS) { PJ_PERROR(4,(stream->name.ptr, status, "Error generating RTCP PLI")); } else { len += (int)pli_len; } /* Send! */ status = pjmedia_transport_send_rtcp(stream->transport, pkt, len); return status; } return PJ_SUCCESS; } /* * Initialize the video stream rate control with default settings. */ PJ_DEF(void) pjmedia_vid_stream_rc_config_default(pjmedia_vid_stream_rc_config *cfg) { pj_bzero(cfg, sizeof(*cfg)); cfg->method = PJMEDIA_VID_STREAM_RC_SIMPLE_BLOCKING; } #endif /* PJMEDIA_HAS_VIDEO */