From 181bc7b13d8ca62fb6502d8a721517977a11f4a8 Mon Sep 17 00:00:00 2001 From: bossiel Date: Tue, 9 Apr 2013 22:22:16 +0000 Subject: [PATCH] - Adds support for ZeroArtifacts (Perfect video quality) - Better interop with WebRTC endpoints (better video quality) - Lock-free on MediaSessionMgr for better performances on both audio and video - Re-design the video jitter buffer for better CPU prefs and video quality. Request lost frames (RTC-NACK) as many times as required to deal with RTCP-losses. The FPS guesser is smarter. - Fix issues on RTP timestamps on video pkts - Update libsrtp binaries on Android and Windows (Use latest CVS) - Better interop with other h264-rtp implementations (e.g. gstreamer, bria, cisco, polycom, lync...) - Fix issue 233 (tinyNET does not compile on MAC + fix/patch) - Fix issue 234 (tinyDAV does not compile on MAC) - Fix issue 238 (iOS: Bad audio quality when audio/video call uses cpu intensive audio codec (e.g. g729 or speex)) - Fix issue 239 (Adds support for thread priority setting). Timers and audio/video threads now use high priority. - Fix issue 242 (Hold/Resume fails when audio driver is opensl-es (Android)) - Fix issue 243 (PictureID in VP8 is not correct (only happens when there is overflow on the first 4 bytes)) - Fix issue 244 (Adds callbacks from codecs to session to signal IDR frames decoding) - Fix issue 245 (Fail to decode h264 buffer) - FIx issue 246 (Gnu Autotools: Detect support for monotonic timers in configure.ac) --- .../bindings/_common/MediaSessionMgr.cxx | 10 +- .../bindings/_common/MediaSessionMgr.h | 4 +- .../bindings/_common/ProxyConsumer.cxx | 99 +- .../bindings/_common/ProxyProducer.cxx | 3 +- .../bindings/csharp/MediaSessionMgr.cs | 14 +- .../bindings/csharp/tinyWRAPPINVOKE.cs | 8 +- .../bindings/csharp/tinyWRAP_wrap.cxx | 24 +- .../bindings/java/MediaSessionMgr.java | 12 +- .../java/android/MediaSessionMgr.java | 12 +- .../bindings/java/android/tinyWRAPJNI.java | 4 +- .../bindings/java/android/tinyWRAP_wrap.cxx | 28 +- .../doubango/bindings/java/tinyWRAPJNI.java | 4 +- .../doubango/bindings/java/tinyWRAP_wrap.cxx | 28 +- .../2.0/doubango/bindings/perl/tinyWRAP.pm | 2 + .../doubango/bindings/perl/tinyWRAP_wrap.cxx | 44 +- .../2.0/doubango/bindings/python/tinyWRAP.py | 16 +- .../bindings/python/tinyWRAP_wrap.cxx | 34 +- branches/2.0/doubango/configure.ac | 8 + .../plugins/audio_opensles/audio_opensles.cxx | 2 + .../audio_opensles/audio_opensles_device.cxx | 23 +- .../android/include/srtp/config.h | 10 +- .../thirdparties/android/lib/libsrtp.a | Bin 152914 -> 152914 bytes .../thirdparties/win32/include/srtp/config.h | 2 +- .../thirdparties/win32/lib/srtp/libsrtp.a | Bin 125182 -> 144936 bytes .../audio/coreaudio/tdav_producer_audiounit.h | 3 - .../tinydav/audio/tdav_speakup_jitterbuffer.h | 2 +- .../tinydav/audio/tdav_speex_jitterbuffer.h | 2 +- .../tinydav/video/jb/tdav_video_frame.h | 2 + .../tinydav/video/tdav_session_video.h | 10 + .../doubango/tinyDAV/include/tinydav_config.h | 3 +- .../audio/coreaudio/tdav_producer_audiounit.c | 155 +- .../tinyDAV/src/codecs/h263/tdav_codec_h263.c | 23 +- .../tinyDAV/src/codecs/h264/tdav_codec_h264.c | 2057 ++++----- .../src/codecs/h264/tdav_codec_h264_rtp.c | 735 ++-- .../src/codecs/mp4ves/tdav_codec_mp4ves.c | 2 +- .../src/codecs/theora/tdav_codec_theora.c | 2 +- .../tinyDAV/src/codecs/vpx/tdav_codec_vp8.c | 1839 ++++---- .../doubango/tinyDAV/src/tdav_session_av.c | 4 +- .../tinyDAV/src/video/jb/tdav_video_frame.c | 51 +- .../tinyDAV/src/video/jb/tdav_video_jb.c | 266 +- .../tinyDAV/src/video/tdav_session_video.c | 178 +- .../include/tinymedia/tmedia_common.h | 2 +- .../include/tinymedia/tmedia_defaults.h | 3 + .../doubango/tinyMEDIA/src/tmedia_defaults.c | 733 +-- .../doubango/tinyMEDIA/src/tmedia_session.c | 3910 +++++++++-------- .../tinyNET/src/ice/tnet_ice_candidate.c | 28 +- .../doubango/tinyNET/src/ice/tnet_ice_ctx.c | 13 +- .../doubango/tinyNET/src/ice/tnet_ice_pair.c | 110 +- .../doubango/tinyNET/src/ice/tnet_ice_pair.h | 1 + .../2.0/doubango/tinyNET/src/tls/tnet_tls.h | 1 + .../2.0/doubango/tinyNET/src/tnet_transport.c | 7 + .../2.0/doubango/tinyNET/src/tnet_utils.c | 11 +- .../include/tinyrtp/rtcp/trtp_rtcp_session.h | 2 +- .../tinyRTP/include/tinyrtp/trtp_manager.h | 1 + .../tinyRTP/src/rtcp/trtp_rtcp_report_fb.c | 2 +- .../tinyRTP/src/rtcp/trtp_rtcp_session.c | 38 +- .../2.0/doubango/tinyRTP/src/trtp_manager.c | 6 +- branches/2.0/doubango/tinyRTP/src/trtp_srtp.c | 6 + branches/2.0/doubango/tinySAK/Makefile.am | 4 + .../2.0/doubango/tinySAK/src/tsk_runnable.c | 41 +- .../2.0/doubango/tinySAK/src/tsk_runnable.h | 4 + .../2.0/doubango/tinySAK/src/tsk_thread.c | 2 +- .../2.0/doubango/tinySAK/src/tsk_thread.h | 11 +- branches/2.0/doubango/tinySAK/src/tsk_time.c | 9 + branches/2.0/doubango/tinySAK/src/tsk_time.h | 1 + 65 files changed, 5658 insertions(+), 5013 deletions(-) diff --git a/branches/2.0/doubango/bindings/_common/MediaSessionMgr.cxx b/branches/2.0/doubango/bindings/_common/MediaSessionMgr.cxx index 23edbb67..e2c608e2 100644 --- a/branches/2.0/doubango/bindings/_common/MediaSessionMgr.cxx +++ b/branches/2.0/doubango/bindings/_common/MediaSessionMgr.cxx @@ -479,9 +479,17 @@ bool MediaSessionMgr::defaultsGetByPassDecoding(){ bool MediaSessionMgr::defaultsSetVideoJbEnabled(bool enabled){ return (tmedia_defaults_set_videojb_enabled(enabled ? tsk_true : tsk_false) == 0); } -bool MediaSessionMgr::defaultsGetVideoJbEnabled(bool enabled){ +bool MediaSessionMgr::defaultsGetVideoJbEnabled(){ return (tmedia_defaults_get_videojb_enabled() == tsk_true); } + +bool MediaSessionMgr::defaultsSetVideoZeroArtifactsEnabled(bool enabled){ + return (tmedia_defaults_set_video_zeroartifacts_enabled(enabled ? tsk_true : tsk_false) == 0); +} +bool MediaSessionMgr::defaultsGetVideoZeroArtifactsEnabled(){ + return (tmedia_defaults_get_video_zeroartifacts_enabled() == tsk_true); +} + bool MediaSessionMgr::defaultsSetRtpBuffSize(unsigned buffSize){ return (tmedia_defaults_set_rtpbuff_size(buffSize) == 0); } diff --git a/branches/2.0/doubango/bindings/_common/MediaSessionMgr.h b/branches/2.0/doubango/bindings/_common/MediaSessionMgr.h index 0d49dc43..ff8ca6ef 100644 --- a/branches/2.0/doubango/bindings/_common/MediaSessionMgr.h +++ b/branches/2.0/doubango/bindings/_common/MediaSessionMgr.h @@ -139,7 +139,9 @@ public: static bool defaultsSetByPassDecoding(bool enabled); static bool defaultsGetByPassDecoding(); static bool defaultsSetVideoJbEnabled(bool enabled); - static bool defaultsGetVideoJbEnabled(bool enabled); + static bool defaultsGetVideoJbEnabled(); + static bool defaultsSetVideoZeroArtifactsEnabled(bool enabled); + static bool defaultsGetVideoZeroArtifactsEnabled(); static bool defaultsSetRtpBuffSize(unsigned buffSize); static unsigned defaultsGetRtpBuffSize(); static bool defaultsSetAvpfTail(unsigned tail_min, unsigned tail_max); diff --git a/branches/2.0/doubango/bindings/_common/ProxyConsumer.cxx b/branches/2.0/doubango/bindings/_common/ProxyConsumer.cxx index 777ae1f2..6d4219f2 100644 --- a/branches/2.0/doubango/bindings/_common/ProxyConsumer.cxx +++ b/branches/2.0/doubango/bindings/_common/ProxyConsumer.cxx @@ -46,6 +46,7 @@ typedef struct twrap_consumer_proxy_audio_s uint64_t id; tsk_bool_t started; + const ProxyAudioConsumer* pcConsumer; // thread-safe and will be destroyed at the time as the "struct" } twrap_consumer_proxy_audio_t; #define TWRAP_CONSUMER_PROXY_AUDIO(self) ((twrap_consumer_proxy_audio_t*)(self)) @@ -61,15 +62,15 @@ int twrap_consumer_proxy_audio_set(tmedia_consumer_t* _self, const tmedia_param_ int twrap_consumer_proxy_audio_prepare(tmedia_consumer_t* self, const tmedia_codec_t* codec) { + twrap_consumer_proxy_audio_t* audio = TWRAP_CONSUMER_PROXY_AUDIO(self); ProxyPluginMgr* manager; int ret = -1; if(codec && (manager = ProxyPluginMgr::getInstance())){ - const ProxyAudioConsumer* audioConsumer; - if((audioConsumer = manager->findAudioConsumer(TWRAP_CONSUMER_PROXY_AUDIO(self)->id)) && audioConsumer->getCallback()){ + if((audio->pcConsumer = manager->findAudioConsumer(audio->id)) && audio->pcConsumer->getCallback()){ self->audio.ptime = codec->plugin->audio.ptime; self->audio.in.channels = codec->plugin->audio.channels; self->audio.in.rate = codec->plugin->rate; - ret = audioConsumer->getCallback()->prepare((int)codec->plugin->audio.ptime, codec->plugin->rate, codec->plugin->audio.channels); + ret = audio->pcConsumer->getCallback()->prepare((int)codec->plugin->audio.ptime, codec->plugin->rate, codec->plugin->audio.channels); } } @@ -93,17 +94,23 @@ int twrap_consumer_proxy_audio_start(tmedia_consumer_t* self) int twrap_consumer_proxy_audio_consume(tmedia_consumer_t* self, const void* buffer, tsk_size_t size, const tsk_object_t* proto_hdr) { - ProxyPluginMgr* manager; + twrap_consumer_proxy_audio_t* audio = TWRAP_CONSUMER_PROXY_AUDIO(self); + + if(!audio->pcConsumer){ + ProxyPluginMgr* manager; + if((manager = ProxyPluginMgr::getInstance())){ + audio->pcConsumer = manager->findAudioConsumer(audio->id); + } + } + + ProxyAudioConsumerCallback* callback; int ret = -1; - if((manager = ProxyPluginMgr::getInstance())){ - const ProxyAudioConsumer* audioConsumer; - if((audioConsumer = manager->findAudioConsumer(TWRAP_CONSUMER_PROXY_AUDIO(self)->id)) && audioConsumer->getCallback()){ - if(audioConsumer->getCallback()->putInJitterBuffer()){ - ret = tdav_consumer_audio_put(TDAV_CONSUMER_AUDIO(self), buffer, size, proto_hdr); - } - else{ - ret = audioConsumer->getCallback()->consume(buffer, size, proto_hdr); - } + if(audio->pcConsumer && (callback = audio->pcConsumer->getCallback())){ + if(callback->putInJitterBuffer()){ + ret = tdav_consumer_audio_put(TDAV_CONSUMER_AUDIO(self), buffer, size, proto_hdr); + } + else{ + ret = callback->consume(buffer, size, proto_hdr); } } @@ -381,6 +388,7 @@ typedef struct twrap_consumer_proxy_video_s uint64_t id; tsk_bool_t started; + const ProxyVideoConsumer* pcConsumer; // thread-safe and will be destroyed at the time as the "struct" } twrap_consumer_proxy_video_t; #define TWRAP_CONSUMER_PROXY_VIDEO(self) ((twrap_consumer_proxy_video_t*)(self)) @@ -393,25 +401,25 @@ int twrap_consumer_proxy_video_set(tmedia_consumer_t* self, const tmedia_param_t int twrap_consumer_proxy_video_prepare(tmedia_consumer_t* self, const tmedia_codec_t* codec) { ProxyPluginMgr* manager; + twrap_consumer_proxy_video_t* video = TWRAP_CONSUMER_PROXY_VIDEO(self); int ret = -1; if(codec && (manager = ProxyPluginMgr::getInstance())){ - const ProxyVideoConsumer* videoConsumer; - if((videoConsumer = manager->findVideoConsumer(TWRAP_CONSUMER_PROXY_VIDEO(self)->id)) && videoConsumer->getCallback()){ + if((video->pcConsumer = manager->findVideoConsumer(video->id)) && video->pcConsumer->getCallback()){ self->video.fps = TMEDIA_CODEC_VIDEO(codec)->in.fps; // in self->video.in.chroma = tmedia_chroma_yuv420p; self->video.in.width = TMEDIA_CODEC_VIDEO(codec)->in.width; self->video.in.height = TMEDIA_CODEC_VIDEO(codec)->in.height; // display (out) - self->video.display.chroma = videoConsumer->getChroma(); - self->video.display.auto_resize = videoConsumer->getAutoResizeDisplay(); + self->video.display.chroma = video->pcConsumer->getChroma(); + self->video.display.auto_resize = video->pcConsumer->getAutoResizeDisplay(); if(!self->video.display.width){ self->video.display.width = self->video.in.width; } if(!self->video.display.height){ self->video.display.height = self->video.in.height; } - ret = videoConsumer->getCallback()->prepare(TMEDIA_CODEC_VIDEO(codec)->in.width, TMEDIA_CODEC_VIDEO(codec)->in.height, TMEDIA_CODEC_VIDEO(codec)->in.fps); + ret = video->pcConsumer->getCallback()->prepare(TMEDIA_CODEC_VIDEO(codec)->in.width, TMEDIA_CODEC_VIDEO(codec)->in.height, TMEDIA_CODEC_VIDEO(codec)->in.fps); } } @@ -435,37 +443,44 @@ int twrap_consumer_proxy_video_start(tmedia_consumer_t* self) int twrap_consumer_proxy_video_consume(tmedia_consumer_t* self, const void* buffer, tsk_size_t size, const tsk_object_t* proto_hdr) { - ProxyPluginMgr* manager; - int ret = -1; - if(!self || !buffer || !size){ TSK_DEBUG_ERROR("Invalid parameter"); return -1; } - - if((manager = ProxyPluginMgr::getInstance())){ - const ProxyVideoConsumer* videoConsumer; - if((videoConsumer = manager->findVideoConsumer(TWRAP_CONSUMER_PROXY_VIDEO(self)->id)) && videoConsumer->getCallback()){ - if(tdav_consumer_video_has_jb(TDAV_CONSUMER_VIDEO(self))){ - ret = tdav_consumer_video_put(TDAV_CONSUMER_VIDEO(self), buffer, size, proto_hdr); - } - else{ - if(videoConsumer->hasConsumeBuffer()){ - unsigned nCopiedSize = videoConsumer->copyBuffer(buffer, size); - ret = videoConsumer->getCallback()->bufferCopied(nCopiedSize, size); - } - else{ - ProxyVideoFrame* frame = new ProxyVideoFrame(buffer, size, const_cast(videoConsumer)->getDecodedWidth(), const_cast(videoConsumer)->getDecodedHeight(), proto_hdr); - ret = videoConsumer->getCallback()->consume(frame); - delete frame, frame = tsk_null; - } - } - } - else{ - TSK_DEBUG_ERROR("Cannot find consumer with id=%lld", TWRAP_CONSUMER_PROXY_VIDEO(self)->id); + + twrap_consumer_proxy_video_t* video = TWRAP_CONSUMER_PROXY_VIDEO(self); + + if(!video->pcConsumer){ + ProxyPluginMgr* manager; + if((manager = ProxyPluginMgr::getInstance())){ + video->pcConsumer = manager->findVideoConsumer(video->id); } } + int ret = -1; + ProxyVideoConsumerCallback* callback; + + if(video->pcConsumer && (callback = video->pcConsumer->getCallback())){ + if(tdav_consumer_video_has_jb(TDAV_CONSUMER_VIDEO(self))){ + ret = tdav_consumer_video_put(TDAV_CONSUMER_VIDEO(self), buffer, size, proto_hdr); + } + else{ + if(video->pcConsumer->hasConsumeBuffer()){ + unsigned nCopiedSize = video->pcConsumer->copyBuffer(buffer, size); + ret = callback->bufferCopied(nCopiedSize, size); + } + else{ + ProxyVideoFrame* frame = new ProxyVideoFrame(buffer, size, const_cast(video->pcConsumer)->getDecodedWidth(), const_cast(video->pcConsumer)->getDecodedHeight(), proto_hdr); + ret = callback->consume(frame); + delete frame, frame = tsk_null; + } + } + } + else if(!video->pcConsumer){ + TSK_DEBUG_ERROR("Cannot find consumer with id=%lld", TWRAP_CONSUMER_PROXY_VIDEO(self)->id); + } + + return ret; } diff --git a/branches/2.0/doubango/bindings/_common/ProxyProducer.cxx b/branches/2.0/doubango/bindings/_common/ProxyProducer.cxx index 6f3caeed..c82a64a7 100644 --- a/branches/2.0/doubango/bindings/_common/ProxyProducer.cxx +++ b/branches/2.0/doubango/bindings/_common/ProxyProducer.cxx @@ -451,7 +451,7 @@ static tsk_object_t* twrap_producer_proxy_video_dtor(tsk_object_t * self) { twrap_producer_proxy_video_t *producer = (twrap_producer_proxy_video_t *)self; if(producer){ - + TSK_DEBUG_INFO("twrap_producer_proxy_video_dtor()"); /* stop */ if(producer->started){ twrap_producer_proxy_video_stop(TMEDIA_PRODUCER(producer)); @@ -512,6 +512,7 @@ ProxyVideoProducer::ProxyVideoProducer(tmedia_chroma_t eChroma, struct twrap_pro ProxyVideoProducer::~ProxyVideoProducer() { + TSK_DEBUG_INFO("~ProxyVideoProducer"); } int ProxyVideoProducer::getRotation()const diff --git a/branches/2.0/doubango/bindings/csharp/MediaSessionMgr.cs b/branches/2.0/doubango/bindings/csharp/MediaSessionMgr.cs index 1437cc17..8daa3614 100644 --- a/branches/2.0/doubango/bindings/csharp/MediaSessionMgr.cs +++ b/branches/2.0/doubango/bindings/csharp/MediaSessionMgr.cs @@ -329,8 +329,18 @@ public class MediaSessionMgr : IDisposable { return ret; } - public static bool defaultsGetVideoJbEnabled(bool enabled) { - bool ret = tinyWRAPPINVOKE.MediaSessionMgr_defaultsGetVideoJbEnabled(enabled); + public static bool defaultsGetVideoJbEnabled() { + bool ret = tinyWRAPPINVOKE.MediaSessionMgr_defaultsGetVideoJbEnabled(); + return ret; + } + + public static bool defaultsSetVideoZeroArtifactsEnabled(bool enabled) { + bool ret = tinyWRAPPINVOKE.MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled(enabled); + return ret; + } + + public static bool defaultsGetVideoZeroArtifactsEnabled() { + bool ret = tinyWRAPPINVOKE.MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled(); return ret; } diff --git a/branches/2.0/doubango/bindings/csharp/tinyWRAPPINVOKE.cs b/branches/2.0/doubango/bindings/csharp/tinyWRAPPINVOKE.cs index 9d8f631b..a1d4b093 100644 --- a/branches/2.0/doubango/bindings/csharp/tinyWRAPPINVOKE.cs +++ b/branches/2.0/doubango/bindings/csharp/tinyWRAPPINVOKE.cs @@ -463,7 +463,13 @@ class tinyWRAPPINVOKE { public static extern bool MediaSessionMgr_defaultsSetVideoJbEnabled(bool jarg1); [DllImport("tinyWRAP", EntryPoint="CSharp_MediaSessionMgr_defaultsGetVideoJbEnabled")] - public static extern bool MediaSessionMgr_defaultsGetVideoJbEnabled(bool jarg1); + public static extern bool MediaSessionMgr_defaultsGetVideoJbEnabled(); + + [DllImport("tinyWRAP", EntryPoint="CSharp_MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled")] + public static extern bool MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled(bool jarg1); + + [DllImport("tinyWRAP", EntryPoint="CSharp_MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled")] + public static extern bool MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled(); [DllImport("tinyWRAP", EntryPoint="CSharp_MediaSessionMgr_defaultsSetRtpBuffSize")] public static extern bool MediaSessionMgr_defaultsSetRtpBuffSize(uint jarg1); diff --git a/branches/2.0/doubango/bindings/csharp/tinyWRAP_wrap.cxx b/branches/2.0/doubango/bindings/csharp/tinyWRAP_wrap.cxx index 678c472f..f744beab 100644 --- a/branches/2.0/doubango/bindings/csharp/tinyWRAP_wrap.cxx +++ b/branches/2.0/doubango/bindings/csharp/tinyWRAP_wrap.cxx @@ -2288,13 +2288,33 @@ SWIGEXPORT unsigned int SWIGSTDCALL CSharp_MediaSessionMgr_defaultsSetVideoJbEna } -SWIGEXPORT unsigned int SWIGSTDCALL CSharp_MediaSessionMgr_defaultsGetVideoJbEnabled(unsigned int jarg1) { +SWIGEXPORT unsigned int SWIGSTDCALL CSharp_MediaSessionMgr_defaultsGetVideoJbEnabled() { + unsigned int jresult ; + bool result; + + result = (bool)MediaSessionMgr::defaultsGetVideoJbEnabled(); + jresult = result; + return jresult; +} + + +SWIGEXPORT unsigned int SWIGSTDCALL CSharp_MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled(unsigned int jarg1) { unsigned int jresult ; bool arg1 ; bool result; arg1 = jarg1 ? true : false; - result = (bool)MediaSessionMgr::defaultsGetVideoJbEnabled(arg1); + result = (bool)MediaSessionMgr::defaultsSetVideoZeroArtifactsEnabled(arg1); + jresult = result; + return jresult; +} + + +SWIGEXPORT unsigned int SWIGSTDCALL CSharp_MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled() { + unsigned int jresult ; + bool result; + + result = (bool)MediaSessionMgr::defaultsGetVideoZeroArtifactsEnabled(); jresult = result; return jresult; } diff --git a/branches/2.0/doubango/bindings/java/MediaSessionMgr.java b/branches/2.0/doubango/bindings/java/MediaSessionMgr.java index 5fe4d7a6..d05c8973 100644 --- a/branches/2.0/doubango/bindings/java/MediaSessionMgr.java +++ b/branches/2.0/doubango/bindings/java/MediaSessionMgr.java @@ -266,8 +266,16 @@ public class MediaSessionMgr { return tinyWRAPJNI.MediaSessionMgr_defaultsSetVideoJbEnabled(enabled); } - public static boolean defaultsGetVideoJbEnabled(boolean enabled) { - return tinyWRAPJNI.MediaSessionMgr_defaultsGetVideoJbEnabled(enabled); + public static boolean defaultsGetVideoJbEnabled() { + return tinyWRAPJNI.MediaSessionMgr_defaultsGetVideoJbEnabled(); + } + + public static boolean defaultsSetVideoZeroArtifactsEnabled(boolean enabled) { + return tinyWRAPJNI.MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled(enabled); + } + + public static boolean defaultsGetVideoZeroArtifactsEnabled() { + return tinyWRAPJNI.MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled(); } public static boolean defaultsSetRtpBuffSize(long buffSize) { diff --git a/branches/2.0/doubango/bindings/java/android/MediaSessionMgr.java b/branches/2.0/doubango/bindings/java/android/MediaSessionMgr.java index 5fe4d7a6..d05c8973 100644 --- a/branches/2.0/doubango/bindings/java/android/MediaSessionMgr.java +++ b/branches/2.0/doubango/bindings/java/android/MediaSessionMgr.java @@ -266,8 +266,16 @@ public class MediaSessionMgr { return tinyWRAPJNI.MediaSessionMgr_defaultsSetVideoJbEnabled(enabled); } - public static boolean defaultsGetVideoJbEnabled(boolean enabled) { - return tinyWRAPJNI.MediaSessionMgr_defaultsGetVideoJbEnabled(enabled); + public static boolean defaultsGetVideoJbEnabled() { + return tinyWRAPJNI.MediaSessionMgr_defaultsGetVideoJbEnabled(); + } + + public static boolean defaultsSetVideoZeroArtifactsEnabled(boolean enabled) { + return tinyWRAPJNI.MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled(enabled); + } + + public static boolean defaultsGetVideoZeroArtifactsEnabled() { + return tinyWRAPJNI.MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled(); } public static boolean defaultsSetRtpBuffSize(long buffSize) { diff --git a/branches/2.0/doubango/bindings/java/android/tinyWRAPJNI.java b/branches/2.0/doubango/bindings/java/android/tinyWRAPJNI.java index 0aaba596..8c5f767b 100644 --- a/branches/2.0/doubango/bindings/java/android/tinyWRAPJNI.java +++ b/branches/2.0/doubango/bindings/java/android/tinyWRAPJNI.java @@ -101,7 +101,9 @@ public class tinyWRAPJNI { public final static native boolean MediaSessionMgr_defaultsSetByPassDecoding(boolean jarg1); public final static native boolean MediaSessionMgr_defaultsGetByPassDecoding(); public final static native boolean MediaSessionMgr_defaultsSetVideoJbEnabled(boolean jarg1); - public final static native boolean MediaSessionMgr_defaultsGetVideoJbEnabled(boolean jarg1); + public final static native boolean MediaSessionMgr_defaultsGetVideoJbEnabled(); + public final static native boolean MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled(boolean jarg1); + public final static native boolean MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled(); public final static native boolean MediaSessionMgr_defaultsSetRtpBuffSize(long jarg1); public final static native long MediaSessionMgr_defaultsGetRtpBuffSize(); public final static native boolean MediaSessionMgr_defaultsSetAvpfTail(long jarg1, long jarg2); diff --git a/branches/2.0/doubango/bindings/java/android/tinyWRAP_wrap.cxx b/branches/2.0/doubango/bindings/java/android/tinyWRAP_wrap.cxx index b6ad4cbd..23e1807c 100644 --- a/branches/2.0/doubango/bindings/java/android/tinyWRAP_wrap.cxx +++ b/branches/2.0/doubango/bindings/java/android/tinyWRAP_wrap.cxx @@ -3412,7 +3412,19 @@ SWIGEXPORT jboolean JNICALL Java_org_doubango_tinyWRAP_tinyWRAPJNI_MediaSessionM } -SWIGEXPORT jboolean JNICALL Java_org_doubango_tinyWRAP_tinyWRAPJNI_MediaSessionMgr_1defaultsGetVideoJbEnabled(JNIEnv *jenv, jclass jcls, jboolean jarg1) { +SWIGEXPORT jboolean JNICALL Java_org_doubango_tinyWRAP_tinyWRAPJNI_MediaSessionMgr_1defaultsGetVideoJbEnabled(JNIEnv *jenv, jclass jcls) { + jboolean jresult = 0 ; + bool result; + + (void)jenv; + (void)jcls; + result = (bool)MediaSessionMgr::defaultsGetVideoJbEnabled(); + jresult = (jboolean)result; + return jresult; +} + + +SWIGEXPORT jboolean JNICALL Java_org_doubango_tinyWRAP_tinyWRAPJNI_MediaSessionMgr_1defaultsSetVideoZeroArtifactsEnabled(JNIEnv *jenv, jclass jcls, jboolean jarg1) { jboolean jresult = 0 ; bool arg1 ; bool result; @@ -3420,7 +3432,19 @@ SWIGEXPORT jboolean JNICALL Java_org_doubango_tinyWRAP_tinyWRAPJNI_MediaSessionM (void)jenv; (void)jcls; arg1 = jarg1 ? true : false; - result = (bool)MediaSessionMgr::defaultsGetVideoJbEnabled(arg1); + result = (bool)MediaSessionMgr::defaultsSetVideoZeroArtifactsEnabled(arg1); + jresult = (jboolean)result; + return jresult; +} + + +SWIGEXPORT jboolean JNICALL Java_org_doubango_tinyWRAP_tinyWRAPJNI_MediaSessionMgr_1defaultsGetVideoZeroArtifactsEnabled(JNIEnv *jenv, jclass jcls) { + jboolean jresult = 0 ; + bool result; + + (void)jenv; + (void)jcls; + result = (bool)MediaSessionMgr::defaultsGetVideoZeroArtifactsEnabled(); jresult = (jboolean)result; return jresult; } diff --git a/branches/2.0/doubango/bindings/java/tinyWRAPJNI.java b/branches/2.0/doubango/bindings/java/tinyWRAPJNI.java index 0aaba596..8c5f767b 100644 --- a/branches/2.0/doubango/bindings/java/tinyWRAPJNI.java +++ b/branches/2.0/doubango/bindings/java/tinyWRAPJNI.java @@ -101,7 +101,9 @@ public class tinyWRAPJNI { public final static native boolean MediaSessionMgr_defaultsSetByPassDecoding(boolean jarg1); public final static native boolean MediaSessionMgr_defaultsGetByPassDecoding(); public final static native boolean MediaSessionMgr_defaultsSetVideoJbEnabled(boolean jarg1); - public final static native boolean MediaSessionMgr_defaultsGetVideoJbEnabled(boolean jarg1); + public final static native boolean MediaSessionMgr_defaultsGetVideoJbEnabled(); + public final static native boolean MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled(boolean jarg1); + public final static native boolean MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled(); public final static native boolean MediaSessionMgr_defaultsSetRtpBuffSize(long jarg1); public final static native long MediaSessionMgr_defaultsGetRtpBuffSize(); public final static native boolean MediaSessionMgr_defaultsSetAvpfTail(long jarg1, long jarg2); diff --git a/branches/2.0/doubango/bindings/java/tinyWRAP_wrap.cxx b/branches/2.0/doubango/bindings/java/tinyWRAP_wrap.cxx index d9b7ec45..af09d992 100644 --- a/branches/2.0/doubango/bindings/java/tinyWRAP_wrap.cxx +++ b/branches/2.0/doubango/bindings/java/tinyWRAP_wrap.cxx @@ -3412,7 +3412,19 @@ SWIGEXPORT jboolean JNICALL Java_org_doubango_tinyWRAP_tinyWRAPJNI_MediaSessionM } -SWIGEXPORT jboolean JNICALL Java_org_doubango_tinyWRAP_tinyWRAPJNI_MediaSessionMgr_1defaultsGetVideoJbEnabled(JNIEnv *jenv, jclass jcls, jboolean jarg1) { +SWIGEXPORT jboolean JNICALL Java_org_doubango_tinyWRAP_tinyWRAPJNI_MediaSessionMgr_1defaultsGetVideoJbEnabled(JNIEnv *jenv, jclass jcls) { + jboolean jresult = 0 ; + bool result; + + (void)jenv; + (void)jcls; + result = (bool)MediaSessionMgr::defaultsGetVideoJbEnabled(); + jresult = (jboolean)result; + return jresult; +} + + +SWIGEXPORT jboolean JNICALL Java_org_doubango_tinyWRAP_tinyWRAPJNI_MediaSessionMgr_1defaultsSetVideoZeroArtifactsEnabled(JNIEnv *jenv, jclass jcls, jboolean jarg1) { jboolean jresult = 0 ; bool arg1 ; bool result; @@ -3420,7 +3432,19 @@ SWIGEXPORT jboolean JNICALL Java_org_doubango_tinyWRAP_tinyWRAPJNI_MediaSessionM (void)jenv; (void)jcls; arg1 = jarg1 ? true : false; - result = (bool)MediaSessionMgr::defaultsGetVideoJbEnabled(arg1); + result = (bool)MediaSessionMgr::defaultsSetVideoZeroArtifactsEnabled(arg1); + jresult = (jboolean)result; + return jresult; +} + + +SWIGEXPORT jboolean JNICALL Java_org_doubango_tinyWRAP_tinyWRAPJNI_MediaSessionMgr_1defaultsGetVideoZeroArtifactsEnabled(JNIEnv *jenv, jclass jcls) { + jboolean jresult = 0 ; + bool result; + + (void)jenv; + (void)jcls; + result = (bool)MediaSessionMgr::defaultsGetVideoZeroArtifactsEnabled(); jresult = (jboolean)result; return jresult; } diff --git a/branches/2.0/doubango/bindings/perl/tinyWRAP.pm b/branches/2.0/doubango/bindings/perl/tinyWRAP.pm index b7284745..b609b3bd 100644 --- a/branches/2.0/doubango/bindings/perl/tinyWRAP.pm +++ b/branches/2.0/doubango/bindings/perl/tinyWRAP.pm @@ -289,6 +289,8 @@ sub DESTROY { *defaultsGetByPassDecoding = *tinyWRAPc::MediaSessionMgr_defaultsGetByPassDecoding; *defaultsSetVideoJbEnabled = *tinyWRAPc::MediaSessionMgr_defaultsSetVideoJbEnabled; *defaultsGetVideoJbEnabled = *tinyWRAPc::MediaSessionMgr_defaultsGetVideoJbEnabled; +*defaultsSetVideoZeroArtifactsEnabled = *tinyWRAPc::MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled; +*defaultsGetVideoZeroArtifactsEnabled = *tinyWRAPc::MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled; *defaultsSetRtpBuffSize = *tinyWRAPc::MediaSessionMgr_defaultsSetRtpBuffSize; *defaultsGetRtpBuffSize = *tinyWRAPc::MediaSessionMgr_defaultsGetRtpBuffSize; *defaultsSetAvpfTail = *tinyWRAPc::MediaSessionMgr_defaultsSetAvpfTail; diff --git a/branches/2.0/doubango/bindings/perl/tinyWRAP_wrap.cxx b/branches/2.0/doubango/bindings/perl/tinyWRAP_wrap.cxx index 6732aa91..67fabef2 100644 --- a/branches/2.0/doubango/bindings/perl/tinyWRAP_wrap.cxx +++ b/branches/2.0/doubango/bindings/perl/tinyWRAP_wrap.cxx @@ -5044,6 +5044,24 @@ XS(_wrap_MediaSessionMgr_defaultsSetVideoJbEnabled) { XS(_wrap_MediaSessionMgr_defaultsGetVideoJbEnabled) { + { + int argvi = 0; + bool result; + dXSARGS; + + if ((items < 0) || (items > 0)) { + SWIG_croak("Usage: MediaSessionMgr_defaultsGetVideoJbEnabled();"); + } + result = (bool)MediaSessionMgr::defaultsGetVideoJbEnabled(); + ST(argvi) = SWIG_From_bool SWIG_PERL_CALL_ARGS_1(static_cast< bool >(result)); argvi++ ; + XSRETURN(argvi); + fail: + SWIG_croak_null(); + } +} + + +XS(_wrap_MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled) { { bool arg1 ; bool val1 ; @@ -5053,14 +5071,14 @@ XS(_wrap_MediaSessionMgr_defaultsGetVideoJbEnabled) { dXSARGS; if ((items < 1) || (items > 1)) { - SWIG_croak("Usage: MediaSessionMgr_defaultsGetVideoJbEnabled(enabled);"); + SWIG_croak("Usage: MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled(enabled);"); } ecode1 = SWIG_AsVal_bool SWIG_PERL_CALL_ARGS_2(ST(0), &val1); if (!SWIG_IsOK(ecode1)) { - SWIG_exception_fail(SWIG_ArgError(ecode1), "in method '" "MediaSessionMgr_defaultsGetVideoJbEnabled" "', argument " "1"" of type '" "bool""'"); + SWIG_exception_fail(SWIG_ArgError(ecode1), "in method '" "MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled" "', argument " "1"" of type '" "bool""'"); } arg1 = static_cast< bool >(val1); - result = (bool)MediaSessionMgr::defaultsGetVideoJbEnabled(arg1); + result = (bool)MediaSessionMgr::defaultsSetVideoZeroArtifactsEnabled(arg1); ST(argvi) = SWIG_From_bool SWIG_PERL_CALL_ARGS_1(static_cast< bool >(result)); argvi++ ; XSRETURN(argvi); @@ -5071,6 +5089,24 @@ XS(_wrap_MediaSessionMgr_defaultsGetVideoJbEnabled) { } +XS(_wrap_MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled) { + { + int argvi = 0; + bool result; + dXSARGS; + + if ((items < 0) || (items > 0)) { + SWIG_croak("Usage: MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled();"); + } + result = (bool)MediaSessionMgr::defaultsGetVideoZeroArtifactsEnabled(); + ST(argvi) = SWIG_From_bool SWIG_PERL_CALL_ARGS_1(static_cast< bool >(result)); argvi++ ; + XSRETURN(argvi); + fail: + SWIG_croak_null(); + } +} + + XS(_wrap_MediaSessionMgr_defaultsSetRtpBuffSize) { { unsigned int arg1 ; @@ -26745,6 +26781,8 @@ static swig_command_info swig_commands[] = { {"tinyWRAPc::MediaSessionMgr_defaultsGetByPassDecoding", _wrap_MediaSessionMgr_defaultsGetByPassDecoding}, {"tinyWRAPc::MediaSessionMgr_defaultsSetVideoJbEnabled", _wrap_MediaSessionMgr_defaultsSetVideoJbEnabled}, {"tinyWRAPc::MediaSessionMgr_defaultsGetVideoJbEnabled", _wrap_MediaSessionMgr_defaultsGetVideoJbEnabled}, +{"tinyWRAPc::MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled", _wrap_MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled}, +{"tinyWRAPc::MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled", _wrap_MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled}, {"tinyWRAPc::MediaSessionMgr_defaultsSetRtpBuffSize", _wrap_MediaSessionMgr_defaultsSetRtpBuffSize}, {"tinyWRAPc::MediaSessionMgr_defaultsGetRtpBuffSize", _wrap_MediaSessionMgr_defaultsGetRtpBuffSize}, {"tinyWRAPc::MediaSessionMgr_defaultsSetAvpfTail", _wrap_MediaSessionMgr_defaultsSetAvpfTail}, diff --git a/branches/2.0/doubango/bindings/python/tinyWRAP.py b/branches/2.0/doubango/bindings/python/tinyWRAP.py index 563b321c..34f96ea7 100644 --- a/branches/2.0/doubango/bindings/python/tinyWRAP.py +++ b/branches/2.0/doubango/bindings/python/tinyWRAP.py @@ -285,6 +285,10 @@ class MediaSessionMgr(_object): if _newclass:defaultsSetVideoJbEnabled = staticmethod(_tinyWRAP.MediaSessionMgr_defaultsSetVideoJbEnabled) __swig_getmethods__["defaultsGetVideoJbEnabled"] = lambda x: _tinyWRAP.MediaSessionMgr_defaultsGetVideoJbEnabled if _newclass:defaultsGetVideoJbEnabled = staticmethod(_tinyWRAP.MediaSessionMgr_defaultsGetVideoJbEnabled) + __swig_getmethods__["defaultsSetVideoZeroArtifactsEnabled"] = lambda x: _tinyWRAP.MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled + if _newclass:defaultsSetVideoZeroArtifactsEnabled = staticmethod(_tinyWRAP.MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled) + __swig_getmethods__["defaultsGetVideoZeroArtifactsEnabled"] = lambda x: _tinyWRAP.MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled + if _newclass:defaultsGetVideoZeroArtifactsEnabled = staticmethod(_tinyWRAP.MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled) __swig_getmethods__["defaultsSetRtpBuffSize"] = lambda x: _tinyWRAP.MediaSessionMgr_defaultsSetRtpBuffSize if _newclass:defaultsSetRtpBuffSize = staticmethod(_tinyWRAP.MediaSessionMgr_defaultsSetRtpBuffSize) __swig_getmethods__["defaultsGetRtpBuffSize"] = lambda x: _tinyWRAP.MediaSessionMgr_defaultsGetRtpBuffSize @@ -482,10 +486,18 @@ def MediaSessionMgr_defaultsSetVideoJbEnabled(*args): return _tinyWRAP.MediaSessionMgr_defaultsSetVideoJbEnabled(*args) MediaSessionMgr_defaultsSetVideoJbEnabled = _tinyWRAP.MediaSessionMgr_defaultsSetVideoJbEnabled -def MediaSessionMgr_defaultsGetVideoJbEnabled(*args): - return _tinyWRAP.MediaSessionMgr_defaultsGetVideoJbEnabled(*args) +def MediaSessionMgr_defaultsGetVideoJbEnabled(): + return _tinyWRAP.MediaSessionMgr_defaultsGetVideoJbEnabled() MediaSessionMgr_defaultsGetVideoJbEnabled = _tinyWRAP.MediaSessionMgr_defaultsGetVideoJbEnabled +def MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled(*args): + return _tinyWRAP.MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled(*args) +MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled = _tinyWRAP.MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled + +def MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled(): + return _tinyWRAP.MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled() +MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled = _tinyWRAP.MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled + def MediaSessionMgr_defaultsSetRtpBuffSize(*args): return _tinyWRAP.MediaSessionMgr_defaultsSetRtpBuffSize(*args) MediaSessionMgr_defaultsSetRtpBuffSize = _tinyWRAP.MediaSessionMgr_defaultsSetRtpBuffSize diff --git a/branches/2.0/doubango/bindings/python/tinyWRAP_wrap.cxx b/branches/2.0/doubango/bindings/python/tinyWRAP_wrap.cxx index e7715f21..f65ad43d 100644 --- a/branches/2.0/doubango/bindings/python/tinyWRAP_wrap.cxx +++ b/branches/2.0/doubango/bindings/python/tinyWRAP_wrap.cxx @@ -7822,6 +7822,19 @@ fail: SWIGINTERN PyObject *_wrap_MediaSessionMgr_defaultsGetVideoJbEnabled(PyObject *SWIGUNUSEDPARM(self), PyObject *args) { + PyObject *resultobj = 0; + bool result; + + if (!PyArg_ParseTuple(args,(char *)":MediaSessionMgr_defaultsGetVideoJbEnabled")) SWIG_fail; + result = (bool)MediaSessionMgr::defaultsGetVideoJbEnabled(); + resultobj = SWIG_From_bool(static_cast< bool >(result)); + return resultobj; +fail: + return NULL; +} + + +SWIGINTERN PyObject *_wrap_MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled(PyObject *SWIGUNUSEDPARM(self), PyObject *args) { PyObject *resultobj = 0; bool arg1 ; bool val1 ; @@ -7829,13 +7842,26 @@ SWIGINTERN PyObject *_wrap_MediaSessionMgr_defaultsGetVideoJbEnabled(PyObject *S PyObject * obj0 = 0 ; bool result; - if (!PyArg_ParseTuple(args,(char *)"O:MediaSessionMgr_defaultsGetVideoJbEnabled",&obj0)) SWIG_fail; + if (!PyArg_ParseTuple(args,(char *)"O:MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled",&obj0)) SWIG_fail; ecode1 = SWIG_AsVal_bool(obj0, &val1); if (!SWIG_IsOK(ecode1)) { - SWIG_exception_fail(SWIG_ArgError(ecode1), "in method '" "MediaSessionMgr_defaultsGetVideoJbEnabled" "', argument " "1"" of type '" "bool""'"); + SWIG_exception_fail(SWIG_ArgError(ecode1), "in method '" "MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled" "', argument " "1"" of type '" "bool""'"); } arg1 = static_cast< bool >(val1); - result = (bool)MediaSessionMgr::defaultsGetVideoJbEnabled(arg1); + result = (bool)MediaSessionMgr::defaultsSetVideoZeroArtifactsEnabled(arg1); + resultobj = SWIG_From_bool(static_cast< bool >(result)); + return resultobj; +fail: + return NULL; +} + + +SWIGINTERN PyObject *_wrap_MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled(PyObject *SWIGUNUSEDPARM(self), PyObject *args) { + PyObject *resultobj = 0; + bool result; + + if (!PyArg_ParseTuple(args,(char *)":MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled")) SWIG_fail; + result = (bool)MediaSessionMgr::defaultsGetVideoZeroArtifactsEnabled(); resultobj = SWIG_From_bool(static_cast< bool >(result)); return resultobj; fail: @@ -25331,6 +25357,8 @@ static PyMethodDef SwigMethods[] = { { (char *)"MediaSessionMgr_defaultsGetByPassDecoding", _wrap_MediaSessionMgr_defaultsGetByPassDecoding, METH_VARARGS, NULL}, { (char *)"MediaSessionMgr_defaultsSetVideoJbEnabled", _wrap_MediaSessionMgr_defaultsSetVideoJbEnabled, METH_VARARGS, NULL}, { (char *)"MediaSessionMgr_defaultsGetVideoJbEnabled", _wrap_MediaSessionMgr_defaultsGetVideoJbEnabled, METH_VARARGS, NULL}, + { (char *)"MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled", _wrap_MediaSessionMgr_defaultsSetVideoZeroArtifactsEnabled, METH_VARARGS, NULL}, + { (char *)"MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled", _wrap_MediaSessionMgr_defaultsGetVideoZeroArtifactsEnabled, METH_VARARGS, NULL}, { (char *)"MediaSessionMgr_defaultsSetRtpBuffSize", _wrap_MediaSessionMgr_defaultsSetRtpBuffSize, METH_VARARGS, NULL}, { (char *)"MediaSessionMgr_defaultsGetRtpBuffSize", _wrap_MediaSessionMgr_defaultsGetRtpBuffSize, METH_VARARGS, NULL}, { (char *)"MediaSessionMgr_defaultsSetAvpfTail", _wrap_MediaSessionMgr_defaultsSetAvpfTail, METH_VARARGS, NULL}, diff --git a/branches/2.0/doubango/configure.ac b/branches/2.0/doubango/configure.ac index f30b37df..a9ba5ae4 100755 --- a/branches/2.0/doubango/configure.ac +++ b/branches/2.0/doubango/configure.ac @@ -123,6 +123,14 @@ AC_CHECK_LIB([resolv], [dns_search], AC_SUBST(LIBRESOLV_LIBADD, "") ) +AM_CONDITIONAL(USE_RT, false) +AC_CHECK_LIB([rt], [clock_gettime], + AC_SUBST(LIBRT_LIBADD, "-lrt") + AC_DEFINE(HAVE_CLOCK_GETTIME, 1, [Define to 1 if we have the `clock_gettime' function.]) + AM_CONDITIONAL(USE_RT, true), + AC_SUBST(LIBRT_LIBADD, "") + ) + AH_TEMPLATE([TNET_HAVE_SS_LEN], [Define if sockaddr_storage.ss_len exists]) AC_CHECK_MEMBER([struct sockaddr_storage.ss_len], AC_DEFINE(TNET_HAVE_SS_LEN, 1), AC_DEFINE(TNET_HAVE_SS_LEN,0), [#include ]) diff --git a/branches/2.0/doubango/plugins/audio_opensles/audio_opensles.cxx b/branches/2.0/doubango/plugins/audio_opensles/audio_opensles.cxx index 20d3cf8d..46d7eb98 100644 --- a/branches/2.0/doubango/plugins/audio_opensles/audio_opensles.cxx +++ b/branches/2.0/doubango/plugins/audio_opensles/audio_opensles.cxx @@ -498,6 +498,7 @@ int audio_opensles_instance_stop_consumer(audio_opensles_instance_handle_t* _sel } else{ self->isConsumerStarted = self->device->Playing(); + self->isConsumerPrepared = false; } done: @@ -525,6 +526,7 @@ int audio_opensles_instance_stop_producer(audio_opensles_instance_handle_t* _sel } else{ self->isProducerStarted = self->device->Recording(); + self->isProducerPrepared = false; } done: diff --git a/branches/2.0/doubango/plugins/audio_opensles/audio_opensles_device.cxx b/branches/2.0/doubango/plugins/audio_opensles/audio_opensles_device.cxx index 6afbc615..f18823fe 100644 --- a/branches/2.0/doubango/plugins/audio_opensles/audio_opensles_device.cxx +++ b/branches/2.0/doubango/plugins/audio_opensles/audio_opensles_device.cxx @@ -15,12 +15,12 @@ #define CHECK_TRUE(_bool, _text) { if(!_bool){ AUDIO_OPENSLES_DEBUG_ERROR(_text); return -1; } } #define CHECK_FALSE(_bool, _text) { if(_bool){ AUDIO_OPENSLES_DEBUG_ERROR(_text); return -1; } } -#define CHECK_PLAYOUT_INITIALIZED() CHECK_TRUE(m_bPlayoutInitialized, "Playout already initialized") -#define CHECK_PLAYOUT_NOT_INITIALIZED() CHECK_FALSE(m_bPlayoutInitialized, "Playout not initialized") -#define CHECK_RECORDING_INITIALIZED() CHECK_TRUE(m_bRecordingInitialized, "Recording already initialized") -#define CHECK_RECORDING_NOT_INITIALIZED() CHECK_FALSE(m_bRecordingInitialized, "Recording not initialized") -#define CHECK_MICROPHONE_INITIALIZED() CHECK_TRUE(m_bMicrophoneInitialized, "Microphone already initialized") -#define CHECK_MICROPHONE_NOT_INITIALIZED() CHECK_FALSE(m_bMicrophoneInitialized, "Microphone not initialized") +#define CHECK_PLAYOUT_INITIALIZED() CHECK_TRUE(m_bPlayoutInitialized, "Playout not initialized") +#define CHECK_PLAYOUT_NOT_INITIALIZED() CHECK_FALSE(m_bPlayoutInitialized, "Playout initialized") +#define CHECK_RECORDING_INITIALIZED() CHECK_TRUE(m_bRecordingInitialized, "Recording not initialized") +#define CHECK_RECORDING_NOT_INITIALIZED() CHECK_FALSE(m_bRecordingInitialized, "Recording initialized") +#define CHECK_MICROPHONE_INITIALIZED() CHECK_TRUE(m_bMicrophoneInitialized, "Microphone not initialized") +#define CHECK_MICROPHONE_NOT_INITIALIZED() CHECK_FALSE(m_bMicrophoneInitialized, "Microphone initialized") #if AUDIO_OPENSLES_UNDER_ANDROID static inline SLuint32 SL_SAMPLING_RATE(int RATE_INT){ @@ -427,6 +427,8 @@ int SLAudioDevice::PlayoutSampleRate(int *pPlayoutSampleRate) int SLAudioDevice::StartPlayout() { + AUDIO_OPENSLES_DEBUG_INFO("SLAudioDevice::StartPlayout()"); + CHECK_TRUE(m_bInitialized, "Not initialized"); CHECK_PLAYOUT_INITIALIZED(); @@ -493,6 +495,8 @@ bool SLAudioDevice::Playing() int SLAudioDevice::StopPlayout() { + AUDIO_OPENSLES_DEBUG_INFO("SLAudioDevice::StopPlayout()"); + if(!m_bPlaying){ return 0; } @@ -557,9 +561,8 @@ int SLAudioDevice::MicrophoneIsAvailable(bool *pAvailable) int SLAudioDevice::InitMicrophone() { - CHECK_TRUE(m_bInitialized, "Device not initialized"); - AUDIO_OPENSLES_DEBUG_INFO("SLAudioDevice::InitMicrophone()"); + CHECK_TRUE(m_bInitialized, "Device not initialized"); if(m_bMicrophoneInitialized){ return 0; @@ -776,6 +779,8 @@ int SLAudioDevice::RecordingSampleRate(int *pRecordingSampleRate) int SLAudioDevice::StartRecording() { + AUDIO_OPENSLES_DEBUG_INFO("SLAudioDevice::StartRecording()"); + CHECK_TRUE(m_bInitialized, "Not initialized"); CHECK_RECORDING_INITIALIZED(); @@ -840,6 +845,7 @@ bool SLAudioDevice::Recording() int SLAudioDevice::StopRecording() { + AUDIO_OPENSLES_DEBUG_INFO("SLAudioDevice::StopRecording()"); if (!m_bRecording) { return 0; } @@ -866,6 +872,7 @@ int SLAudioDevice::StopRecording() AUDIO_OPENSLES_DEBUG_INFO("Recording stopped"); m_bRecording = false; + m_bRecordingInitialized = false; return 0; } diff --git a/branches/2.0/doubango/thirdparties/android/include/srtp/config.h b/branches/2.0/doubango/thirdparties/android/include/srtp/config.h index bf70e155..e6355b7e 100644 --- a/branches/2.0/doubango/thirdparties/android/include/srtp/config.h +++ b/branches/2.0/doubango/thirdparties/android/include/srtp/config.h @@ -47,7 +47,9 @@ /* #undef HAVE_LIBSOCKET */ /* Define to 1 if you have the header file. */ -/* #undef HAVE_MACHINE_TYPES_H */ +#if !defined(ANDROID) +#define HAVE_MACHINE_TYPES_H 1 +#endif /* Define to 1 if you have the header file. */ #define HAVE_MEMORY_H 1 @@ -110,10 +112,10 @@ /* #undef HAVE_USLEEP */ /* Define to 1 if you have the header file. */ -/* #undef HAVE_WINDOWS_H */ +#define HAVE_WINDOWS_H 1 /* Define to 1 if you have the header file. */ -/* #undef HAVE_WINSOCK2_H */ +#define HAVE_WINSOCK2_H 1 /* Define to use X86 inlined assembly code */ /* #undef HAVE_X86 */ @@ -149,7 +151,7 @@ /* #undef SRTP_KERNEL_LINUX */ /* Define to 1 if you have the ANSI C header files. */ -/* #undef STDC_HEADERS */ +#define STDC_HEADERS 1 /* Write errors to this file */ /* #undef USE_ERR_REPORTING_FILE */ diff --git a/branches/2.0/doubango/thirdparties/android/lib/libsrtp.a b/branches/2.0/doubango/thirdparties/android/lib/libsrtp.a index 0db53f5e19882f9f6d4a52465f413042d3178301..dfea1ffc4d2b72dd3c51c9f371c4d57b5a112032 100644 GIT binary patch delta 479 zcmcb#iu2Mc&IwW+W~L^lCI;rF8NF#>_ z<0MsNA@S`ya~Wj?VM4~+@6|Cz@F2K$-HZu3a4y*LJ=+}oA~yZ z{}^rAU_vI_^;ntu*x_7IaBRQL!z9QF7n%;VRD3(P7}HC*%_d-F!pcl@IpE4BuiGZE z{em{r9W=vE8#8T$t1<<;QGEMs8>SjYe&y(g`wXKWG8rU4V6F!~|0LDK^r1Ma6% z)9dY+q^G;tF{$W+#6j}gfa+lKtPBjVwkbzH1j#WOB!l&+7)U?W*goBki4zuSKwCg= KIpE7Qi5&otI-d{# diff --git a/branches/2.0/doubango/thirdparties/win32/include/srtp/config.h b/branches/2.0/doubango/thirdparties/win32/include/srtp/config.h index 750fe262..6f8e38ab 100644 --- a/branches/2.0/doubango/thirdparties/win32/include/srtp/config.h +++ b/branches/2.0/doubango/thirdparties/win32/include/srtp/config.h @@ -11,7 +11,7 @@ /* #undef DEV_URANDOM */ /* Define to compile in dynamic debugging system. */ -/* #undef ENABLE_DEBUGGING */ +#define ENABLE_DEBUGGING 1 /* Report errors to this file. */ /* #undef ERR_REPORTING_FILE */ diff --git a/branches/2.0/doubango/thirdparties/win32/lib/srtp/libsrtp.a b/branches/2.0/doubango/thirdparties/win32/lib/srtp/libsrtp.a index ec21a291de6fec144f84f16bc517c25b9d52c014..10bb589918c775a827807b0775a1acc1db773979 100644 GIT binary patch delta 53623 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%0ALX3XyhnVawhwL8 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 #endif #if TARGET_OS_MAC # define TDAV_UNDER_MAC 1 @@ -58,7 +59,7 @@ #endif // Mobile -#if defined(_WIN32_WCE) || defined(ANDROID) // iOS (not true)=> || defined(IOS) +#if defined(_WIN32_WCE) || defined(ANDROID) || TDAV_UNDER_IPHONE || TDAV_UNDER_IPHONE_SIMULATOR # define TDAV_UNDER_MOBILE 1 #endif diff --git a/branches/2.0/doubango/tinyDAV/src/audio/coreaudio/tdav_producer_audiounit.c b/branches/2.0/doubango/tinyDAV/src/audio/coreaudio/tdav_producer_audiounit.c index ad7131e9..29bd7433 100644 --- a/branches/2.0/doubango/tinyDAV/src/audio/coreaudio/tdav_producer_audiounit.c +++ b/branches/2.0/doubango/tinyDAV/src/audio/coreaudio/tdav_producer_audiounit.c @@ -33,15 +33,7 @@ #include "tsk_thread.h" #include "tsk_debug.h" -#define kRingPacketCount +10 -// If the "ptime" value is less than "kMaxPtimeBeforeUsingCondVars", then we can use nonosleep() function instead of conditional -// variables for better performance. -// When the prodcuer's stop() function is called we will wait until the sender thread exist (using join()) this is -// why "kMaxPtimeBeforeUsingCondVars" should be small. This problem will not happen when using conditional variables: thanks to braodcast(). -#define kMaxPtimeBeforeUsingCondVars +500 /* milliseconds */ - -static void *__sender_thread(void *param); -static int __sender_thread_set_realtime(uint32_t ptime); +#define kRingPacketCount 10 static OSStatus __handle_input_buffer(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, @@ -71,119 +63,19 @@ static OSStatus __handle_input_buffer(void *inRefCon, inNumberFrames, &buffers); if(status == 0){ - tsk_mutex_lock(producer->ring.mutex); + // must not be done on async thread: doing it gives bad audio quality when audio+video call is done with CPU consuming codec (e.g. speex or g729) speex_buffer_write(producer->ring.buffer, buffers.mBuffers[0].mData, buffers.mBuffers[0].mDataByteSize); - tsk_mutex_unlock(producer->ring.mutex); + int avail = speex_buffer_get_available(producer->ring.buffer); + while (producer->started && avail >= producer->ring.chunck.size) { + avail -= speex_buffer_read(producer->ring.buffer, producer->ring.chunck.buffer, producer->ring.chunck.size); + TMEDIA_PRODUCER(producer)->enc_cb.callback(TMEDIA_PRODUCER(producer)->enc_cb.callback_data, + producer->ring.chunck.buffer, producer->ring.chunck.size); + } } return status; } -static int __sender_thread_set_realtime(uint32_t ptime) { - struct thread_time_constraint_policy policy; - int params [2] = {CTL_HW, HW_BUS_FREQ}; - int ret; - - // get bus frequence - int freq_ns, freq_ms; - size_t size = sizeof (freq_ns); - if((ret = sysctl (params, 2, &freq_ns, &size, NULL, 0))){ - // check errno for more information - TSK_DEBUG_INFO("sysctl() failed with error code=%d", ret); - return ret; - } - freq_ms = freq_ns/1000; - - /* - * THREAD_TIME_CONSTRAINT_POLICY: - * - * This scheduling mode is for threads which have real time - * constraints on their execution. - * - * Parameters: - * - * period: This is the nominal amount of time between separate - * processing arrivals, specified in absolute time units. A - * value of 0 indicates that there is no inherent periodicity in - * the computation. - * - * computation: This is the nominal amount of computation - * time needed during a separate processing arrival, specified - * in absolute time units. - * - * constraint: This is the maximum amount of real time that - * may elapse from the start of a separate processing arrival - * to the end of computation for logically correct functioning, - * specified in absolute time units. Must be (>= computation). - * Note that latency = (constraint - computation). - * - * preemptible: This indicates that the computation may be - * interrupted, subject to the constraint specified above. - */ - policy.period = (ptime/2) * freq_ms; // Half of the ptime - policy.computation = 2 * freq_ms; - policy.constraint = 3 * freq_ms; - policy.preemptible = true; - - if ((ret = thread_policy_set(mach_thread_self(), - THREAD_TIME_CONSTRAINT_POLICY, (int *)&policy, - THREAD_TIME_CONSTRAINT_POLICY_COUNT)) != KERN_SUCCESS) { - TSK_DEBUG_ERROR("thread_policy_set failed(period=%u,computation=%u,constraint=%u) failed with error code= %d", - policy.period, policy.computation, policy.constraint, - ret); - return ret; - } - return 0; -} - -static void *__sender_thread(void *param) -{ - TSK_DEBUG_INFO("__sender_thread::ENTER"); - - tdav_producer_audiounit_t* producer = (tdav_producer_audiounit_t*)param; - uint32_t ptime = TMEDIA_PRODUCER(producer)->audio.ptime; - tsk_ssize_t avail; - - // interval to sleep when using nonosleep() instead of conditional variable - struct timespec interval; - interval.tv_sec = (long)(ptime/1000); - interval.tv_nsec = (long)(ptime%1000) * 1000000; - - // change thread priority -//#if TARGET_OS_IPHONE - __sender_thread_set_realtime(TMEDIA_PRODUCER(producer)->audio.ptime); -//#endif - - // starts looping - for (;;) { - // wait for "ptime" milliseconds - if(ptime <= kMaxPtimeBeforeUsingCondVars){ - nanosleep(&interval, 0); - } - else { - tsk_condwait_timedwait(producer->senderCondWait, (uint64_t)ptime); - } - // check state - if(!producer->started){ - break; - } - // read data and send them - if(TMEDIA_PRODUCER(producer)->enc_cb.callback) { - tsk_mutex_lock(producer->ring.mutex); - avail = speex_buffer_get_available(producer->ring.buffer); - while (producer->started && avail >= producer->ring.chunck.size) { - avail -= speex_buffer_read(producer->ring.buffer, producer->ring.chunck.buffer, producer->ring.chunck.size); - TMEDIA_PRODUCER(producer)->enc_cb.callback(TMEDIA_PRODUCER(producer)->enc_cb.callback_data, - producer->ring.chunck.buffer, producer->ring.chunck.size); - } - tsk_mutex_unlock(producer->ring.mutex); - } - else; - } - TSK_DEBUG_INFO("__sender_thread::EXIT"); - return tsk_null; -} - /* ============ Media Producer Interface ================= */ int tdav_producer_audiounit_set(tmedia_producer_t* self, const tmedia_param_t* param) { @@ -352,11 +244,6 @@ static int tdav_producer_audiounit_prepare(tmedia_producer_t* self, const tmedia TSK_DEBUG_ERROR("Failed to allocate new buffer"); return -7; } - // create mutex for ring buffer - if(!producer->ring.mutex && !(producer->ring.mutex = tsk_mutex_create_2(tsk_false))){ - TSK_DEBUG_ERROR("Failed to create new mutex"); - return -8; - } // create ringbuffer producer->ring.size = kRingPacketCount * producer->ring.chunck.size; if(!producer->ring.buffer){ @@ -411,18 +298,6 @@ static int tdav_producer_audiounit_start(tmedia_producer_t* self) // apply parameters (because could be lost when the producer is restarted -handle recreated-) ret = tdav_audiounit_handle_mute(producer->audioUnitHandle, producer->muted); - - // create conditional variable - if(!(producer->senderCondWait = tsk_condwait_create())){ - TSK_DEBUG_ERROR("Failed to create conditional variable"); - return -2; - } - // start the reader thread - ret = tsk_thread_create(&producer->senderThreadId[0], __sender_thread, producer); - if(ret){ - TSK_DEBUG_ERROR("Failed to start the sender thread. error code=%d", ret); - return ret; - } TSK_DEBUG_INFO("AudioUnit producer started"); return 0; @@ -466,14 +341,6 @@ static int tdav_producer_audiounit_stop(tmedia_producer_t* self) #endif } producer->started = tsk_false; - // signal - if(producer->senderCondWait){ - tsk_condwait_broadcast(producer->senderCondWait); - } - // stop thread - if(producer->senderThreadId[0]){ - tsk_thread_join(&(producer->senderThreadId[0])); - } TSK_DEBUG_INFO("AudioUnit producer stoppped"); return 0; } @@ -507,16 +374,10 @@ static tsk_object_t* tdav_producer_audiounit_dtor(tsk_object_t * self) if (producer->audioUnitHandle) { tdav_audiounit_handle_destroy(&producer->audioUnitHandle); } - if(producer->ring.mutex){ - tsk_mutex_destroy(&producer->ring.mutex); - } TSK_FREE(producer->ring.chunck.buffer); if(producer->ring.buffer){ speex_buffer_destroy(producer->ring.buffer); } - if(producer->senderCondWait){ - tsk_condwait_destroy(&producer->senderCondWait); - } /* deinit base */ tdav_producer_audio_deinit(TDAV_PRODUCER_AUDIO(producer)); } diff --git a/branches/2.0/doubango/tinyDAV/src/codecs/h263/tdav_codec_h263.c b/branches/2.0/doubango/tinyDAV/src/codecs/h263/tdav_codec_h263.c index f9e67254..4172a04e 100644 --- a/branches/2.0/doubango/tinyDAV/src/codecs/h263/tdav_codec_h263.c +++ b/branches/2.0/doubango/tinyDAV/src/codecs/h263/tdav_codec_h263.c @@ -330,6 +330,7 @@ static tsk_size_t tdav_codec_h263_decode(tmedia_codec_t* self, const void* in_da tdav_codec_h263_t* h263 = (tdav_codec_h263_t*)self; const trtp_rtp_header_t* rtp_hdr = proto_hdr; + tsk_bool_t is_idr = tsk_false; if(!self || !in_data || !in_size || !out_data || !h263->decoder.context){ TSK_DEBUG_ERROR("Invalid parameter"); @@ -345,6 +346,10 @@ static tsk_size_t tdav_codec_h263_decode(tmedia_codec_t* self, const void* in_da Optional PB-frames mode as defined by the H.263 [4]. "0" implies normal I or P frame, "1" PB-frames. When F=1, P also indicates modes: mode B if P=0, mode C if P=1. + + I: 1 bit. + Picture coding type, bit 9 in PTYPE defined by H.263[4], "0" is + intra-coded, "1" is inter-coded. */ F = *pdata >> 7; P = (*pdata >> 6) & 0x01; @@ -362,6 +367,7 @@ static tsk_size_t tdav_codec_h263_decode(tmedia_codec_t* self, const void* in_da +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ hdr_size = H263_HEADER_MODE_A_SIZE; + is_idr = (in_size >= 2) && !(pdata[1] & 0x10) /* I==1 */; } else if(P == 0){ // F=1 and P=0 /* MODE B @@ -374,6 +380,7 @@ static tsk_size_t tdav_codec_h263_decode(tmedia_codec_t* self, const void* in_da +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ hdr_size = H263_HEADER_MODE_B_SIZE; + is_idr = (in_size >= 5) && !(pdata[4] & 0x80) /* I==1 */; } else{ // F=1 and P=1 /* MODE C @@ -388,6 +395,7 @@ static tsk_size_t tdav_codec_h263_decode(tmedia_codec_t* self, const void* in_da +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ hdr_size = H263_HEADER_MODE_C_SIZE; + is_idr = (in_size >= 5) && !(pdata[4] & 0x80) /* I==1 */; } /* Check size */ @@ -458,6 +466,13 @@ static tsk_size_t tdav_codec_h263_decode(tmedia_codec_t* self, const void* in_da } else if(got_picture_ptr){ retsize = xsize; + // Is it IDR frame? + if(is_idr && TMEDIA_CODEC_VIDEO(self)->in.callback){ + TSK_DEBUG_INFO("Decoded H.263 IDR"); + TMEDIA_CODEC_VIDEO(self)->in.result.type = tmedia_video_decode_result_type_idr; + TMEDIA_CODEC_VIDEO(self)->in.result.proto_hdr = proto_hdr; + TMEDIA_CODEC_VIDEO(self)->in.callback(&TMEDIA_CODEC_VIDEO(self)->in.result); + } TMEDIA_CODEC_VIDEO(h263)->in.width = h263->decoder.context->width; TMEDIA_CODEC_VIDEO(h263)->in.height = h263->decoder.context->height; /* copy picture into a linear buffer */ @@ -628,7 +643,7 @@ static tsk_size_t tdav_codec_h263p_decode(tmedia_codec_t* self, const void* in_d } /* - 5.1. General H.263+ Payload Header + rfc4629 - 5.1. General H.263+ Payload Header 0 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 @@ -1175,7 +1190,7 @@ static void tdav_codec_h263_rtp_callback(tdav_codec_h263_t *self, const void *da if(TMEDIA_CODEC_VIDEO(self)->out.callback){ TMEDIA_CODEC_VIDEO(self)->out.result.buffer.ptr = self->rtp.ptr; TMEDIA_CODEC_VIDEO(self)->out.result.buffer.size = (size + H263_HEADER_MODE_A_SIZE); - TMEDIA_CODEC_VIDEO(self)->out.result.duration = (3003* (30/TMEDIA_CODEC_VIDEO(self)->out.fps)); + TMEDIA_CODEC_VIDEO(self)->out.result.duration = (1./(double)TMEDIA_CODEC_VIDEO(self)->out.fps) * TMEDIA_CODEC(self)->plugin->rate; TMEDIA_CODEC_VIDEO(self)->out.result.last_chunck = marker; TMEDIA_CODEC_VIDEO(self)->out.callback(&TMEDIA_CODEC_VIDEO(self)->out.result); } @@ -1320,7 +1335,7 @@ static void tdav_codec_h263p_rtp_callback(tdav_codec_h263_t *self, const void *d if(TMEDIA_CODEC_VIDEO(self)->out.callback){ TMEDIA_CODEC_VIDEO(self)->out.result.buffer.ptr = _ptr; TMEDIA_CODEC_VIDEO(self)->out.result.buffer.size = _size; - TMEDIA_CODEC_VIDEO(self)->out.result.duration = (3003* (30/TMEDIA_CODEC_VIDEO(self)->out.fps)); + TMEDIA_CODEC_VIDEO(self)->out.result.duration = (1./(double)TMEDIA_CODEC_VIDEO(self)->out.fps) * TMEDIA_CODEC(self)->plugin->rate; TMEDIA_CODEC_VIDEO(self)->out.result.last_chunck = marker; TMEDIA_CODEC_VIDEO(self)->out.callback(&TMEDIA_CODEC_VIDEO(self)->out.result); } @@ -1328,4 +1343,4 @@ static void tdav_codec_h263p_rtp_callback(tdav_codec_h263_t *self, const void *d -#endif /* HAVE_FFMPEG */ \ No newline at end of file +#endif /* HAVE_FFMPEG */ diff --git a/branches/2.0/doubango/tinyDAV/src/codecs/h264/tdav_codec_h264.c b/branches/2.0/doubango/tinyDAV/src/codecs/h264/tdav_codec_h264.c index f2f81891..d4596563 100644 --- a/branches/2.0/doubango/tinyDAV/src/codecs/h264/tdav_codec_h264.c +++ b/branches/2.0/doubango/tinyDAV/src/codecs/h264/tdav_codec_h264.c @@ -1,1016 +1,1043 @@ -/* -* Copyright (C) 2010-2011 Mamadou Diop. -* -* Contact: Mamadou Diop -* -* This file is part of Open Source Doubango Framework. -* -* DOUBANGO 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 3 of the License, or -* (at your option) any later version. -* -* DOUBANGO 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 DOUBANGO. -* -*/ - -/**@file tdav_codec_h264.c - * @brief H.264 codec plugin using FFmpeg for decoding and x264 for encoding - * RTP payloader/depayloader follows RFC 3984 - * - * @author Mamadou Diop - * - - */ -#include "tinydav/codecs/h264/tdav_codec_h264.h" - -#if (HAVE_FFMPEG && (!defined(HAVE_H264) || HAVE_H264)) || HAVE_H264_PASSTHROUGH - -#include "tinydav/codecs/h264/tdav_codec_h264_rtp.h" -#include "tinydav/video/tdav_converter_video.h" - -#include "tinyrtp/rtp/trtp_rtp_packet.h" - -#include "tinymedia/tmedia_params.h" - -#include "tsk_params.h" -#include "tsk_memory.h" -#include "tsk_debug.h" - -#if HAVE_FFMPEG -#include -#endif - -typedef struct tdav_codec_h264_s -{ - TDAV_DECLARE_CODEC_H264_COMMON; - - // Encoder - struct{ -#if HAVE_FFMPEG - AVCodec* codec; - AVCodecContext* context; - AVFrame* picture; -#endif - void* buffer; - int64_t frame_count; - tsk_bool_t force_idr; - int32_t quality; // [1-31] - int rotation; - } encoder; - - // decoder - struct{ -#if HAVE_FFMPEG - AVCodec* codec; - AVCodecContext* context; - AVFrame* picture; -#endif - void* accumulator; - tsk_size_t accumulator_pos; - tsk_size_t accumulator_size; - uint16_t last_seq; - } decoder; -} -tdav_codec_h264_t; - -#define TDAV_H264_GOP_SIZE_IN_SECONDS 25 - -static int tdav_codec_h264_init(tdav_codec_h264_t* self, profile_idc_t profile); -static int tdav_codec_h264_deinit(tdav_codec_h264_t* self); -static int tdav_codec_h264_open_encoder(tdav_codec_h264_t* self); -static int tdav_codec_h264_close_encoder(tdav_codec_h264_t* self); -static int tdav_codec_h264_open_decoder(tdav_codec_h264_t* self); -static int tdav_codec_h264_close_decoder(tdav_codec_h264_t* self); - -static void tdav_codec_h264_encap(const tdav_codec_h264_t* h264, const uint8_t* pdata, tsk_size_t size); - -/* ============ H.264 Base/Main Profile X.X Plugin interface functions ================= */ - -static int tdav_codec_h264_set(tmedia_codec_t* self, const tmedia_param_t* param) -{ - tdav_codec_h264_t* h264 = (tdav_codec_h264_t*)self; - if(!self->opened){ - TSK_DEBUG_ERROR("Codec not opened"); - return -1; - } - if(param->value_type == tmedia_pvt_int32){ - if(tsk_striequals(param->key, "action")){ - tmedia_codec_action_t action = (tmedia_codec_action_t)TSK_TO_INT32((uint8_t*)param->value); - switch(action){ - case tmedia_codec_action_encode_idr: - { - h264->encoder.force_idr = tsk_true; - break; - } - case tmedia_codec_action_bw_down: - { - h264->encoder.quality = TSK_CLAMP(1, (h264->encoder.quality + 1), 31); -#if HAVE_FFMPEG - h264->encoder.context->global_quality = FF_QP2LAMBDA * h264->encoder.quality; -#endif - break; - } - case tmedia_codec_action_bw_up: - { - h264->encoder.quality = TSK_CLAMP(1, (h264->encoder.quality - 1), 31); -#if HAVE_FFMPEG - h264->encoder.context->global_quality = FF_QP2LAMBDA * h264->encoder.quality; -#endif - break; - } - } - return 0; - } - else if(tsk_striequals(param->key, "rotation")){ - int rotation = *((int32_t*)param->value); - if(h264->encoder.rotation != rotation){ - if(self->opened){ - int ret; - h264->encoder.rotation = rotation; - if((ret = tdav_codec_h264_close_encoder(h264))){ - return ret; - } - if((ret = tdav_codec_h264_open_encoder(h264))){ - return ret; - } -#if 0 // Not working - if((ret = avcodec_close(h264->encoder.context))){ - TSK_DEBUG_ERROR("Failed to close [%s] codec", TMEDIA_CODEC(h264)->plugin->desc); - return ret; - } - h264->encoder.context->width = (rotation == 90 || rotation == 270) ? TMEDIA_CODEC_VIDEO(h264)->out.height : TMEDIA_CODEC_VIDEO(h264)->out.width; - h264->encoder.context->height = (rotation == 90 || rotation == 270) ? TMEDIA_CODEC_VIDEO(h264)->out.width : TMEDIA_CODEC_VIDEO(h264)->out.height; - if((ret = avcodec_open(h264->encoder.context, h264->encoder.codec)) < 0){ - TSK_DEBUG_ERROR("Failed to open [%s] codec", TMEDIA_CODEC(h264)->plugin->desc); - return ret; - } - h264->encoder.force_idr = tsk_true; -#endif - } - } - return 0; - } - } - return -1; -} - - -static int tdav_codec_h264_open(tmedia_codec_t* self) -{ - int ret; - tdav_codec_h264_t* h264 = (tdav_codec_h264_t*)self; - - if(!h264){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - /* the caller (base class) already checked that the codec is not opened */ - - // Encoder - if((ret = tdav_codec_h264_open_encoder(h264))){ - return ret; - } - - // Decoder - if((ret = tdav_codec_h264_open_decoder(h264))){ - return ret; - } - - return 0; -} - -static int tdav_codec_h264_close(tmedia_codec_t* self) -{ - tdav_codec_h264_t* h264 = (tdav_codec_h264_t*)self; - - if(!h264){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - /* the caller (base class) alreasy checked that the codec is opened */ - - // Encoder - tdav_codec_h264_close_encoder(h264); - - // Decoder - tdav_codec_h264_close_decoder(h264); - - return 0; -} - -static tsk_size_t tdav_codec_h264_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) -{ - int ret = 0; - -#if HAVE_FFMPEG - int size; - tsk_bool_t send_idr, send_hdr; -#endif - - tdav_codec_h264_t* h264 = (tdav_codec_h264_t*)self; - - if(!self || !in_data || !in_size || !out_data){ - TSK_DEBUG_ERROR("Invalid parameter"); - return 0; - } - - if(!self->opened){ - TSK_DEBUG_ERROR("Codec not opened"); - return 0; - } - -#if HAVE_FFMPEG - - // wrap yuv420 buffer - size = avpicture_fill((AVPicture *)h264->encoder.picture, (uint8_t*)in_data, PIX_FMT_YUV420P, h264->encoder.context->width, h264->encoder.context->height); - if(size != in_size){ - /* guard */ - TSK_DEBUG_ERROR("Invalid size"); - return 0; - } - - // send IDR for: - // - the first frame - // - remote peer requested an IDR - // - every second within the first 4seconds - send_idr = ( - h264->encoder.frame_count++ == 0 - || h264 ->encoder.force_idr - || ( (h264->encoder.frame_count < (int)TMEDIA_CODEC_VIDEO(h264)->out.fps * 4) && ((h264->encoder.frame_count % TMEDIA_CODEC_VIDEO(h264)->out.fps)==0) ) - ); - - // send SPS and PPS headers for: - // - IDR frames (not required but it's the easiest way to deal with pakt loss) - // - every 5 seconds after the first 4seconds - send_hdr = ( - send_idr - || ( (h264->encoder.frame_count % (TMEDIA_CODEC_VIDEO(h264)->out.fps * 5))==0 ) - ); - if(send_hdr){ - tdav_codec_h264_encap(h264, h264->encoder.context->extradata, (tsk_size_t)h264->encoder.context->extradata_size); - } - - // Encode data -#if LIBAVCODEC_VERSION_MAJOR <= 53 - h264->encoder.picture->pict_type = send_idr ? FF_I_TYPE : 0; -#else - h264->encoder.picture->pict_type = send_idr ? AV_PICTURE_TYPE_I : AV_PICTURE_TYPE_NONE; -#endif - h264->encoder.picture->pts = h264->encoder.frame_count; - h264->encoder.picture->quality = h264->encoder.context->global_quality; - // h264->encoder.picture->pts = h264->encoder.frame_count; MUST NOT - ret = avcodec_encode_video(h264->encoder.context, h264->encoder.buffer, size, h264->encoder.picture); - if(ret > 0){ - tdav_codec_h264_encap(h264, h264->encoder.buffer, (tsk_size_t)ret); - } - h264 ->encoder.force_idr = tsk_false; - -#elif HAVE_H264_PASSTHROUGH - - tdav_codec_h264_encap(h264, (const uint8_t*)in_data, in_size); - -#endif - - return 0; -} - -static tsk_size_t tdav_codec_h264_decode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size, const tsk_object_t* proto_hdr) -{ - tdav_codec_h264_t* h264 = (tdav_codec_h264_t*)self; - const trtp_rtp_header_t* rtp_hdr = (const trtp_rtp_header_t*)proto_hdr; - - const uint8_t* pay_ptr = tsk_null; - tsk_size_t pay_size = 0; - int ret; - tsk_bool_t append_scp; - tsk_size_t retsize = 0, size_to_copy = 0; - static tsk_size_t xmax_size = (1920 * 1080 * 3) >> 3; - static tsk_size_t start_code_prefix_size = sizeof(H264_START_CODE_PREFIX); -#if HAVE_FFMPEG - int got_picture_ptr; -#endif - - if(!h264 || !in_data || !in_size || !out_data -#if HAVE_FFMPEG - || !h264->decoder.context -#endif - ) - { - TSK_DEBUG_ERROR("Invalid parameter"); - return 0; - } - - //TSK_DEBUG_INFO("SeqNo=%hu", rtp_hdr->seq_num); - - /* Packet lost? */ - if((h264->decoder.last_seq + 1) != rtp_hdr->seq_num && h264->decoder.last_seq){ - TSK_DEBUG_INFO("Packet lost, seq_num=%d", (h264->decoder.last_seq + 1)); - } - h264->decoder.last_seq = rtp_hdr->seq_num; - - - /* 5.3. NAL Unit Octet Usage - +---------------+ - |0|1|2|3|4|5|6|7| - +-+-+-+-+-+-+-+-+ - |F|NRI| Type | - +---------------+ - */ - if(*((uint8_t*)in_data) >> 7){ - TSK_DEBUG_WARN("F=1"); - /* reset accumulator */ - h264->decoder.accumulator = 0; - return 0; - } - - /* get payload */ - if((ret = tdav_codec_h264_get_pay(in_data, in_size, (const void**)&pay_ptr, &pay_size, &append_scp)) || !pay_ptr || !pay_size){ - TSK_DEBUG_ERROR("Depayloader failed to get H.264 content"); - return 0; - } - //append_scp = tsk_true; - size_to_copy = pay_size + (append_scp ? start_code_prefix_size : 0); - - // start-accumulator - if(!h264->decoder.accumulator){ - if(size_to_copy > xmax_size){ - TSK_DEBUG_ERROR("%u too big to contain valid encoded data. xmax_size=%u", size_to_copy, xmax_size); - return 0; - } - if(!(h264->decoder.accumulator = tsk_calloc(size_to_copy, sizeof(uint8_t)))){ - TSK_DEBUG_ERROR("Failed to allocated new buffer"); - return 0; - } - h264->decoder.accumulator_size = size_to_copy; - } - if((h264->decoder.accumulator_pos + size_to_copy) >= xmax_size){ - TSK_DEBUG_ERROR("BufferOverflow"); - h264->decoder.accumulator_pos = 0; - return 0; - } - if((h264->decoder.accumulator_pos + size_to_copy) > h264->decoder.accumulator_size){ - if(!(h264->decoder.accumulator = tsk_realloc(h264->decoder.accumulator, (h264->decoder.accumulator_pos + size_to_copy)))){ - TSK_DEBUG_ERROR("Failed to reallocated new buffer"); - h264->decoder.accumulator_pos = 0; - h264->decoder.accumulator_size = 0; - return 0; - } - h264->decoder.accumulator_size = (h264->decoder.accumulator_pos + size_to_copy); - } - - if(append_scp){ - memcpy(&((uint8_t*)h264->decoder.accumulator)[h264->decoder.accumulator_pos], H264_START_CODE_PREFIX, start_code_prefix_size); - h264->decoder.accumulator_pos += start_code_prefix_size; - } - memcpy(&((uint8_t*)h264->decoder.accumulator)[h264->decoder.accumulator_pos], pay_ptr, pay_size); - h264->decoder.accumulator_pos += pay_size; - // end-accumulator - - if(rtp_hdr->marker){ -#if HAVE_FFMPEG - AVPacket packet; - - - /* decode the picture */ - av_init_packet(&packet); - packet.size = h264->decoder.accumulator_pos; - packet.data = h264->decoder.accumulator; - ret = avcodec_decode_video2(h264->decoder.context, h264->decoder.picture, &got_picture_ptr, &packet); - - if(ret <0){ - TSK_DEBUG_INFO("Failed to decode the buffer with error code =%d", ret); - if(TMEDIA_CODEC_VIDEO(self)->in.callback){ - TMEDIA_CODEC_VIDEO(self)->in.result.type = tmedia_video_decode_result_type_error; +/* +* Copyright (C) 2010-2011 Mamadou Diop. +* +* Contact: Mamadou Diop +* +* This file is part of Open Source Doubango Framework. +* +* DOUBANGO 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 3 of the License, or +* (at your option) any later version. +* +* DOUBANGO 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 DOUBANGO. +* +*/ + +/**@file tdav_codec_h264.c + * @brief H.264 codec plugin using FFmpeg for decoding and x264 for encoding + * RTP payloader/depayloader follows RFC 3984 + * + * @author Mamadou Diop + * + + */ +#include "tinydav/codecs/h264/tdav_codec_h264.h" + +#if (HAVE_FFMPEG && (!defined(HAVE_H264) || HAVE_H264)) || HAVE_H264_PASSTHROUGH + +#include "tinydav/codecs/h264/tdav_codec_h264_rtp.h" +#include "tinydav/video/tdav_converter_video.h" + +#include "tinyrtp/rtp/trtp_rtp_packet.h" + +#include "tinymedia/tmedia_params.h" + +#include "tsk_params.h" +#include "tsk_memory.h" +#include "tsk_debug.h" + +#if HAVE_FFMPEG +#include +#endif + +typedef struct tdav_codec_h264_s +{ + TDAV_DECLARE_CODEC_H264_COMMON; + + // Encoder + struct{ +#if HAVE_FFMPEG + AVCodec* codec; + AVCodecContext* context; + AVFrame* picture; +#endif + void* buffer; + int64_t frame_count; + tsk_bool_t force_idr; + int32_t quality; // [1-31] + int rotation; + } encoder; + + // decoder + struct{ +#if HAVE_FFMPEG + AVCodec* codec; + AVCodecContext* context; + AVFrame* picture; +#endif + void* accumulator; + tsk_size_t accumulator_pos; + tsk_size_t accumulator_size; + uint16_t last_seq; + } decoder; +} +tdav_codec_h264_t; + +#define TDAV_H264_GOP_SIZE_IN_SECONDS 25 + +static int tdav_codec_h264_init(tdav_codec_h264_t* self, profile_idc_t profile); +static int tdav_codec_h264_deinit(tdav_codec_h264_t* self); +static int tdav_codec_h264_open_encoder(tdav_codec_h264_t* self); +static int tdav_codec_h264_close_encoder(tdav_codec_h264_t* self); +static int tdav_codec_h264_open_decoder(tdav_codec_h264_t* self); +static int tdav_codec_h264_close_decoder(tdav_codec_h264_t* self); + +static void tdav_codec_h264_encap(const tdav_codec_h264_t* h264, const uint8_t* pdata, tsk_size_t size); + +/* ============ H.264 Base/Main Profile X.X Plugin interface functions ================= */ + +static int tdav_codec_h264_set(tmedia_codec_t* self, const tmedia_param_t* param) +{ + tdav_codec_h264_t* h264 = (tdav_codec_h264_t*)self; + if(!self->opened){ + TSK_DEBUG_ERROR("Codec not opened"); + return -1; + } + if(param->value_type == tmedia_pvt_int32){ + if(tsk_striequals(param->key, "action")){ + tmedia_codec_action_t action = (tmedia_codec_action_t)TSK_TO_INT32((uint8_t*)param->value); + switch(action){ + case tmedia_codec_action_encode_idr: + { + h264->encoder.force_idr = tsk_true; + break; + } + case tmedia_codec_action_bw_down: + { + h264->encoder.quality = TSK_CLAMP(1, (h264->encoder.quality + 1), 31); +#if HAVE_FFMPEG + h264->encoder.context->global_quality = FF_QP2LAMBDA * h264->encoder.quality; +#endif + break; + } + case tmedia_codec_action_bw_up: + { + h264->encoder.quality = TSK_CLAMP(1, (h264->encoder.quality - 1), 31); +#if HAVE_FFMPEG + h264->encoder.context->global_quality = FF_QP2LAMBDA * h264->encoder.quality; +#endif + break; + } + } + return 0; + } + else if(tsk_striequals(param->key, "rotation")){ + int rotation = *((int32_t*)param->value); + if(h264->encoder.rotation != rotation){ + if(self->opened){ + int ret; + h264->encoder.rotation = rotation; + if((ret = tdav_codec_h264_close_encoder(h264))){ + return ret; + } + if((ret = tdav_codec_h264_open_encoder(h264))){ + return ret; + } +#if 0 // Not working + if((ret = avcodec_close(h264->encoder.context))){ + TSK_DEBUG_ERROR("Failed to close [%s] codec", TMEDIA_CODEC(h264)->plugin->desc); + return ret; + } + h264->encoder.context->width = (rotation == 90 || rotation == 270) ? TMEDIA_CODEC_VIDEO(h264)->out.height : TMEDIA_CODEC_VIDEO(h264)->out.width; + h264->encoder.context->height = (rotation == 90 || rotation == 270) ? TMEDIA_CODEC_VIDEO(h264)->out.width : TMEDIA_CODEC_VIDEO(h264)->out.height; + if((ret = avcodec_open(h264->encoder.context, h264->encoder.codec)) < 0){ + TSK_DEBUG_ERROR("Failed to open [%s] codec", TMEDIA_CODEC(h264)->plugin->desc); + return ret; + } + h264->encoder.force_idr = tsk_true; +#endif + } + } + return 0; + } + } + return -1; +} + + +static int tdav_codec_h264_open(tmedia_codec_t* self) +{ + int ret; + tdav_codec_h264_t* h264 = (tdav_codec_h264_t*)self; + + if(!h264){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* the caller (base class) already checked that the codec is not opened */ + + // Encoder + if((ret = tdav_codec_h264_open_encoder(h264))){ + return ret; + } + + // Decoder + if((ret = tdav_codec_h264_open_decoder(h264))){ + return ret; + } + + return 0; +} + +static int tdav_codec_h264_close(tmedia_codec_t* self) +{ + tdav_codec_h264_t* h264 = (tdav_codec_h264_t*)self; + + if(!h264){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* the caller (base class) alreasy checked that the codec is opened */ + + // Encoder + tdav_codec_h264_close_encoder(h264); + + // Decoder + tdav_codec_h264_close_decoder(h264); + + return 0; +} + +static tsk_size_t tdav_codec_h264_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + int ret = 0; + +#if HAVE_FFMPEG + int size; + tsk_bool_t send_idr, send_hdr; +#endif + + tdav_codec_h264_t* h264 = (tdav_codec_h264_t*)self; + + if(!self || !in_data || !in_size){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + if(!self->opened){ + TSK_DEBUG_ERROR("Codec not opened"); + return 0; + } + +#if HAVE_FFMPEG + + // wrap yuv420 buffer + size = avpicture_fill((AVPicture *)h264->encoder.picture, (uint8_t*)in_data, PIX_FMT_YUV420P, h264->encoder.context->width, h264->encoder.context->height); + if(size != in_size){ + /* guard */ + TSK_DEBUG_ERROR("Invalid size"); + return 0; + } + + // send IDR for: + // - the first frame + // - remote peer requested an IDR + // - every second within the first 4seconds + send_idr = ( + h264->encoder.frame_count++ == 0 + || h264 ->encoder.force_idr + || ( (h264->encoder.frame_count < (int)TMEDIA_CODEC_VIDEO(h264)->out.fps * 4) && ((h264->encoder.frame_count % TMEDIA_CODEC_VIDEO(h264)->out.fps)==0) ) + ); + + // send SPS and PPS headers for: + // - IDR frames (not required but it's the easiest way to deal with pakt loss) + // - every 5 seconds after the first 4seconds + send_hdr = ( + send_idr + || ( (h264->encoder.frame_count % (TMEDIA_CODEC_VIDEO(h264)->out.fps * 5))==0 ) + ); + if(send_hdr){ + tdav_codec_h264_encap(h264, h264->encoder.context->extradata, (tsk_size_t)h264->encoder.context->extradata_size); + } + + // Encode data +#if LIBAVCODEC_VERSION_MAJOR <= 53 + h264->encoder.picture->pict_type = send_idr ? FF_I_TYPE : 0; +#else + h264->encoder.picture->pict_type = send_idr ? AV_PICTURE_TYPE_I : AV_PICTURE_TYPE_NONE; +#endif + h264->encoder.picture->pts = AV_NOPTS_VALUE; + h264->encoder.picture->quality = h264->encoder.context->global_quality; + // h264->encoder.picture->pts = h264->encoder.frame_count; MUST NOT + ret = avcodec_encode_video(h264->encoder.context, h264->encoder.buffer, size, h264->encoder.picture); + if(ret > 0){ + tdav_codec_h264_encap(h264, h264->encoder.buffer, (tsk_size_t)ret); + } + h264 ->encoder.force_idr = tsk_false; + +#elif HAVE_H264_PASSTHROUGH + + tdav_codec_h264_encap(h264, (const uint8_t*)in_data, in_size); + +#endif + + return 0; +} + +static tsk_size_t tdav_codec_h264_decode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size, const tsk_object_t* proto_hdr) +{ + tdav_codec_h264_t* h264 = (tdav_codec_h264_t*)self; + const trtp_rtp_header_t* rtp_hdr = (const trtp_rtp_header_t*)proto_hdr; + + const uint8_t* pay_ptr = tsk_null; + tsk_size_t pay_size = 0; + int ret; + tsk_bool_t append_scp; + tsk_bool_t sps_or_pps; + tsk_size_t retsize = 0, size_to_copy = 0; + static tsk_size_t xmax_size = (1920 * 1080 * 3) >> 3; + static tsk_size_t start_code_prefix_size = sizeof(H264_START_CODE_PREFIX); +#if HAVE_FFMPEG + int got_picture_ptr; +#endif + + if(!h264 || !in_data || !in_size || !out_data +#if HAVE_FFMPEG + || !h264->decoder.context +#endif + ) + { + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + //TSK_DEBUG_INFO("SeqNo=%hu", rtp_hdr->seq_num); + + /* Packet lost? */ + if((h264->decoder.last_seq + 1) != rtp_hdr->seq_num && h264->decoder.last_seq){ + TSK_DEBUG_INFO("Packet lost, seq_num=%d", (h264->decoder.last_seq + 1)); + } + h264->decoder.last_seq = rtp_hdr->seq_num; + + + /* 5.3. NAL Unit Octet Usage + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |F|NRI| Type | + +---------------+ + */ + if(*((uint8_t*)in_data) & 0x80){ + TSK_DEBUG_WARN("F=1"); + /* reset accumulator */ + h264->decoder.accumulator = 0; + return 0; + } + + /* get payload */ + if((ret = tdav_codec_h264_get_pay(in_data, in_size, (const void**)&pay_ptr, &pay_size, &append_scp)) || !pay_ptr || !pay_size){ + TSK_DEBUG_ERROR("Depayloader failed to get H.264 content"); + return 0; + } + //append_scp = tsk_true; + size_to_copy = pay_size + (append_scp ? start_code_prefix_size : 0); + // whether it's SPS or PPS + sps_or_pps = pay_ptr && ((pay_ptr[0] & 0x1F) == 7 || (pay_ptr[0] & 0x1F) == 8); + + // start-accumulator + if(!h264->decoder.accumulator){ + if(size_to_copy > xmax_size){ + TSK_DEBUG_ERROR("%u too big to contain valid encoded data. xmax_size=%u", size_to_copy, xmax_size); + return 0; + } + if(!(h264->decoder.accumulator = tsk_calloc(size_to_copy, sizeof(uint8_t)))){ + TSK_DEBUG_ERROR("Failed to allocated new buffer"); + return 0; + } + h264->decoder.accumulator_size = size_to_copy; + } + if((h264->decoder.accumulator_pos + size_to_copy) >= xmax_size){ + TSK_DEBUG_ERROR("BufferOverflow"); + h264->decoder.accumulator_pos = 0; + return 0; + } + if((h264->decoder.accumulator_pos + size_to_copy) > h264->decoder.accumulator_size){ + if(!(h264->decoder.accumulator = tsk_realloc(h264->decoder.accumulator, (h264->decoder.accumulator_pos + size_to_copy)))){ + TSK_DEBUG_ERROR("Failed to reallocated new buffer"); + h264->decoder.accumulator_pos = 0; + h264->decoder.accumulator_size = 0; + return 0; + } + h264->decoder.accumulator_size = (h264->decoder.accumulator_pos + size_to_copy); + } + + if(append_scp){ + memcpy(&((uint8_t*)h264->decoder.accumulator)[h264->decoder.accumulator_pos], H264_START_CODE_PREFIX, start_code_prefix_size); + h264->decoder.accumulator_pos += start_code_prefix_size; + } + memcpy(&((uint8_t*)h264->decoder.accumulator)[h264->decoder.accumulator_pos], pay_ptr, pay_size); + h264->decoder.accumulator_pos += pay_size; + // end-accumulator + + if(sps_or_pps){ + // http://libav-users.943685.n4.nabble.com/Decode-H264-streams-how-to-fill-AVCodecContext-from-SPS-PPS-td2484472.html + // SPS and PPS should be bundled with IDR + TSK_DEBUG_INFO("Receiving SPS or PPS ...to be tied to an IDR"); + } + else if(rtp_hdr->marker){ +#if HAVE_FFMPEG + AVPacket packet; + + /* decode the picture */ + av_init_packet(&packet); + packet.dts = packet.pts = AV_NOPTS_VALUE; + packet.size = h264->decoder.accumulator_pos; + packet.data = h264->decoder.accumulator; + ret = avcodec_decode_video2(h264->decoder.context, h264->decoder.picture, &got_picture_ptr, &packet); + + if(ret <0){ + TSK_DEBUG_INFO("Failed to decode the buffer with error code =%d, size=%u, append=%s", ret, h264->decoder.accumulator_pos, append_scp ? "yes" : "no"); + if(TMEDIA_CODEC_VIDEO(self)->in.callback){ + TMEDIA_CODEC_VIDEO(self)->in.result.type = tmedia_video_decode_result_type_error; + TMEDIA_CODEC_VIDEO(self)->in.result.proto_hdr = proto_hdr; + TMEDIA_CODEC_VIDEO(self)->in.callback(&TMEDIA_CODEC_VIDEO(self)->in.result); + } + } + else if(got_picture_ptr){ + tsk_size_t xsize; + + /* IDR ? */ + if(((((int8_t*)in_data)[0] & 0x1F) == 0x05) && TMEDIA_CODEC_VIDEO(self)->in.callback){ + TSK_DEBUG_INFO("Decoded H.264 IDR"); + TMEDIA_CODEC_VIDEO(self)->in.result.type = tmedia_video_decode_result_type_idr; TMEDIA_CODEC_VIDEO(self)->in.result.proto_hdr = proto_hdr; - TMEDIA_CODEC_VIDEO(self)->in.callback(&TMEDIA_CODEC_VIDEO(self)->in.result); - } - } - else if(got_picture_ptr){ - tsk_size_t xsize; - - /* fill out */ - xsize = avpicture_get_size(h264->decoder.context->pix_fmt, h264->decoder.context->width, h264->decoder.context->height); - if(*out_max_sizein.width = h264->decoder.context->width; - TMEDIA_CODEC_VIDEO(h264)->in.height = h264->decoder.context->height; - avpicture_layout((AVPicture *)h264->decoder.picture, h264->decoder.context->pix_fmt, h264->decoder.context->width, h264->decoder.context->height, - *out_data, retsize); - } -#elif HAVE_H264_PASSTHROUGH - if(*out_max_size < h264->decoder.accumulator_pos){ - if((*out_data = tsk_realloc(*out_data, h264->decoder.accumulator_pos))){ - *out_max_size = h264->decoder.accumulator_pos; - } - else{ - *out_max_size = 0; - return 0; - } - } - memcpy(*out_data, h264->decoder.accumulator, h264->decoder.accumulator_pos); - retsize = h264->decoder.accumulator_pos; -#endif - h264->decoder.accumulator_pos = 0; - } - - return retsize; -} - -static tsk_bool_t tdav_codec_h264_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) -{ - tdav_codec_h264_t* h264 = (tdav_codec_h264_t*)codec; - tsk_bool_t ret = tsk_true; - - if(!h264){ - TSK_DEBUG_ERROR("Invalid parameter"); - return tsk_false; - } - - TSK_DEBUG_INFO("Trying to match [%s:%s]", att_name, att_value); - - if(tsk_striequals(att_name, "fmtp")){ - int val_int; - profile_idc_t profile; - level_idc_t level; - tsk_params_L_t* params; - - /* Check whether the profile match (If the profile is missing, then we consider that it's ok) */ - if(tdav_codec_h264_get_profile_and_level(att_value, &profile, &level) != 0){ - TSK_DEBUG_ERROR("Not valid profile-level: %s", att_value); - return tsk_false; - } - if(TDAV_CODEC_H264_COMMON(codec)->profile != profile){ - return tsk_false; - } - else{ - if(TDAV_CODEC_H264_COMMON(codec)->level != level){ - unsigned width, height; - TDAV_CODEC_H264_COMMON(codec)->level = TSK_MIN(TDAV_CODEC_H264_COMMON(codec)->level, level); - if(tdav_codec_h264_common_size_from_level(TDAV_CODEC_H264_COMMON(codec)->level, &width, &height) != 0){ - return tsk_false; - } - TMEDIA_CODEC_VIDEO(codec)->in.width = TMEDIA_CODEC_VIDEO(codec)->out.width = width; - TMEDIA_CODEC_VIDEO(codec)->in.height = TMEDIA_CODEC_VIDEO(codec)->out.height = height; - } - } - - /* e.g. profile-level-id=42e00a; packetization-mode=1; max-br=452; max-mbps=11880 */ - if((params = tsk_params_fromstring(att_value, ";", tsk_true))){ - - /* === max-br ===*/ - if((val_int = tsk_params_get_param_value_as_int(params, "max-br")) != -1){ - // should compare "max-br"? - TMEDIA_CODEC_VIDEO(h264)->out.max_br = val_int*1000; - } - - /* === max-mbps ===*/ - if((val_int = tsk_params_get_param_value_as_int(params, "max-mbps")) != -1){ - // should compare "max-mbps"? - TMEDIA_CODEC_VIDEO(h264)->out.max_mbps = val_int*1000; - } - - /* === packetization-mode ===*/ - if((val_int = tsk_params_get_param_value_as_int(params, "packetization-mode")) != -1){ - if((packetization_mode_t)val_int == Single_NAL_Unit_Mode || (packetization_mode_t)val_int == Non_Interleaved_Mode){ - TDAV_CODEC_H264_COMMON(h264)->pack_mode = (packetization_mode_t)val_int; - } - else{ - TSK_DEBUG_INFO("packetization-mode not matching"); - ret = tsk_false; - goto bail; - } - } - } -bail: - TSK_OBJECT_SAFE_FREE(params); - } - else if(tsk_striequals(att_name, "imageattr")){ - unsigned in_width, in_height, out_width, out_height; - unsigned width, height; - tsk_size_t s; - if(tmedia_parse_video_imageattr(att_value, TMEDIA_CODEC_VIDEO(codec)->pref_size, &in_width, &in_height, &out_width, &out_height) != 0){ - return tsk_false; - } - // check that 'imageattr' is comform to H.264 'profile-level' - if(tdav_codec_h264_common_size_from_level(TDAV_CODEC_H264_COMMON(codec)->level, &width, &height) != 0){ - return tsk_false; - } - if((s = ((width * height * 3) >> 1)) < ((in_width * in_height * 3) >> 1) || s < ((out_width * out_height * 3) >> 1)){ - return tsk_false; - } - - TMEDIA_CODEC_VIDEO(codec)->in.width = in_width; - TMEDIA_CODEC_VIDEO(codec)->in.height = in_height; - TMEDIA_CODEC_VIDEO(codec)->out.width = out_width; - TMEDIA_CODEC_VIDEO(codec)->out.height = out_height; - } - - return ret; -} - -static char* tdav_codec_h264_sdp_att_get(const tmedia_codec_t* self, const char* att_name) -{ - tdav_codec_h264_t* h264 = (tdav_codec_h264_t*)self; - - if(!h264 || !att_name){ - TSK_DEBUG_ERROR("Invalid parameter"); - return tsk_null; - } - - if(tsk_striequals(att_name, "fmtp")){ - char* fmtp = tsk_null; -#if 1 - tsk_sprintf(&fmtp, "profile-level-id=%x; packetization-mode=%d", ((TDAV_CODEC_H264_COMMON(h264)->profile << 16) | TDAV_CODEC_H264_COMMON(h264)->level), TDAV_CODEC_H264_COMMON(h264)->pack_mode); -#else - tsk_strcat_2(&fmtp, "profile-level-id=%s; packetization-mode=%d; max-br=%d; max-mbps=%d", - profile_level, TDAV_CODEC_H264_COMMON(h264)->pack_mode, TMEDIA_CODEC_VIDEO(h264)->in.max_br/1000, TMEDIA_CODEC_VIDEO(h264)->in.max_mbps/1000); -#endif - return fmtp; - } - else if(tsk_striequals(att_name, "imageattr")){ - return tmedia_get_video_imageattr(TMEDIA_CODEC_VIDEO(self)->pref_size, - TMEDIA_CODEC_VIDEO(self)->in.width, TMEDIA_CODEC_VIDEO(self)->in.height, TMEDIA_CODEC_VIDEO(self)->out.width, TMEDIA_CODEC_VIDEO(self)->out.height); - } - return tsk_null; -} - - - - -/* ============ H.264 Base Profile Plugin interface ================= */ - -/* constructor */ -static tsk_object_t* tdav_codec_h264_base_ctor(tsk_object_t * self, va_list * app) -{ - tdav_codec_h264_t *h264 = (tdav_codec_h264_t*)self; - if(h264){ - /* init base: called by tmedia_codec_create() */ - /* init self */ - if(tdav_codec_h264_init(h264, profile_idc_baseline) != 0){ - return tsk_null; - } - } - return self; -} -/* destructor */ -static tsk_object_t* tdav_codec_h264_base_dtor(tsk_object_t * self) -{ - tdav_codec_h264_t *h264 = (tdav_codec_h264_t*)self; - if(h264){ - /* deinit base */ - tdav_codec_h264_common_deinit(self); - /* deinit self */ - tdav_codec_h264_deinit(h264); - - } - - return self; -} -/* object definition */ -static const tsk_object_def_t tdav_codec_h264_base_def_s = -{ - sizeof(tdav_codec_h264_t), - tdav_codec_h264_base_ctor, - tdav_codec_h264_base_dtor, - tmedia_codec_cmp, -}; -/* plugin definition*/ -static const tmedia_codec_plugin_def_t tdav_codec_h264_base_plugin_def_s = -{ - &tdav_codec_h264_base_def_s, - - tmedia_video, - tmedia_codec_id_h264_bp, - "H264", - "H264 Base Profile", - TMEDIA_CODEC_FORMAT_H264_BP, - tsk_true, - 90000, // rate - - /* audio */ - { 0 }, - - /* video */ - {176, 144, 15}, - - tdav_codec_h264_set, - tdav_codec_h264_open, - tdav_codec_h264_close, - tdav_codec_h264_encode, - tdav_codec_h264_decode, - tdav_codec_h264_sdp_att_match, - tdav_codec_h264_sdp_att_get -}; -const tmedia_codec_plugin_def_t *tdav_codec_h264_base_plugin_def_t = &tdav_codec_h264_base_plugin_def_s; - -/* ============ H.264 Main Profile Plugin interface ================= */ - -/* constructor */ -static tsk_object_t* tdav_codec_h264_main_ctor(tsk_object_t * self, va_list * app) -{ - tdav_codec_h264_t *h264 = self; - if(h264){ - /* init base: called by tmedia_codec_create() */ - /* init self */ - if(tdav_codec_h264_init(h264, profile_idc_main) != 0){ - return tsk_null; - } - } - return self; -} -/* destructor */ -static tsk_object_t* tdav_codec_h264_main_dtor(tsk_object_t * self) -{ - tdav_codec_h264_t *h264 = (tdav_codec_h264_t*)self; - if(h264){ - /* deinit base */ - tdav_codec_h264_common_deinit(self); - /* deinit self */ - tdav_codec_h264_deinit(h264); - - } - - return self; -} -/* object definition */ -static const tsk_object_def_t tdav_codec_h264_main_def_s = -{ - sizeof(tdav_codec_h264_t), - tdav_codec_h264_main_ctor, - tdav_codec_h264_main_dtor, - tmedia_codec_cmp, -}; -/* plugin definition*/ -static const tmedia_codec_plugin_def_t tdav_codec_h264_main_plugin_def_s = -{ - &tdav_codec_h264_main_def_s, - - tmedia_video, - tmedia_codec_id_h264_mp, - "H264", - "H264 Main Profile", - TMEDIA_CODEC_FORMAT_H264_MP, - tsk_true, - 90000, // rate - - /* audio */ - { 0 }, - - /* video */ - {176, 144, 15}, - - tdav_codec_h264_set, - tdav_codec_h264_open, - tdav_codec_h264_close, - tdav_codec_h264_encode, - tdav_codec_h264_decode, - tdav_codec_h264_sdp_att_match, - tdav_codec_h264_sdp_att_get -}; -const tmedia_codec_plugin_def_t *tdav_codec_h264_main_plugin_def_t = &tdav_codec_h264_main_plugin_def_s; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -/* ============ Common To all H264 codecs ================= */ - -int tdav_codec_h264_open_encoder(tdav_codec_h264_t* self) -{ -#if HAVE_FFMPEG - int ret; - tsk_size_t size; - - if(self->encoder.context){ - TSK_DEBUG_ERROR("Encoder already opened"); - return -1; - } - - self->encoder.context = avcodec_alloc_context(); - avcodec_get_context_defaults(self->encoder.context); - -#if TDAV_UNDER_X86 && LIBAVCODEC_VERSION_MAJOR <= 53 - self->encoder.context->dsp_mask = (FF_MM_MMX | FF_MM_MMXEXT | FF_MM_SSE); -#endif - - self->encoder.context->pix_fmt = PIX_FMT_YUV420P; - self->encoder.context->time_base.num = 1; - self->encoder.context->time_base.den = TMEDIA_CODEC_VIDEO(self)->out.fps; - self->encoder.context->width = (self->encoder.rotation == 90 || self->encoder.rotation == 270) ? TMEDIA_CODEC_VIDEO(self)->out.height : TMEDIA_CODEC_VIDEO(self)->out.width; - self->encoder.context->height = (self->encoder.rotation == 90 || self->encoder.rotation == 270) ? TMEDIA_CODEC_VIDEO(self)->out.width : TMEDIA_CODEC_VIDEO(self)->out.height; - - self->encoder.context->bit_rate = ((TMEDIA_CODEC_VIDEO(self)->out.width * TMEDIA_CODEC_VIDEO(self)->out.height * 256 / 352 / 288) * 1000); - self->encoder.context->rc_min_rate = (self->encoder.context->bit_rate >> 3); - self->encoder.context->rc_max_rate = self->encoder.context->bit_rate; - -#if LIBAVCODEC_VERSION_MAJOR <= 53 - self->encoder.context->rc_lookahead = 0; -#endif - self->encoder.context->global_quality = FF_QP2LAMBDA * self->encoder.quality; - - self->encoder.context->scenechange_threshold = 0; - self->encoder.context->me_subpel_quality = 0; -#if LIBAVCODEC_VERSION_MAJOR <= 53 - self->encoder.context->partitions = X264_PART_I4X4 | X264_PART_I8X8 | X264_PART_P8X8 | X264_PART_B8X8; -#endif - self->encoder.context->me_method = ME_EPZS; - self->encoder.context->trellis = 0; - - self->encoder.context->me_range = 16; - self->encoder.context->qmin = 10; - self->encoder.context->qmax = 51; -#if LIBAVCODEC_VERSION_MAJOR <= 53 - self->encoder.context->mb_qmin = self->encoder.context->qmin; - self->encoder.context->mb_qmax = self->encoder.context->qmax; -#endif - self->encoder.context->qcompress = 0.6f; - self->encoder.context->mb_decision = FF_MB_DECISION_SIMPLE; -#if LIBAVCODEC_VERSION_MAJOR <= 53 - self->encoder.context->flags2 |= CODEC_FLAG2_FASTPSKIP; -#else - self->encoder.context->flags2 |= CODEC_FLAG2_FAST; -#endif -#if TDAV_UNDER_X86 - //self->encoder.context->flags2 &= ~CODEC_FLAG2_PSY; - //self->encoder.context->flags2 |= CODEC_FLAG2_SSIM; -#endif - self->encoder.context->flags |= CODEC_FLAG_LOOP_FILTER; - self->encoder.context->flags |= CODEC_FLAG_GLOBAL_HEADER; - self->encoder.context->flags |= CODEC_FLAG_LOW_DELAY; - self->encoder.context->max_b_frames = 0; - self->encoder.context->b_frame_strategy = 1; - self->encoder.context->chromaoffset = 0; - - switch(TDAV_CODEC_H264_COMMON(self)->profile){ - case profile_idc_baseline: - default: - self->encoder.context->profile = FF_PROFILE_H264_BASELINE; - self->encoder.context->level = TDAV_CODEC_H264_COMMON(self)->level; - break; - case profile_idc_main: - self->encoder.context->profile = FF_PROFILE_H264_MAIN; - self->encoder.context->level = TDAV_CODEC_H264_COMMON(self)->level; - break; - } - - self->encoder.context->rtp_payload_size = H264_RTP_PAYLOAD_SIZE; - self->encoder.context->opaque = tsk_null; - self->encoder.context->gop_size = (TMEDIA_CODEC_VIDEO(self)->out.fps * TDAV_H264_GOP_SIZE_IN_SECONDS); - - - // Picture (YUV 420) - if(!(self->encoder.picture = avcodec_alloc_frame())){ - TSK_DEBUG_ERROR("Failed to create encoder picture"); - return -2; - } - avcodec_get_frame_defaults(self->encoder.picture); - - - size = avpicture_get_size(PIX_FMT_YUV420P, self->encoder.context->width, self->encoder.context->height); - if(!(self->encoder.buffer = tsk_calloc(size, sizeof(uint8_t)))){ - TSK_DEBUG_ERROR("Failed to allocate encoder buffer"); - return -2; - } - - // Open encoder - if((ret = avcodec_open(self->encoder.context, self->encoder.codec)) < 0){ - TSK_DEBUG_ERROR("Failed to open [%s] codec", TMEDIA_CODEC(self)->plugin->desc); - return ret; - } - - return ret; -#elif HAVE_H264_PASSTHROUGH - return 0; -#endif - - TSK_DEBUG_ERROR("Not expected code called"); - return -1; -} - -int tdav_codec_h264_close_encoder(tdav_codec_h264_t* self) -{ -#if HAVE_FFMPEG - if(self->encoder.context){ - avcodec_close(self->encoder.context); - av_free(self->encoder.context); - self->encoder.context = tsk_null; - } - if(self->encoder.picture){ - av_free(self->encoder.picture); - self->encoder.picture = tsk_null; - } -#endif - if(self->encoder.buffer){ - TSK_FREE(self->encoder.buffer); - } - self->encoder.frame_count = 0; - - return 0; -} - -int tdav_codec_h264_open_decoder(tdav_codec_h264_t* self) -{ -#if HAVE_FFMPEG - int ret; - - if(self->decoder.context){ - TSK_DEBUG_ERROR("Decoder already opened"); - return -1; - } - - self->decoder.context = avcodec_alloc_context(); - avcodec_get_context_defaults(self->decoder.context); - - self->decoder.context->pix_fmt = PIX_FMT_YUV420P; - self->decoder.context->flags2 |= CODEC_FLAG2_FAST; - self->decoder.context->width = TMEDIA_CODEC_VIDEO(self)->in.width; - self->decoder.context->height = TMEDIA_CODEC_VIDEO(self)->in.height; - -#if TDAV_UNDER_WINDOWS - self->decoder.context->dsp_mask = (FF_MM_MMX | FF_MM_MMXEXT | FF_MM_SSE); -#endif - - // Picture (YUV 420) - if(!(self->decoder.picture = avcodec_alloc_frame())){ - TSK_DEBUG_ERROR("Failed to create decoder picture"); - return -2; - } - avcodec_get_frame_defaults(self->decoder.picture); - - // Open decoder - if((ret = avcodec_open(self->decoder.context, self->decoder.codec)) < 0){ - TSK_DEBUG_ERROR("Failed to open [%s] codec", TMEDIA_CODEC(self)->plugin->desc); - return ret; - } - - return ret; - -#elif HAVE_H264_PASSTHROUGH - return 0; -#endif - - TSK_DEBUG_ERROR("Unexpected code called"); - return -1; - -} - -int tdav_codec_h264_close_decoder(tdav_codec_h264_t* self) -{ -#if HAVE_FFMPEG - if(self->decoder.context){ - avcodec_close(self->decoder.context); - av_free(self->decoder.context); - self->decoder.context = tsk_null; - } - if(self->decoder.picture){ - av_free(self->decoder.picture); - self->decoder.picture = tsk_null; - } -#endif - TSK_FREE(self->decoder.accumulator); - self->decoder.accumulator_pos = 0; - - return 0; -} - -int tdav_codec_h264_init(tdav_codec_h264_t* self, profile_idc_t profile) -{ - int ret = 0; - level_idc_t level; - - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - if((ret = tdav_codec_h264_common_init(TDAV_CODEC_H264_COMMON(self)))){ - TSK_DEBUG_ERROR("tdav_codec_h264_common_init() faile with error code=%d", ret); - return ret; - } - - if((ret = tdav_codec_h264_common_level_from_size(TMEDIA_CODEC_VIDEO(self)->out.width, TMEDIA_CODEC_VIDEO(self)->out.height, &level))){ - TSK_DEBUG_ERROR("Failed to find level for size=[%u, %u]", TMEDIA_CODEC_VIDEO(self)->out.width, TMEDIA_CODEC_VIDEO(self)->out.height); - return ret; - } - - TDAV_CODEC_H264_COMMON(self)->pack_mode = H264_PACKETIZATION_MODE; - TDAV_CODEC_H264_COMMON(self)->profile = profile; - TDAV_CODEC_H264_COMMON(self)->level = level; - TMEDIA_CODEC_VIDEO(self)->in.max_mbps = TMEDIA_CODEC_VIDEO(self)->out.max_mbps = H264_MAX_MBPS*1000; - TMEDIA_CODEC_VIDEO(self)->in.max_br = TMEDIA_CODEC_VIDEO(self)->out.max_br = H264_MAX_BR*1000; - -#if HAVE_FFMPEG - if(!(self->encoder.codec = avcodec_find_encoder(CODEC_ID_H264))){ - TSK_DEBUG_ERROR("Failed to find H.264 encoder"); - ret = -2; - } - - if(!(self->decoder.codec = avcodec_find_decoder(CODEC_ID_H264))){ - TSK_DEBUG_ERROR("Failed to find H.264 decoder"); - ret = -3; - } -#elif HAVE_H264_PASSTHROUGH - TMEDIA_CODEC(self)->passthrough = tsk_true; -#endif - - self->encoder.quality = 1; - - /* allocations MUST be done by open() */ - return ret; -} - -int tdav_codec_h264_deinit(tdav_codec_h264_t* self) -{ - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - -#if HAVE_FFMPEG - self->encoder.codec = tsk_null; - self->decoder.codec = tsk_null; - - // FFMpeg resources are destroyed by close() -#endif - - return 0; -} - -static void tdav_codec_h264_encap(const tdav_codec_h264_t* h264, const uint8_t* pdata, tsk_size_t size) -{ - register int32_t i; - int32_t last_scp, prev_scp; - static int32_t size_of_scp = sizeof(H264_START_CODE_PREFIX); /* we know it's equal to 4 ..but */ - - if(!pdata || !size){ - return; - } - - last_scp = 0, prev_scp = 0; - for(i = size_of_scp; i<(int32_t)(size - size_of_scp); i++){ - if(pdata[i] == H264_START_CODE_PREFIX[0] && pdata[i+1] == H264_START_CODE_PREFIX[1] && pdata[i+2] == H264_START_CODE_PREFIX[2] && pdata[i+3] == H264_START_CODE_PREFIX[3]){ /* Found Start Code Prefix */ - prev_scp = last_scp; - if((i - last_scp) >= H264_RTP_PAYLOAD_SIZE || 1){ - tdav_codec_h264_rtp_callback(TDAV_CODEC_H264_COMMON(h264), pdata + prev_scp, - (i - prev_scp), (prev_scp == size)); - } - last_scp = i; - } - } - - if(last_scp < (int32_t)size){ - tdav_codec_h264_rtp_callback(TDAV_CODEC_H264_COMMON(h264), pdata + last_scp, - (size - last_scp), tsk_true); - } -} - -#endif /* HAVE_FFMPEG */ + TMEDIA_CODEC_VIDEO(self)->in.callback(&TMEDIA_CODEC_VIDEO(self)->in.result); + } + /* fill out */ + xsize = avpicture_get_size(h264->decoder.context->pix_fmt, h264->decoder.context->width, h264->decoder.context->height); + if(*out_max_sizein.width = h264->decoder.context->width; + TMEDIA_CODEC_VIDEO(h264)->in.height = h264->decoder.context->height; + avpicture_layout((AVPicture *)h264->decoder.picture, h264->decoder.context->pix_fmt, h264->decoder.context->width, h264->decoder.context->height, + *out_data, retsize); + } +#elif HAVE_H264_PASSTHROUGH + if(*out_max_size < h264->decoder.accumulator_pos){ + if((*out_data = tsk_realloc(*out_data, h264->decoder.accumulator_pos))){ + *out_max_size = h264->decoder.accumulator_pos; + } + else{ + *out_max_size = 0; + return 0; + } + } + memcpy(*out_data, h264->decoder.accumulator, h264->decoder.accumulator_pos); + retsize = h264->decoder.accumulator_pos; +#endif + h264->decoder.accumulator_pos = 0; + } + + return retsize; +} + +static tsk_bool_t tdav_codec_h264_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ + tdav_codec_h264_t* h264 = (tdav_codec_h264_t*)codec; + tsk_bool_t ret = tsk_true; + + if(!h264){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_false; + } + + TSK_DEBUG_INFO("Trying to match [%s:%s]", att_name, att_value); + + if(tsk_striequals(att_name, "fmtp")){ + int val_int; + profile_idc_t profile; + level_idc_t level; + tsk_params_L_t* params; + + /* Check whether the profile match (If the profile is missing, then we consider that it's ok) */ + if(tdav_codec_h264_get_profile_and_level(att_value, &profile, &level) != 0){ + TSK_DEBUG_ERROR("Not valid profile-level: %s", att_value); + return tsk_false; + } + if(TDAV_CODEC_H264_COMMON(codec)->profile != profile){ + return tsk_false; + } + else{ + if(TDAV_CODEC_H264_COMMON(codec)->level != level){ + unsigned width, height; + TDAV_CODEC_H264_COMMON(codec)->level = TSK_MIN(TDAV_CODEC_H264_COMMON(codec)->level, level); + if(tdav_codec_h264_common_size_from_level(TDAV_CODEC_H264_COMMON(codec)->level, &width, &height) != 0){ + return tsk_false; + } + TMEDIA_CODEC_VIDEO(codec)->in.width = TMEDIA_CODEC_VIDEO(codec)->out.width = width; + TMEDIA_CODEC_VIDEO(codec)->in.height = TMEDIA_CODEC_VIDEO(codec)->out.height = height; + } + } + + /* e.g. profile-level-id=42e00a; packetization-mode=1; max-br=452; max-mbps=11880 */ + if((params = tsk_params_fromstring(att_value, ";", tsk_true))){ + + /* === max-br ===*/ + if((val_int = tsk_params_get_param_value_as_int(params, "max-br")) != -1){ + // should compare "max-br"? + TMEDIA_CODEC_VIDEO(h264)->out.max_br = val_int*1000; + } + + /* === max-mbps ===*/ + if((val_int = tsk_params_get_param_value_as_int(params, "max-mbps")) != -1){ + // should compare "max-mbps"? + TMEDIA_CODEC_VIDEO(h264)->out.max_mbps = val_int*1000; + } + + /* === packetization-mode ===*/ + if((val_int = tsk_params_get_param_value_as_int(params, "packetization-mode")) != -1){ + if((packetization_mode_t)val_int == Single_NAL_Unit_Mode || (packetization_mode_t)val_int == Non_Interleaved_Mode){ + TDAV_CODEC_H264_COMMON(h264)->pack_mode = (packetization_mode_t)val_int; + } + else{ + TSK_DEBUG_INFO("packetization-mode not matching"); + ret = tsk_false; + goto bail; + } + } + } +bail: + TSK_OBJECT_SAFE_FREE(params); + } + else if(tsk_striequals(att_name, "imageattr")){ + unsigned in_width, in_height, out_width, out_height; + unsigned width, height; + tsk_size_t s; + if(tmedia_parse_video_imageattr(att_value, TMEDIA_CODEC_VIDEO(codec)->pref_size, &in_width, &in_height, &out_width, &out_height) != 0){ + return tsk_false; + } + // check that 'imageattr' is comform to H.264 'profile-level' + if(tdav_codec_h264_common_size_from_level(TDAV_CODEC_H264_COMMON(codec)->level, &width, &height) != 0){ + return tsk_false; + } + if((s = ((width * height * 3) >> 1)) < ((in_width * in_height * 3) >> 1) || s < ((out_width * out_height * 3) >> 1)){ + return tsk_false; + } + + TMEDIA_CODEC_VIDEO(codec)->in.width = in_width; + TMEDIA_CODEC_VIDEO(codec)->in.height = in_height; + TMEDIA_CODEC_VIDEO(codec)->out.width = out_width; + TMEDIA_CODEC_VIDEO(codec)->out.height = out_height; + } + + return ret; +} + +static char* tdav_codec_h264_sdp_att_get(const tmedia_codec_t* self, const char* att_name) +{ + tdav_codec_h264_t* h264 = (tdav_codec_h264_t*)self; + + if(!h264 || !att_name){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_null; + } + + if(tsk_striequals(att_name, "fmtp")){ + char* fmtp = tsk_null; +#if 1 + tsk_sprintf(&fmtp, "profile-level-id=%x; packetization-mode=%d", ((TDAV_CODEC_H264_COMMON(h264)->profile << 16) | TDAV_CODEC_H264_COMMON(h264)->level), TDAV_CODEC_H264_COMMON(h264)->pack_mode); +#else + tsk_strcat_2(&fmtp, "profile-level-id=%s; packetization-mode=%d; max-br=%d; max-mbps=%d", + profile_level, TDAV_CODEC_H264_COMMON(h264)->pack_mode, TMEDIA_CODEC_VIDEO(h264)->in.max_br/1000, TMEDIA_CODEC_VIDEO(h264)->in.max_mbps/1000); +#endif + return fmtp; + } + else if(tsk_striequals(att_name, "imageattr")){ + return tmedia_get_video_imageattr(TMEDIA_CODEC_VIDEO(self)->pref_size, + TMEDIA_CODEC_VIDEO(self)->in.width, TMEDIA_CODEC_VIDEO(self)->in.height, TMEDIA_CODEC_VIDEO(self)->out.width, TMEDIA_CODEC_VIDEO(self)->out.height); + } + return tsk_null; +} + + + + +/* ============ H.264 Base Profile Plugin interface ================= */ + +/* constructor */ +static tsk_object_t* tdav_codec_h264_base_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_h264_t *h264 = (tdav_codec_h264_t*)self; + if(h264){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + if(tdav_codec_h264_init(h264, profile_idc_baseline) != 0){ + return tsk_null; + } + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_h264_base_dtor(tsk_object_t * self) +{ + tdav_codec_h264_t *h264 = (tdav_codec_h264_t*)self; + if(h264){ + /* deinit base */ + tdav_codec_h264_common_deinit(self); + /* deinit self */ + tdav_codec_h264_deinit(h264); + + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_h264_base_def_s = +{ + sizeof(tdav_codec_h264_t), + tdav_codec_h264_base_ctor, + tdav_codec_h264_base_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_h264_base_plugin_def_s = +{ + &tdav_codec_h264_base_def_s, + + tmedia_video, + tmedia_codec_id_h264_bp, + "H264", + "H264 Base Profile", + TMEDIA_CODEC_FORMAT_H264_BP, + tsk_true, + 90000, // rate + + /* audio */ + { 0 }, + + /* video */ + {176, 144, 15}, + + tdav_codec_h264_set, + tdav_codec_h264_open, + tdav_codec_h264_close, + tdav_codec_h264_encode, + tdav_codec_h264_decode, + tdav_codec_h264_sdp_att_match, + tdav_codec_h264_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_h264_base_plugin_def_t = &tdav_codec_h264_base_plugin_def_s; + +/* ============ H.264 Main Profile Plugin interface ================= */ + +/* constructor */ +static tsk_object_t* tdav_codec_h264_main_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_h264_t *h264 = self; + if(h264){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + if(tdav_codec_h264_init(h264, profile_idc_main) != 0){ + return tsk_null; + } + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_h264_main_dtor(tsk_object_t * self) +{ + tdav_codec_h264_t *h264 = (tdav_codec_h264_t*)self; + if(h264){ + /* deinit base */ + tdav_codec_h264_common_deinit(self); + /* deinit self */ + tdav_codec_h264_deinit(h264); + + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_h264_main_def_s = +{ + sizeof(tdav_codec_h264_t), + tdav_codec_h264_main_ctor, + tdav_codec_h264_main_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_h264_main_plugin_def_s = +{ + &tdav_codec_h264_main_def_s, + + tmedia_video, + tmedia_codec_id_h264_mp, + "H264", + "H264 Main Profile", + TMEDIA_CODEC_FORMAT_H264_MP, + tsk_true, + 90000, // rate + + /* audio */ + { 0 }, + + /* video */ + {176, 144, 15}, + + tdav_codec_h264_set, + tdav_codec_h264_open, + tdav_codec_h264_close, + tdav_codec_h264_encode, + tdav_codec_h264_decode, + tdav_codec_h264_sdp_att_match, + tdav_codec_h264_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_h264_main_plugin_def_t = &tdav_codec_h264_main_plugin_def_s; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +/* ============ Common To all H264 codecs ================= */ + +int tdav_codec_h264_open_encoder(tdav_codec_h264_t* self) +{ +#if HAVE_FFMPEG + int ret; + tsk_size_t size; + + if(self->encoder.context){ + TSK_DEBUG_ERROR("Encoder already opened"); + return -1; + } + + self->encoder.context = avcodec_alloc_context(); + avcodec_get_context_defaults(self->encoder.context); + +#if TDAV_UNDER_X86 && LIBAVCODEC_VERSION_MAJOR <= 53 + self->encoder.context->dsp_mask = (FF_MM_MMX | FF_MM_MMXEXT | FF_MM_SSE); +#endif + + self->encoder.context->pix_fmt = PIX_FMT_YUV420P; + self->encoder.context->time_base.num = 1; + self->encoder.context->time_base.den = TMEDIA_CODEC_VIDEO(self)->out.fps; + self->encoder.context->width = (self->encoder.rotation == 90 || self->encoder.rotation == 270) ? TMEDIA_CODEC_VIDEO(self)->out.height : TMEDIA_CODEC_VIDEO(self)->out.width; + self->encoder.context->height = (self->encoder.rotation == 90 || self->encoder.rotation == 270) ? TMEDIA_CODEC_VIDEO(self)->out.width : TMEDIA_CODEC_VIDEO(self)->out.height; + + self->encoder.context->bit_rate = ((TMEDIA_CODEC_VIDEO(self)->out.width * TMEDIA_CODEC_VIDEO(self)->out.height * 256 / 352 / 288) * 1000); + self->encoder.context->rc_min_rate = (self->encoder.context->bit_rate >> 3); + self->encoder.context->rc_max_rate = self->encoder.context->bit_rate; + +#if LIBAVCODEC_VERSION_MAJOR <= 53 + self->encoder.context->rc_lookahead = 0; +#endif + self->encoder.context->global_quality = FF_QP2LAMBDA * self->encoder.quality; + + self->encoder.context->scenechange_threshold = 0; + self->encoder.context->me_subpel_quality = 0; +#if LIBAVCODEC_VERSION_MAJOR <= 53 + self->encoder.context->partitions = X264_PART_I4X4 | X264_PART_I8X8 | X264_PART_P8X8 | X264_PART_B8X8; +#endif + self->encoder.context->me_method = ME_EPZS; + self->encoder.context->trellis = 0; + + self->encoder.context->me_range = 16; + self->encoder.context->qmin = 10; + self->encoder.context->qmax = 51; +#if LIBAVCODEC_VERSION_MAJOR <= 53 + self->encoder.context->mb_qmin = self->encoder.context->qmin; + self->encoder.context->mb_qmax = self->encoder.context->qmax; +#endif + self->encoder.context->qcompress = 0.6f; + self->encoder.context->mb_decision = FF_MB_DECISION_SIMPLE; +#if LIBAVCODEC_VERSION_MAJOR <= 53 + self->encoder.context->flags2 |= CODEC_FLAG2_FASTPSKIP; +#else + self->encoder.context->flags2 |= CODEC_FLAG2_FAST; +#endif +#if TDAV_UNDER_X86 + //self->encoder.context->flags2 &= ~CODEC_FLAG2_PSY; + //self->encoder.context->flags2 |= CODEC_FLAG2_SSIM; +#endif + self->encoder.context->flags |= CODEC_FLAG_LOOP_FILTER; + self->encoder.context->flags |= CODEC_FLAG_GLOBAL_HEADER; + self->encoder.context->flags |= CODEC_FLAG_LOW_DELAY; + self->encoder.context->max_b_frames = 0; + self->encoder.context->b_frame_strategy = 1; + self->encoder.context->chromaoffset = 0; + + switch(TDAV_CODEC_H264_COMMON(self)->profile){ + case profile_idc_baseline: + default: + self->encoder.context->profile = FF_PROFILE_H264_BASELINE; + self->encoder.context->level = TDAV_CODEC_H264_COMMON(self)->level; + break; + case profile_idc_main: + self->encoder.context->profile = FF_PROFILE_H264_MAIN; + self->encoder.context->level = TDAV_CODEC_H264_COMMON(self)->level; + break; + } + + self->encoder.context->rtp_payload_size = H264_RTP_PAYLOAD_SIZE; + self->encoder.context->opaque = tsk_null; + self->encoder.context->gop_size = (TMEDIA_CODEC_VIDEO(self)->out.fps * TDAV_H264_GOP_SIZE_IN_SECONDS); + + + // Picture (YUV 420) + if(!(self->encoder.picture = avcodec_alloc_frame())){ + TSK_DEBUG_ERROR("Failed to create encoder picture"); + return -2; + } + avcodec_get_frame_defaults(self->encoder.picture); + + + size = avpicture_get_size(PIX_FMT_YUV420P, self->encoder.context->width, self->encoder.context->height); + if(!(self->encoder.buffer = tsk_calloc(size, sizeof(uint8_t)))){ + TSK_DEBUG_ERROR("Failed to allocate encoder buffer"); + return -2; + } + + // Open encoder + if((ret = avcodec_open(self->encoder.context, self->encoder.codec)) < 0){ + TSK_DEBUG_ERROR("Failed to open [%s] codec", TMEDIA_CODEC(self)->plugin->desc); + return ret; + } + + return ret; +#elif HAVE_H264_PASSTHROUGH + return 0; +#endif + + TSK_DEBUG_ERROR("Not expected code called"); + return -1; +} + +int tdav_codec_h264_close_encoder(tdav_codec_h264_t* self) +{ +#if HAVE_FFMPEG + if(self->encoder.context){ + avcodec_close(self->encoder.context); + av_free(self->encoder.context); + self->encoder.context = tsk_null; + } + if(self->encoder.picture){ + av_free(self->encoder.picture); + self->encoder.picture = tsk_null; + } +#endif + if(self->encoder.buffer){ + TSK_FREE(self->encoder.buffer); + } + self->encoder.frame_count = 0; + + return 0; +} + +int tdav_codec_h264_open_decoder(tdav_codec_h264_t* self) +{ +#if HAVE_FFMPEG + int ret; + + if(self->decoder.context){ + TSK_DEBUG_ERROR("Decoder already opened"); + return -1; + } + + self->decoder.context = avcodec_alloc_context(); + avcodec_get_context_defaults(self->decoder.context); + + self->decoder.context->pix_fmt = PIX_FMT_YUV420P; + self->decoder.context->flags2 |= CODEC_FLAG2_FAST; + self->decoder.context->width = TMEDIA_CODEC_VIDEO(self)->in.width; + self->decoder.context->height = TMEDIA_CODEC_VIDEO(self)->in.height; + +#if TDAV_UNDER_WINDOWS +// self->decoder.context->dsp_mask = (FF_MM_MMX | FF_MM_MMXEXT | FF_MM_SSE); +#endif + + // Picture (YUV 420) + if(!(self->decoder.picture = avcodec_alloc_frame())){ + TSK_DEBUG_ERROR("Failed to create decoder picture"); + return -2; + } + avcodec_get_frame_defaults(self->decoder.picture); + + // Open decoder + if((ret = avcodec_open(self->decoder.context, self->decoder.codec)) < 0){ + TSK_DEBUG_ERROR("Failed to open [%s] codec", TMEDIA_CODEC(self)->plugin->desc); + return ret; + } + + return ret; + +#elif HAVE_H264_PASSTHROUGH + return 0; +#endif + + TSK_DEBUG_ERROR("Unexpected code called"); + return -1; + +} + +int tdav_codec_h264_close_decoder(tdav_codec_h264_t* self) +{ +#if HAVE_FFMPEG + if(self->decoder.context){ + avcodec_close(self->decoder.context); + av_free(self->decoder.context); + self->decoder.context = tsk_null; + } + if(self->decoder.picture){ + av_free(self->decoder.picture); + self->decoder.picture = tsk_null; + } +#endif + TSK_FREE(self->decoder.accumulator); + self->decoder.accumulator_pos = 0; + + return 0; +} + +int tdav_codec_h264_init(tdav_codec_h264_t* self, profile_idc_t profile) +{ + int ret = 0; + level_idc_t level; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if((ret = tdav_codec_h264_common_init(TDAV_CODEC_H264_COMMON(self)))){ + TSK_DEBUG_ERROR("tdav_codec_h264_common_init() faile with error code=%d", ret); + return ret; + } + + if((ret = tdav_codec_h264_common_level_from_size(TMEDIA_CODEC_VIDEO(self)->out.width, TMEDIA_CODEC_VIDEO(self)->out.height, &level))){ + TSK_DEBUG_ERROR("Failed to find level for size=[%u, %u]", TMEDIA_CODEC_VIDEO(self)->out.width, TMEDIA_CODEC_VIDEO(self)->out.height); + return ret; + } + + TDAV_CODEC_H264_COMMON(self)->pack_mode = H264_PACKETIZATION_MODE; + TDAV_CODEC_H264_COMMON(self)->profile = profile; + TDAV_CODEC_H264_COMMON(self)->level = level; + TMEDIA_CODEC_VIDEO(self)->in.max_mbps = TMEDIA_CODEC_VIDEO(self)->out.max_mbps = H264_MAX_MBPS*1000; + TMEDIA_CODEC_VIDEO(self)->in.max_br = TMEDIA_CODEC_VIDEO(self)->out.max_br = H264_MAX_BR*1000; + +#if HAVE_FFMPEG + if(!(self->encoder.codec = avcodec_find_encoder(CODEC_ID_H264))){ + TSK_DEBUG_ERROR("Failed to find H.264 encoder"); + ret = -2; + } + + if(!(self->decoder.codec = avcodec_find_decoder(CODEC_ID_H264))){ + TSK_DEBUG_ERROR("Failed to find H.264 decoder"); + ret = -3; + } +#elif HAVE_H264_PASSTHROUGH + TMEDIA_CODEC(self)->passthrough = tsk_true; +#endif + + self->encoder.quality = 1; + + /* allocations MUST be done by open() */ + return ret; +} + +int tdav_codec_h264_deinit(tdav_codec_h264_t* self) +{ + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + +#if HAVE_FFMPEG + self->encoder.codec = tsk_null; + self->decoder.codec = tsk_null; + + // FFMpeg resources are destroyed by close() +#endif + + return 0; +} + +static void tdav_codec_h264_encap(const tdav_codec_h264_t* h264, const uint8_t* pdata, tsk_size_t size) +{ + static const tsk_size_t size_of_scp = sizeof(H264_START_CODE_PREFIX); /* we know it's equal to 4 .. */ + register tsk_size_t i; + tsk_size_t last_scp, prev_scp; + tsk_size_t _size; + + if(!pdata || size < size_of_scp){ + return; + } + + if(pdata[0] == 0 && pdata[1] == 0){ + if(pdata[2] == 1){ + pdata += 3, size -= 3; + } + else if(pdata[2] == 0 && pdata[3] == 1){ + pdata += 4, size -= 4; + } + } + + _size = (size - size_of_scp); + last_scp = 0, prev_scp = 0; + for(i = size_of_scp; i<_size; i++){ + if(pdata[i] == 0 && pdata[i+1] == 0 && (pdata[i+2] == 1 || (pdata[i+2] == 0 && pdata[i+3] == 1))){ /* Find Start Code Prefix */ + prev_scp = last_scp; + if((i - last_scp) >= H264_RTP_PAYLOAD_SIZE || 1){ + tdav_codec_h264_rtp_callback(TDAV_CODEC_H264_COMMON(h264), pdata + prev_scp, + (i - prev_scp), (prev_scp == size)); + } + last_scp = i; + i += (pdata[i+2] == 1) ? 3 : 4; + } + } + + if(last_scp < (int32_t)size){ + tdav_codec_h264_rtp_callback(TDAV_CODEC_H264_COMMON(h264), pdata + last_scp, + (size - last_scp), tsk_true); + } +} + +#endif /* HAVE_FFMPEG */ diff --git a/branches/2.0/doubango/tinyDAV/src/codecs/h264/tdav_codec_h264_rtp.c b/branches/2.0/doubango/tinyDAV/src/codecs/h264/tdav_codec_h264_rtp.c index cca45f74..0d3613ee 100644 --- a/branches/2.0/doubango/tinyDAV/src/codecs/h264/tdav_codec_h264_rtp.c +++ b/branches/2.0/doubango/tinyDAV/src/codecs/h264/tdav_codec_h264_rtp.c @@ -1,373 +1,362 @@ -/* -* Copyright (C) 2010-2011 Mamadou Diop. -* -* Contact: Mamadou Diop -* -* This file is part of Open Source Doubango Framework. -* -* DOUBANGO 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 3 of the License, or -* (at your option) any later version. -* -* DOUBANGO 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 DOUBANGO. -* -*/ - -/**@file tdav_codec_h264_rtp.c - * @brief H.264 payloader/depayloder as per RFC 3984 - * - * @author Mamadou Diop - * - - */ -#include "tinydav/codecs/h264/tdav_codec_h264_rtp.h" - -#include "tinydav/codecs/h264/tdav_codec_h264_common.h" - -#include "tinymedia/tmedia_codec.h" - -#include "tsk_string.h" -#include "tsk_debug.h" - -#include "tsk_memory.h" -#include /* strlen() */ -#include /* strtol() */ - -/* -* ITU H.264 - http://www.itu.int/rec/T-REC-H.264-200903-S/en -*/ - -uint8_t H264_START_CODE_PREFIX[4] = { 0x00, 0x00, 0x00, 0x01 }; - -#define H264_NAL_UNIT_TYPE_HEADER_SIZE 1 -#define H264_F_UNIT_TYPE_HEADER_SIZE 1 -#define H264_FUA_HEADER_SIZE 2 -#define H264_FUB_HEADER_SIZE 4 -#define H264_NAL_AGG_MAX_SIZE 65535 - -int tdav_codec_h264_get_fua_pay(const uint8_t* in_data, tsk_size_t in_size, const void** out_data, tsk_size_t *out_size, tsk_bool_t* append_scp); -int tdav_codec_h264_get_nalunit_pay(const uint8_t* in_data, tsk_size_t in_size, const void** out_data, tsk_size_t *out_size); - -// profile_level_id MUST be a "null-terminated" string -int tdav_codec_h264_parse_profile(const char* profile_level_id, profile_idc_t *p_idc, profile_iop_t *p_iop, level_idc_t *l_idc) -{ - uint32_t value; - - if(tsk_strlen(profile_level_id) != 6){ - TSK_DEBUG_ERROR("I say [%s] is an invalid profile-level-id", profile_level_id); - return -1; - } - - value = strtol(profile_level_id, tsk_null, 16); - - /* profile-idc */ - if(p_idc){ - switch((value >> 16)){ - case profile_idc_baseline: - *p_idc = profile_idc_baseline; - break; - case profile_idc_extended: - *p_idc = profile_idc_extended; - break; - case profile_idc_main: - *p_idc = profile_idc_main; - break; - case profile_idc_high: - *p_idc = profile_idc_high; - break; - default: - *p_idc = profile_idc_none; - break; - } - } - - /* profile-iop */ - if(p_iop){ - p_iop->constraint_set0_flag = ((value >> 8) & 0x80)>>7; - p_iop->constraint_set1_flag = ((value >> 8) & 0x40)>>6; - p_iop->constraint_set2_flag = ((value >> 8) & 0x20)>>5; - p_iop->reserved_zero_5bits = ((value >> 8) & 0x1F); - } - - /* level-idc */ - if(l_idc){ - switch((value & 0xFF)){ - case level_idc_1_0: - *l_idc = level_idc_1_0; - break; - case level_idc_1_b: - *l_idc = level_idc_1_b; - break; - case level_idc_1_1: - *l_idc = level_idc_1_1; - break; - case level_idc_1_2: - *l_idc = level_idc_1_2; - break; - case level_idc_1_3: - *l_idc = level_idc_1_3; - break; - case level_idc_2_0: - *l_idc = level_idc_2_0; - break; - case level_idc_2_1: - *l_idc = level_idc_2_1; - break; - case level_idc_2_2: - *l_idc = level_idc_2_2; - break; - case level_idc_3_0: - *l_idc = level_idc_3_0; - break; - case level_idc_3_1: - *l_idc = level_idc_3_1; - break; - case level_idc_3_2: - *l_idc = level_idc_3_2; - break; - case level_idc_4_0: - *l_idc = level_idc_4_0; - break; - case level_idc_4_1: - *l_idc = level_idc_4_1; - break; - case level_idc_4_2: - *l_idc = level_idc_4_2; - break; - case level_idc_5_0: - *l_idc = level_idc_5_0; - break; - case level_idc_5_1: - *l_idc = level_idc_5_1; - break; - case level_idc_5_2: - *l_idc = level_idc_5_2; - break; - default: - *l_idc = level_idc_none; - break; - } - } - - return 0; -} - -int tdav_codec_h264_get_pay(const void* in_data, tsk_size_t in_size, const void** out_data, tsk_size_t *out_size, tsk_bool_t* append_scp) -{ - const uint8_t* pdata = in_data; - if(!in_data || !in_size || !out_data || !out_size){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - *out_data = tsk_null; - *out_size = 0; - *append_scp = tsk_true; - - /* 5.3. NAL Unit Octet Usage - +---------------+ - |0|1|2|3|4|5|6|7| - +-+-+-+-+-+-+-+-+ - |F|NRI| Type | - +---------------+ - */ - switch((pdata[0] & 0x1F)){ - case undefined_0: - case undefined_30: - case undefined_31: - case stap_a: - case stap_b: - case mtap16: - case mtap24: - case fu_b: - break; - case fu_a: - return tdav_codec_h264_get_fua_pay(pdata, in_size, out_data, out_size, append_scp); - default: /* NAL unit (1-23) */ - return tdav_codec_h264_get_nalunit_pay(pdata, in_size, out_data, out_size); - } - - TSK_DEBUG_WARN("%d not supported as valid NAL Unit type", (*pdata & 0x1F)); - return -1; -} - - -int tdav_codec_h264_get_fua_pay(const uint8_t* in_data, tsk_size_t in_size, const void** out_data, tsk_size_t *out_size, tsk_bool_t* append_scp) -{ - if(in_size <=2){ - TSK_DEBUG_ERROR("Too short"); - return -1; - } - /* RFC 3984 - 5.8. Fragmentation Units (FUs) - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | FU indicator | FU header | | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | - | | - | FU payload | - | | - | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | :...OPTIONAL RTP padding | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - - The FU indicator octet has the following format: - - +---------------+ - |0|1|2|3|4|5|6|7| - +-+-+-+-+-+-+-+-+ - |F|NRI| Type | - +---------------+ - - The FU header has the following format: - - +---------------+ - |0|1|2|3|4|5|6|7| - +-+-+-+-+-+-+-+-+ - |S|E|R| Type | - +---------------+ - */ - - if((in_data[1] & 0x80) == 0x80 /*S*/){ - /* discard "FU indicator" - S: 1 bit - When set to one, the Start bit indicates the start of a fragmented - NAL unit. When the following FU payload is not the start of a - fragmented NAL unit payload, the Start bit is set to zero. - */ - if(in_size> H264_NAL_UNIT_TYPE_HEADER_SIZE){ - uint8_t hdr; - *out_data = (in_data + H264_NAL_UNIT_TYPE_HEADER_SIZE); - *out_size = (in_size - H264_NAL_UNIT_TYPE_HEADER_SIZE); - - // F, NRI and Type - hdr = (in_data[0] & 0xe0) /* F,NRI from "FU indicator"*/ | (in_data[1] & 0x1f) /* type from "FU header" */; - *((uint8_t*)*out_data) = hdr; - // Need to append Start Code Prefix - *append_scp = tsk_true; - } - else{ - TSK_DEBUG_ERROR("Too short"); - return -1; - } - } - else{ - /* "FU indicator" and "FU header" */ - if(in_size> H264_FUA_HEADER_SIZE){ - *out_data = (in_data + H264_FUA_HEADER_SIZE); - *out_size = (in_size - H264_FUA_HEADER_SIZE); - *append_scp = tsk_false; - } - else{ - TSK_DEBUG_ERROR("Too short"); - return -1; - } - } - - return 0; -} - -int tdav_codec_h264_get_nalunit_pay(const uint8_t* in_data, tsk_size_t in_size, const void** out_data, tsk_size_t *out_size) -{ - -/* 5.6. Single NAL Unit Packet - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - |F|NRI| type | | - +-+-+-+-+-+-+-+-+ | - | | - | Bytes 2..n of a Single NAL unit | - | | - | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | :...OPTIONAL RTP padding | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -*/ - - *out_data = in_data; - *out_size = in_size; - - return 0; -} - -void tdav_codec_h264_rtp_callback(struct tdav_codec_h264_common_s *self, const void *data, tsk_size_t size, tsk_bool_t marker) -{ - uint8_t* pdata = (uint8_t*)data; - - //TSK_DEBUG_INFO("%x %x %x %x -- %u", pdata[0], pdata[1], pdata[2], pdata[3], size); - - if(size>4 && pdata[0] == H264_START_CODE_PREFIX[0] && pdata[1] == H264_START_CODE_PREFIX[1]){ - if(pdata[2] == H264_START_CODE_PREFIX[3]){ - pdata += 3, size -= 3; - } - else if(pdata[2] == H264_START_CODE_PREFIX[2] && pdata[3] == H264_START_CODE_PREFIX[3]){ - pdata += 4, size -= 4; - } - } - - //TSK_DEBUG_INFO("==> SCP %2x %2x %2x %2x", pdata[0], pdata[1], pdata[2], pdata[3]); - - if(size < H264_RTP_PAYLOAD_SIZE){ - /* Can be packet in a Single Nal Unit */ - // Send data over the network - if(TMEDIA_CODEC_VIDEO(self)->out.callback){ - TMEDIA_CODEC_VIDEO(self)->out.result.buffer.ptr = pdata; - TMEDIA_CODEC_VIDEO(self)->out.result.buffer.size = size; - TMEDIA_CODEC_VIDEO(self)->out.result.duration = (3003* (30/TMEDIA_CODEC_VIDEO(self)->out.fps)); - TMEDIA_CODEC_VIDEO(self)->out.result.last_chunck = marker; - TMEDIA_CODEC_VIDEO(self)->out.callback(&TMEDIA_CODEC_VIDEO(self)->out.result); - } - } - else if(size > H264_NAL_UNIT_TYPE_HEADER_SIZE){ - /* Should be Fragmented as FUA */ - uint8_t fua_hdr[H264_FUA_HEADER_SIZE]; /* "FU indicator" and "FU header" - 2bytes */ - fua_hdr[0] = pdata[0] & 0x60/* F=0 */, fua_hdr[0] |= fu_a; - fua_hdr[1] = 0x80/* S=1,E=0,R=0 */, fua_hdr[1] |= pdata[0] & 0x1f; /* type */ - // discard header - pdata += H264_NAL_UNIT_TYPE_HEADER_SIZE; - size -= H264_NAL_UNIT_TYPE_HEADER_SIZE; - - while(size){ - tsk_size_t packet_size = TSK_MIN(H264_RTP_PAYLOAD_SIZE, size); - - if(self->rtp.size < (packet_size + H264_FUA_HEADER_SIZE)){ - if(!(self->rtp.ptr = tsk_realloc(self->rtp.ptr, (packet_size + H264_FUA_HEADER_SIZE)))){ - TSK_DEBUG_ERROR("Failed to allocate new buffer"); - return; - } - self->rtp.size = (packet_size + H264_FUA_HEADER_SIZE); - } - // set E bit - if((size - packet_size) == 0){ - // Last packet - fua_hdr[1] |= 0x40; - } - // copy FUA header - memcpy(self->rtp.ptr, fua_hdr, H264_FUA_HEADER_SIZE); - // reset "S" bit - fua_hdr[1] &= 0x7F; - // copy data - memcpy((self->rtp.ptr + H264_FUA_HEADER_SIZE), pdata, packet_size); - pdata += packet_size; - size -= packet_size; - - // send data - if(TMEDIA_CODEC_VIDEO(self)->out.callback){ - TMEDIA_CODEC_VIDEO(self)->out.result.buffer.ptr = self->rtp.ptr; - TMEDIA_CODEC_VIDEO(self)->out.result.buffer.size = (packet_size + H264_FUA_HEADER_SIZE); - TMEDIA_CODEC_VIDEO(self)->out.result.duration = (3003* (30/TMEDIA_CODEC_VIDEO(self)->out.fps)); - TMEDIA_CODEC_VIDEO(self)->out.result.last_chunck = (size == 0); - TMEDIA_CODEC_VIDEO(self)->out.callback(&TMEDIA_CODEC_VIDEO(self)->out.result); - } - } - } -} +/* +* Copyright (C) 2010-2011 Mamadou Diop. +* +* Contact: Mamadou Diop +* +* This file is part of Open Source Doubango Framework. +* +* DOUBANGO 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 3 of the License, or +* (at your option) any later version. +* +* DOUBANGO 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 DOUBANGO. +* +*/ + +/**@file tdav_codec_h264_rtp.c + * @brief H.264 payloader/depayloder as per RFC 3984 + * + * @author Mamadou Diop + * + + */ +#include "tinydav/codecs/h264/tdav_codec_h264_rtp.h" + +#include "tinydav/codecs/h264/tdav_codec_h264_common.h" + +#include "tinymedia/tmedia_codec.h" + +#include "tsk_string.h" +#include "tsk_debug.h" + +#include "tsk_memory.h" +#include /* strlen() */ +#include /* strtol() */ + +/* +* ITU H.264 - http://www.itu.int/rec/T-REC-H.264-200903-S/en +*/ + +uint8_t H264_START_CODE_PREFIX[4] = { 0x00, 0x00, 0x00, 0x01 }; + +#define H264_NAL_UNIT_TYPE_HEADER_SIZE 1 +#define H264_F_UNIT_TYPE_HEADER_SIZE 1 +#define H264_FUA_HEADER_SIZE 2 +#define H264_FUB_HEADER_SIZE 4 +#define H264_NAL_AGG_MAX_SIZE 65535 + +int tdav_codec_h264_get_fua_pay(const uint8_t* in_data, tsk_size_t in_size, const void** out_data, tsk_size_t *out_size, tsk_bool_t* append_scp); +int tdav_codec_h264_get_nalunit_pay(const uint8_t* in_data, tsk_size_t in_size, const void** out_data, tsk_size_t *out_size); + +// profile_level_id MUST be a "null-terminated" string +int tdav_codec_h264_parse_profile(const char* profile_level_id, profile_idc_t *p_idc, profile_iop_t *p_iop, level_idc_t *l_idc) +{ + uint32_t value; + + if(tsk_strlen(profile_level_id) != 6){ + TSK_DEBUG_ERROR("I say [%s] is an invalid profile-level-id", profile_level_id); + return -1; + } + + value = strtol(profile_level_id, tsk_null, 16); + + /* profile-idc */ + if(p_idc){ + switch((value >> 16)){ + case profile_idc_baseline: + *p_idc = profile_idc_baseline; + break; + case profile_idc_extended: + *p_idc = profile_idc_extended; + break; + case profile_idc_main: + *p_idc = profile_idc_main; + break; + case profile_idc_high: + *p_idc = profile_idc_high; + break; + default: + *p_idc = profile_idc_none; + break; + } + } + + /* profile-iop */ + if(p_iop){ + p_iop->constraint_set0_flag = ((value >> 8) & 0x80)>>7; + p_iop->constraint_set1_flag = ((value >> 8) & 0x40)>>6; + p_iop->constraint_set2_flag = ((value >> 8) & 0x20)>>5; + p_iop->reserved_zero_5bits = ((value >> 8) & 0x1F); + } + + /* level-idc */ + if(l_idc){ + switch((value & 0xFF)){ + case level_idc_1_0: + *l_idc = level_idc_1_0; + break; + case level_idc_1_b: + *l_idc = level_idc_1_b; + break; + case level_idc_1_1: + *l_idc = level_idc_1_1; + break; + case level_idc_1_2: + *l_idc = level_idc_1_2; + break; + case level_idc_1_3: + *l_idc = level_idc_1_3; + break; + case level_idc_2_0: + *l_idc = level_idc_2_0; + break; + case level_idc_2_1: + *l_idc = level_idc_2_1; + break; + case level_idc_2_2: + *l_idc = level_idc_2_2; + break; + case level_idc_3_0: + *l_idc = level_idc_3_0; + break; + case level_idc_3_1: + *l_idc = level_idc_3_1; + break; + case level_idc_3_2: + *l_idc = level_idc_3_2; + break; + case level_idc_4_0: + *l_idc = level_idc_4_0; + break; + case level_idc_4_1: + *l_idc = level_idc_4_1; + break; + case level_idc_4_2: + *l_idc = level_idc_4_2; + break; + case level_idc_5_0: + *l_idc = level_idc_5_0; + break; + case level_idc_5_1: + *l_idc = level_idc_5_1; + break; + case level_idc_5_2: + *l_idc = level_idc_5_2; + break; + default: + *l_idc = level_idc_none; + break; + } + } + + return 0; +} + +int tdav_codec_h264_get_pay(const void* in_data, tsk_size_t in_size, const void** out_data, tsk_size_t *out_size, tsk_bool_t* append_scp) +{ + const uint8_t* pdata = in_data; + uint8_t nal_type; + if(!in_data || !in_size || !out_data || !out_size){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + *out_data = tsk_null; + *out_size = 0; + + /* 5.3. NAL Unit Octet Usage + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |F|NRI| Type | + +---------------+ + */ + switch((nal_type = (pdata[0] & 0x1F))){ + case undefined_0: + case undefined_30: + case undefined_31: + case stap_a: + case stap_b: + case mtap16: + case mtap24: + case fu_b: + break; + case fu_a: + return tdav_codec_h264_get_fua_pay(pdata, in_size, out_data, out_size, append_scp); + default: /* NAL unit (1-23) */ + *append_scp = tsk_true;//(nal_type != 7 && nal_type != 8); // SPS or PPS + return tdav_codec_h264_get_nalunit_pay(pdata, in_size, out_data, out_size); + } + + TSK_DEBUG_WARN("%d not supported as valid NAL Unit type", (*pdata & 0x1F)); + return -1; +} + + +int tdav_codec_h264_get_fua_pay(const uint8_t* in_data, tsk_size_t in_size, const void** out_data, tsk_size_t *out_size, tsk_bool_t* append_scp) +{ + if(in_size <=2){ + TSK_DEBUG_ERROR("Too short"); + return -1; + } + /* RFC 3984 - 5.8. Fragmentation Units (FUs) + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | FU indicator | FU header | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + | | + | FU payload | + | | + | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | :...OPTIONAL RTP padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + The FU indicator octet has the following format: + + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |F|NRI| Type | + +---------------+ + + The FU header has the following format: + + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |S|E|R| Type | + +---------------+ + */ + + if((in_data[1] & 0x80) /*S*/){ + /* discard "FU indicator" + S: 1 bit + When set to one, the Start bit indicates the start of a fragmented + NAL unit. When the following FU payload is not the start of a + fragmented NAL unit payload, the Start bit is set to zero. + */ + uint8_t hdr; + *out_data = (in_data + H264_NAL_UNIT_TYPE_HEADER_SIZE); + *out_size = (in_size - H264_NAL_UNIT_TYPE_HEADER_SIZE); + + // F, NRI and Type + hdr = (in_data[0] & 0xe0) /* F,NRI from "FU indicator"*/ | (in_data[1] & 0x1f) /* type from "FU header" */; + *((uint8_t*)*out_data) = hdr; + // Need to append Start Code Prefix + *append_scp = tsk_true; +} + else{ + /* "FU indicator" and "FU header" */ + *out_data = (in_data + H264_FUA_HEADER_SIZE); + *out_size = (in_size - H264_FUA_HEADER_SIZE); + *append_scp = tsk_false; + } + + return 0; +} + +int tdav_codec_h264_get_nalunit_pay(const uint8_t* in_data, tsk_size_t in_size, const void** out_data, tsk_size_t *out_size) +{ + +/* 5.6. Single NAL Unit Packet + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |F|NRI| type | | + +-+-+-+-+-+-+-+-+ | + | | + | Bytes 2..n of a Single NAL unit | + | | + | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | :...OPTIONAL RTP padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + + *out_data = in_data; + *out_size = in_size; + + return 0; +} + +void tdav_codec_h264_rtp_callback(struct tdav_codec_h264_common_s *self, const void *data, tsk_size_t size, tsk_bool_t marker) +{ + uint8_t* pdata = (uint8_t*)data; + + //TSK_DEBUG_INFO("%x %x %x %x -- %u", pdata[0], pdata[1], pdata[2], pdata[3], size); + + if(size>4 && pdata[0] == H264_START_CODE_PREFIX[0] && pdata[1] == H264_START_CODE_PREFIX[1]){ + if(pdata[2] == H264_START_CODE_PREFIX[3]){ + pdata += 3, size -= 3; + } + else if(pdata[2] == H264_START_CODE_PREFIX[2] && pdata[3] == H264_START_CODE_PREFIX[3]){ + pdata += 4, size -= 4; + } + } + + //TSK_DEBUG_INFO("==> SCP %2x %2x %2x %2x", pdata[0], pdata[1], pdata[2], pdata[3]); + + if(size < H264_RTP_PAYLOAD_SIZE){ + /* Can be packet in a Single Nal Unit */ + // Send data over the network + if(TMEDIA_CODEC_VIDEO(self)->out.callback){ + TMEDIA_CODEC_VIDEO(self)->out.result.buffer.ptr = pdata; + TMEDIA_CODEC_VIDEO(self)->out.result.buffer.size = size; + TMEDIA_CODEC_VIDEO(self)->out.result.duration = (1./(double)TMEDIA_CODEC_VIDEO(self)->out.fps) * TMEDIA_CODEC(self)->plugin->rate; + TMEDIA_CODEC_VIDEO(self)->out.result.last_chunck = marker; + TMEDIA_CODEC_VIDEO(self)->out.callback(&TMEDIA_CODEC_VIDEO(self)->out.result); + } + } + else if(size > H264_NAL_UNIT_TYPE_HEADER_SIZE){ + /* Should be Fragmented as FUA */ + uint8_t fua_hdr[H264_FUA_HEADER_SIZE]; /* "FU indicator" and "FU header" - 2bytes */ + fua_hdr[0] = pdata[0] & 0x60/* NRI */, fua_hdr[0] |= fu_a; + fua_hdr[1] = 0x80/* S=1,E=0,R=0 */, fua_hdr[1] |= pdata[0] & 0x1f; /* type */ + // discard header + pdata += H264_NAL_UNIT_TYPE_HEADER_SIZE; + size -= H264_NAL_UNIT_TYPE_HEADER_SIZE; + + while(size){ + tsk_size_t packet_size = TSK_MIN(H264_RTP_PAYLOAD_SIZE, size); + + if(self->rtp.size < (packet_size + H264_FUA_HEADER_SIZE)){ + if(!(self->rtp.ptr = tsk_realloc(self->rtp.ptr, (packet_size + H264_FUA_HEADER_SIZE)))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + return; + } + self->rtp.size = (packet_size + H264_FUA_HEADER_SIZE); + } + // set E bit + if((size - packet_size) == 0){ + // Last packet + fua_hdr[1] |= 0x40; + } + // copy FUA header + memcpy(self->rtp.ptr, fua_hdr, H264_FUA_HEADER_SIZE); + // reset "S" bit + fua_hdr[1] &= 0x7F; + // copy data + memcpy((self->rtp.ptr + H264_FUA_HEADER_SIZE), pdata, packet_size); + pdata += packet_size; + size -= packet_size; + + // send data + if(TMEDIA_CODEC_VIDEO(self)->out.callback){ + TMEDIA_CODEC_VIDEO(self)->out.result.buffer.ptr = self->rtp.ptr; + TMEDIA_CODEC_VIDEO(self)->out.result.buffer.size = (packet_size + H264_FUA_HEADER_SIZE); + TMEDIA_CODEC_VIDEO(self)->out.result.duration = (1./(double)TMEDIA_CODEC_VIDEO(self)->out.fps) * TMEDIA_CODEC(self)->plugin->rate; + TMEDIA_CODEC_VIDEO(self)->out.result.last_chunck = (size == 0); + TMEDIA_CODEC_VIDEO(self)->out.callback(&TMEDIA_CODEC_VIDEO(self)->out.result); + } + } + } +} diff --git a/branches/2.0/doubango/tinyDAV/src/codecs/mp4ves/tdav_codec_mp4ves.c b/branches/2.0/doubango/tinyDAV/src/codecs/mp4ves/tdav_codec_mp4ves.c index 990ccc7c..86e910f5 100644 --- a/branches/2.0/doubango/tinyDAV/src/codecs/mp4ves/tdav_codec_mp4ves.c +++ b/branches/2.0/doubango/tinyDAV/src/codecs/mp4ves/tdav_codec_mp4ves.c @@ -724,7 +724,7 @@ static void tdav_codec_mp4ves_rtp_callback(tdav_codec_mp4ves_t *mp4v, const void if(TMEDIA_CODEC_VIDEO(mp4v)->out.callback){ TMEDIA_CODEC_VIDEO(mp4v)->out.result.buffer.ptr = data; TMEDIA_CODEC_VIDEO(mp4v)->out.result.buffer.size = size; - TMEDIA_CODEC_VIDEO(mp4v)->out.result.duration = (3003* (30/TMEDIA_CODEC_VIDEO(mp4v)->out.fps)); + TMEDIA_CODEC_VIDEO(mp4v)->out.result.duration = (1./(double)TMEDIA_CODEC_VIDEO(mp4v)->out.fps) * TMEDIA_CODEC(mp4v)->plugin->rate; TMEDIA_CODEC_VIDEO(mp4v)->out.result.last_chunck = marker; TMEDIA_CODEC_VIDEO(mp4v)->out.callback(&TMEDIA_CODEC_VIDEO(mp4v)->out.result); } diff --git a/branches/2.0/doubango/tinyDAV/src/codecs/theora/tdav_codec_theora.c b/branches/2.0/doubango/tinyDAV/src/codecs/theora/tdav_codec_theora.c index 281ed2f0..611d7784 100644 --- a/branches/2.0/doubango/tinyDAV/src/codecs/theora/tdav_codec_theora.c +++ b/branches/2.0/doubango/tinyDAV/src/codecs/theora/tdav_codec_theora.c @@ -834,7 +834,7 @@ int tdav_codec_theora_send(tdav_codec_theora_t* self, const uint8_t* data, tsk_s if(TMEDIA_CODEC_VIDEO(self)->out.callback){ TMEDIA_CODEC_VIDEO(self)->out.result.buffer.ptr = self->rtp.ptr; TMEDIA_CODEC_VIDEO(self)->out.result.buffer.size = (pay_size + sizeof(pay_hdr)); - TMEDIA_CODEC_VIDEO(self)->out.result.duration = (3003* (30/TMEDIA_CODEC_VIDEO(self)->out.fps)); + TMEDIA_CODEC_VIDEO(self)->out.result.duration = (1./(double)TMEDIA_CODEC_VIDEO(self)->out.fps) * TMEDIA_CODEC(self)->plugin->rate; TMEDIA_CODEC_VIDEO(self)->out.result.last_chunck = (size == 0); TMEDIA_CODEC_VIDEO(self)->out.callback(&TMEDIA_CODEC_VIDEO(self)->out.result); } diff --git a/branches/2.0/doubango/tinyDAV/src/codecs/vpx/tdav_codec_vp8.c b/branches/2.0/doubango/tinyDAV/src/codecs/vpx/tdav_codec_vp8.c index 87fd3cf9..24b1ddff 100644 --- a/branches/2.0/doubango/tinyDAV/src/codecs/vpx/tdav_codec_vp8.c +++ b/branches/2.0/doubango/tinyDAV/src/codecs/vpx/tdav_codec_vp8.c @@ -1,877 +1,962 @@ -/* -* Copyright (C) 2011 Doubango Telecom -* -* Contact: Mamadou Diop -* -* This file is part of Open Source Doubango Framework. -* -* DOUBANGO 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 3 of the License, or -* (at your option) any later version. -* -* DOUBANGO 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 DOUBANGO. -* -*/ - -/**@file tdav_codec_vp8.c - * @brief VP8 codec - * The RTP packetizer/depacketizer follows draft-ietf-payload-vp8 and draft-bankoski-vp8-bitstream-05 - * Google's VP8 (http://www.webmproject.org/) encoder/decoder - * - * @author Mamadou Diop - * - */ -#include "tinydav/codecs/vpx/tdav_codec_vp8.h" - -#if HAVE_LIBVPX - -#if TDAV_UNDER_WINDOWS -# include -#endif - -#include "tinyrtp/rtp/trtp_rtp_packet.h" - -#include "tinymedia/tmedia_params.h" - -#include "tsk_string.h" -#include "tsk_memory.h" -#include "tsk_time.h" -#include "tsk_debug.h" - -#if !defined(TDAV_VP8_DISABLE_EXTENSION) -# define TDAV_VP8_DISABLE_EXTENSION 0 /* Set X fied value to zero */ -#endif - -#if TDAV_VP8_DISABLE_EXTENSION -# define TDAV_VP8_PAY_DESC_SIZE 1 -#else -# define TDAV_VP8_PAY_DESC_SIZE 4 -#endif -#define TDAV_SYSTEM_CORES_COUNT 0 -#define TDAV_VP8_GOP_SIZE_IN_SECONDS 25 -#define TDAV_VP8_RTP_PAYLOAD_MAX_SIZE 1050 -#if !defined(TDAV_VP8_MAX_BANDWIDTH_KB) -# define TDAV_VP8_MAX_BANDWIDTH_KB 6000 -#endif -#if !defined(TDAV_VP8_MIN_BANDWIDTH_KB) -# define TDAV_VP8_MIN_BANDWIDTH_KB 100 -#endif - -/* VP8 codec */ -typedef struct tdav_codec_vp8_s -{ - TMEDIA_DECLARE_CODEC_VIDEO; - - // Encoder - struct{ - vpx_codec_enc_cfg_t cfg; - tsk_bool_t initialized; - vpx_codec_pts_t pts; - vpx_codec_ctx_t context; - uint16_t pic_id; - uint64_t frame_count; - tsk_bool_t force_idr; - uint32_t target_bitrate; - int rotation; - - struct{ - uint8_t* ptr; - tsk_size_t size; - } rtp; - } encoder; - - // decoder - struct{ - vpx_codec_dec_cfg_t cfg; - unsigned initialized:1; - vpx_codec_ctx_t context; - void* accumulator; - tsk_size_t accumulator_pos; - tsk_size_t accumulator_size; - uint16_t last_seq; - unsigned last_PartID:4; - unsigned last_S:1; - unsigned last_N:1; - } decoder; -} -tdav_codec_vp8_t; - -#define vp8_interface_enc (vpx_codec_vp8_cx()) -#define vp8_interface_dec (vpx_codec_vp8_dx()) - -static int tdav_codec_vp8_open_encoder(tdav_codec_vp8_t* self); -static int tdav_codec_vp8_open_decoder(tdav_codec_vp8_t* self); -static int tdav_codec_vp8_close_encoder(tdav_codec_vp8_t* self); -static int tdav_codec_vp8_close_decoder(tdav_codec_vp8_t* self); - -static void tdav_codec_vp8_encap(tdav_codec_vp8_t* self, const vpx_codec_cx_pkt_t *pkt); -static void tdav_codec_vp8_rtp_callback(tdav_codec_vp8_t *self, const void *data, tsk_size_t size, uint32_t partID, tsk_bool_t part_start, tsk_bool_t non_ref, tsk_bool_t last); - -/* ============ VP8 Plugin interface ================= */ - -static int tdav_codec_vp8_set(tmedia_codec_t* self, const tmedia_param_t* param) -{ - tdav_codec_vp8_t* vp8 = (tdav_codec_vp8_t*)self; - vpx_codec_err_t vpx_ret; - - if(!vp8->encoder.initialized){ - TSK_DEBUG_ERROR("Codec not initialized"); - return -1; - } - if(param->value_type == tmedia_pvt_int32){ - if(tsk_striequals(param->key, "action")){ - tmedia_codec_action_t action = (tmedia_codec_action_t)TSK_TO_INT32((uint8_t*)param->value); - tsk_bool_t reconf = tsk_false; - switch(action){ - case tmedia_codec_action_encode_idr: - { - vp8->encoder.force_idr = tsk_true; - break; - } - case tmedia_codec_action_bw_down: - { - vp8->encoder.cfg.rc_target_bitrate = ((vp8->encoder.cfg.rc_target_bitrate << 1) / 3); - TSK_DEBUG_INFO("New target bitrate = %d kbps", vp8->encoder.cfg.rc_target_bitrate); - reconf = tsk_true; - break; - } - case tmedia_codec_action_bw_up: - { - vp8->encoder.cfg.rc_target_bitrate = ((vp8->encoder.cfg.rc_target_bitrate * 3) >> 1); - TSK_DEBUG_INFO("New target bitrate = %d kbps", vp8->encoder.cfg.rc_target_bitrate); - reconf = tsk_true; - break; - } - } - - if(reconf){ - if((vpx_ret = vpx_codec_enc_config_set(&vp8->encoder.context, &vp8->encoder.cfg)) != VPX_CODEC_OK){ - TSK_DEBUG_ERROR("vpx_codec_enc_config_set failed with error =%s", vpx_codec_err_to_string(vpx_ret)); - } - } - } - else if(tsk_striequals(param->key, "rotation")){ - // IMPORTANT: changing resolution requires at least libvpx v1.1.0 "Eider" - int rotation = *((int32_t*)param->value); - if(vp8->encoder.rotation != rotation){ - vp8->encoder.rotation = rotation; - if(vp8->encoder.initialized){ -#if 1 - vp8->encoder.cfg.g_w = (rotation == 90 || rotation == 270) ? TMEDIA_CODEC_VIDEO(vp8)->out.height : TMEDIA_CODEC_VIDEO(vp8)->out.width; - vp8->encoder.cfg.g_h = (rotation == 90 || rotation == 270) ? TMEDIA_CODEC_VIDEO(vp8)->out.width : TMEDIA_CODEC_VIDEO(vp8)->out.height; - if((vpx_ret = vpx_codec_enc_config_set(&vp8->encoder.context, &vp8->encoder.cfg)) != VPX_CODEC_OK){ - TSK_DEBUG_ERROR("vpx_codec_enc_config_set failed with error =%s", vpx_codec_err_to_string(vpx_ret)); - return -1; - } -#else - int ret; - if((ret = tdav_codec_vp8_close_encoder(vp8))){ - return ret; - } - if((ret = tdav_codec_vp8_open_encoder(vp8))){ - return ret; - } -#endif - } - return 0; - } - } - } - return -1; -} - -static int tdav_codec_vp8_open(tmedia_codec_t* self) -{ - tdav_codec_vp8_t* vp8 = (tdav_codec_vp8_t*)self; - int ret; - - if(!vp8){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - /* the caller (base class) already checked that the codec is not opened */ - - - // Encoder - if((ret = tdav_codec_vp8_open_encoder(vp8))){ - return ret; - } - - // Decoder - if((ret = tdav_codec_vp8_open_decoder(vp8))){ - return ret; - } - - return ret; -} - -static int tdav_codec_vp8_close(tmedia_codec_t* self) -{ - tdav_codec_vp8_t* vp8 = (tdav_codec_vp8_t*)self; - - if(!vp8){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - tdav_codec_vp8_close_encoder(vp8); - tdav_codec_vp8_close_decoder(vp8); - - return 0; -} - -static tsk_size_t tdav_codec_vp8_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) -{ - tdav_codec_vp8_t* vp8 = (tdav_codec_vp8_t*)self; - vpx_enc_frame_flags_t flags = 0; - vpx_codec_err_t vpx_ret; - const vpx_codec_cx_pkt_t *pkt; - vpx_codec_iter_t iter = tsk_null; - vpx_image_t image; - - if(!vp8 || !in_data || !in_size || !out_data){ - TSK_DEBUG_ERROR("Invalid parameter"); - return 0; - } - - if(in_size != (vp8->encoder.context.config.enc->g_w * vp8->encoder.context.config.enc->g_h * 3)>>1){ - TSK_DEBUG_ERROR("Invalid size"); - return 0; - } - - // wrap yuv420 buffer - if(!vpx_img_wrap(&image, VPX_IMG_FMT_I420, vp8->encoder.context.config.enc->g_w, vp8->encoder.context.config.enc->g_h, 1, (unsigned char*)in_data)){ - TSK_DEBUG_ERROR("vpx_img_wrap failed"); - return 0; - } - - // encode data - ++vp8->encoder.pts; - if(vp8->encoder.force_idr){ - flags |= VPX_EFLAG_FORCE_KF; - vp8->encoder.force_idr = tsk_false; - } - if((vpx_ret = vpx_codec_encode(&vp8->encoder.context, &image, vp8->encoder.pts, 1, flags, VPX_DL_REALTIME)) != VPX_CODEC_OK){ - TSK_DEBUG_ERROR("vpx_codec_encode failed with error =%s", vpx_codec_err_to_string(vpx_ret)); - vpx_img_free(&image); - return 0; - } - - ++vp8->encoder.frame_count; - ++vp8->encoder.pic_id; - - while((pkt = vpx_codec_get_cx_data(&vp8->encoder.context, &iter))){ - switch(pkt->kind){ - case VPX_CODEC_CX_FRAME_PKT: - { - tdav_codec_vp8_encap(vp8, pkt); - break; - } - default: - case VPX_CODEC_STATS_PKT: /**< Two-pass statistics for this frame */ - case VPX_CODEC_PSNR_PKT: /**< PSNR statistics for this frame */ - case VPX_CODEC_CUSTOM_PKT: /**< Algorithm extensions */ - { - TSK_DEBUG_INFO("pkt->kind=%d not supported", (int)pkt->kind); - break; - } - } - } - - vpx_img_free(&image); - return 0; -} - -static tsk_size_t tdav_codec_vp8_decode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size, const tsk_object_t* proto_hdr) -{ - tdav_codec_vp8_t* vp8 = (tdav_codec_vp8_t*)self; - const trtp_rtp_header_t* rtp_hdr = proto_hdr; - const uint8_t* pdata = in_data; - const uint8_t* pdata_end = (pdata + in_size); - tsk_size_t ret = 0; - static const tsk_size_t xmax_size = (1920 * 1080 * 3) >> 3; - - if(!self || !in_data || in_size<1 || !out_data || !vp8->decoder.initialized){ - TSK_DEBUG_ERROR("Invalid parameter"); - return 0; - } - - { /* 4.2. VP8 Payload Descriptor */ - uint8_t X, R, N, S, I, L, T, K, PartID;//FIXME: store - - X = (*pdata & 0x80)>>7; - R = (*pdata & 0x40)>>6; - if(R){ - TSK_DEBUG_ERROR("R<>0"); - return 0; - } - N = (*pdata & 0x20)>>5; - S = (*pdata & 0x10)>>4; - PartID = (*pdata & 0x0F); - // skip "REQUIRED" header - if(++pdata >= pdata_end){ TSK_DEBUG_ERROR("Too short"); goto bail; } - // check "OPTIONAL" headers - if(X){ - I = (*pdata & 0x80); - L = (*pdata & 0x40); - T = (*pdata & 0x20); - K = (*pdata & 0x10); - if(++pdata >= pdata_end){ TSK_DEBUG_ERROR("Too short"); goto bail; } - - if(I){ - if(*pdata & 0x80){ // M - // PictureID on 16bits - if((pdata += 2) >= pdata_end){ TSK_DEBUG_ERROR("Too short"); goto bail; } - } - else{ - // PictureID on 8bits - if(++pdata >= pdata_end){ TSK_DEBUG_ERROR("Too short"); goto bail; } - } - } - if(L){ - if(++pdata >= pdata_end){ TSK_DEBUG_ERROR("Too short"); goto bail; } - } - if(T || K){ - if(++pdata >= pdata_end){ TSK_DEBUG_ERROR("Too short"); goto bail; } - } - } - } - - in_size = (pdata_end - pdata); - - // Packet lost? - if(vp8->decoder.last_seq && (vp8->decoder.last_seq + 1) != rtp_hdr->seq_num){ - TSK_DEBUG_INFO("Packet lost, seq_num=%d", (vp8->decoder.last_seq + 1)); - } - vp8->decoder.last_seq = rtp_hdr->seq_num; - - if(in_size > xmax_size){ - TSK_DEBUG_ERROR("%u too big to contain valid encoded data. xmax_size=%u", in_size, xmax_size); - goto bail; - } - // start-accumulator - if(!vp8->decoder.accumulator){ - if(!(vp8->decoder.accumulator = tsk_calloc(in_size, sizeof(uint8_t)))){ - TSK_DEBUG_ERROR("Failed to allocated new buffer"); - goto bail; - } - vp8->decoder.accumulator_size = in_size; - } - if((vp8->decoder.accumulator_pos + in_size) >= xmax_size){ - TSK_DEBUG_ERROR("BufferOverflow"); - vp8->decoder.accumulator_pos = 0; - goto bail; - } - if((vp8->decoder.accumulator_pos + in_size) > vp8->decoder.accumulator_size){ - if(!(vp8->decoder.accumulator = tsk_realloc(vp8->decoder.accumulator, (vp8->decoder.accumulator_pos + in_size)))){ - TSK_DEBUG_ERROR("Failed to reallocated new buffer"); - vp8->decoder.accumulator_pos = 0; - vp8->decoder.accumulator_size = 0; - goto bail; - } - vp8->decoder.accumulator_size = (vp8->decoder.accumulator_pos + in_size); - } - - memcpy(&((uint8_t*)vp8->decoder.accumulator)[vp8->decoder.accumulator_pos], pdata, in_size); - vp8->decoder.accumulator_pos += in_size; - // end-accumulator - - // FIXME: First partition is decodable - // for better error handling we should decode it - // (vp8->decoder.last_PartID == 0 && vp8->decoder.last_S && S) => previous was "first decodable" and current is new one - if(rtp_hdr->marker /*|| (vp8->decoder.last_PartID == 0 && vp8->decoder.last_S)*/){ - vpx_image_t *img; - vpx_codec_iter_t iter = tsk_null; - vpx_codec_err_t vpx_ret; - const uint8_t* pay_ptr = (const uint8_t*)vp8->decoder.accumulator; - const tsk_size_t pay_size = vp8->decoder.accumulator_pos; - - // in all cases: reset accumulator - vp8->decoder.accumulator_pos = 0; - -#if 0 /* http://groups.google.com/a/webmproject.org/group/apps-devel/browse_thread/thread/c84438e70fe122fa/2dfc322018aa22a8 */ - // libvpx will crash very ofen when the frame is corrupted => for now we decided not to decode such frame - // according to the latest release there is a function to check if the frame - // is corrupted or not => To be checked - if(vp8->decoder.frame_corrupted){ - vp8->decoder.frame_corrupted = tsk_false; - goto bail; - } -#endif - - vpx_ret = vpx_codec_decode(&vp8->decoder.context, pay_ptr, pay_size, tsk_null, 0); - - if(vpx_ret != VPX_CODEC_OK){ - TSK_DEBUG_INFO("vpx_codec_decode failed with error =%s", vpx_codec_err_to_string(vpx_ret)); - if(TMEDIA_CODEC_VIDEO(self)->in.callback){ - TMEDIA_CODEC_VIDEO(self)->in.result.type = tmedia_video_decode_result_type_error; - TMEDIA_CODEC_VIDEO(self)->in.result.proto_hdr = proto_hdr; - TMEDIA_CODEC_VIDEO(self)->in.callback(&TMEDIA_CODEC_VIDEO(self)->in.result); - } - goto bail; - } - - // copy decoded data - ret = 0; - while((img = vpx_codec_get_frame(&vp8->decoder.context, &iter))){ - unsigned int plane, y; - tsk_size_t xsize; - - // update sizes - TMEDIA_CODEC_VIDEO(vp8)->in.width = img->d_w; - TMEDIA_CODEC_VIDEO(vp8)->in.height = img->d_h; - xsize = (TMEDIA_CODEC_VIDEO(vp8)->in.width * TMEDIA_CODEC_VIDEO(vp8)->in.height * 3) >> 1; - // allocate destination buffer - if(*out_max_size < xsize){ - if(!(*out_data = tsk_realloc(*out_data, xsize))){ - TSK_DEBUG_ERROR("Failed to allocate new buffer"); - vp8->decoder.accumulator_pos = 0; - *out_max_size = 0; - goto bail; - } - *out_max_size = xsize; - } - - // layout picture - for(plane=0; plane < 3; plane++) { - unsigned char *buf =img->planes[plane]; - for(y=0; yd_h >> (plane ? 1 : 0); y++) { - unsigned int w_count = img->d_w >> (plane ? 1 : 0); - if((ret + w_count) > *out_max_size){ - TSK_DEBUG_ERROR("BufferOverflow"); - ret = 0; - goto bail; - } - memcpy(((uint8_t*)*out_data) + ret, buf, w_count); - ret += w_count; - buf += img->stride[plane]; - } - } - } - } - -bail: - -// vp8->decoder.last_PartID = PartID; -// vp8->decoder.last_S = S; -// vp8->decoder.last_N = N; - return ret; -} - -static tsk_bool_t tdav_codec_vp8_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) -{ -#if 0 - if(tsk_striequals(att_name, "fmtp")){ - unsigned width, height, fps; - if(tmedia_parse_video_fmtp(att_value, TMEDIA_CODEC_VIDEO(codec)->pref_size, &width, &height, &fps)){ - TSK_DEBUG_ERROR("Failed to match fmtp=%s", att_value); - return tsk_false; - } - TMEDIA_CODEC_VIDEO(codec)->in.width = TMEDIA_CODEC_VIDEO(codec)->out.width = width; - TMEDIA_CODEC_VIDEO(codec)->in.height = TMEDIA_CODEC_VIDEO(codec)->out.height = height; - TMEDIA_CODEC_VIDEO(codec)->in.fps = TMEDIA_CODEC_VIDEO(codec)->out.fps = fps; - } - else -#endif - if(tsk_striequals(att_name, "imageattr")){ - unsigned in_width, in_height, out_width, out_height; - if(tmedia_parse_video_imageattr(att_value, TMEDIA_CODEC_VIDEO(codec)->pref_size, &in_width, &in_height, &out_width, &out_height) != 0){ - return tsk_false; - } - TMEDIA_CODEC_VIDEO(codec)->in.width = in_width; - TMEDIA_CODEC_VIDEO(codec)->in.height = in_height; - TMEDIA_CODEC_VIDEO(codec)->out.width = out_width; - TMEDIA_CODEC_VIDEO(codec)->out.height = out_height; - } - - return tsk_true; -} - -static char* tdav_codec_vp8_sdp_att_get(const tmedia_codec_t* codec, const char* att_name) -{ -#if 0 - if(tsk_striequals(att_name, "fmtp")){ - return tmedia_get_video_fmtp(TMEDIA_CODEC_VIDEO(codec)->pref_size); - } - else -#endif - if(tsk_striequals(att_name, "imageattr")){ - return tmedia_get_video_imageattr(TMEDIA_CODEC_VIDEO(codec)->pref_size, - TMEDIA_CODEC_VIDEO(codec)->in.width, TMEDIA_CODEC_VIDEO(codec)->in.height, TMEDIA_CODEC_VIDEO(codec)->out.width, TMEDIA_CODEC_VIDEO(codec)->out.height); - } - return tsk_null; -} - -/* ============ VP8 object definition ================= */ - -/* constructor */ -static tsk_object_t* tdav_codec_vp8_ctor(tsk_object_t * self, va_list * app) -{ - tdav_codec_vp8_t *vp8 = self; - if(vp8){ - /* init base: called by tmedia_codec_create() */ - /* init self */ - - } - return self; -} -/* destructor */ -static tsk_object_t* tdav_codec_vp8_dtor(tsk_object_t * self) -{ - tdav_codec_vp8_t *vp8 = self; - if(vp8){ - /* deinit base */ - tmedia_codec_video_deinit(vp8); - /* deinit self */ - if(vp8->encoder.rtp.ptr){ - TSK_FREE(vp8->encoder.rtp.ptr); - vp8->encoder.rtp.size = 0; - } - if(vp8->encoder.initialized){ - vpx_codec_destroy(&vp8->encoder.context); - vp8->encoder.initialized = tsk_false; - } - if(vp8->decoder.initialized){ - vpx_codec_destroy(&vp8->decoder.context); - vp8->decoder.initialized = tsk_false; - } - if(vp8->decoder.accumulator){ - TSK_FREE(vp8->decoder.accumulator); - vp8->decoder.accumulator_pos = 0; - } - } - - return self; -} -/* object definition */ -static const tsk_object_def_t tdav_codec_vp8_def_s = -{ - sizeof(tdav_codec_vp8_t), - tdav_codec_vp8_ctor, - tdav_codec_vp8_dtor, - tmedia_codec_cmp, -}; -/* plugin definition*/ -static const tmedia_codec_plugin_def_t tdav_codec_vp8_plugin_def_s = -{ - &tdav_codec_vp8_def_s, - - tmedia_video, - tmedia_codec_id_vp8, - "VP8", - "VP8 codec", - TMEDIA_CODEC_FORMAT_VP8, - tsk_true, - 90000, // rate - - /* audio */ - { 0 }, - - /* video (defaul width,height,fps) */ - {176, 144, 15}, - - tdav_codec_vp8_set, - tdav_codec_vp8_open, - tdav_codec_vp8_close, - tdav_codec_vp8_encode, - tdav_codec_vp8_decode, - tdav_codec_vp8_sdp_att_match, - tdav_codec_vp8_sdp_att_get -}; -const tmedia_codec_plugin_def_t *tdav_codec_vp8_plugin_def_t = &tdav_codec_vp8_plugin_def_s; - -/* ============ Internal functions ================= */ - -int tdav_codec_vp8_open_encoder(tdav_codec_vp8_t* self) -{ - vpx_codec_err_t vpx_ret; - vpx_enc_frame_flags_t enc_flags; - - if(self->encoder.initialized){ - TSK_DEBUG_ERROR("VP8 encoder already inialized"); - return -1; - } - - if((vpx_ret = vpx_codec_enc_config_default(vp8_interface_enc, &self->encoder.cfg, 0)) != VPX_CODEC_OK){ - TSK_DEBUG_ERROR("vpx_codec_enc_config_default failed with error =%s", vpx_codec_err_to_string(vpx_ret)); - return -2; - } - self->encoder.cfg.g_timebase.num = 1; - self->encoder.cfg.g_timebase.den = TMEDIA_CODEC_VIDEO(self)->out.fps; - self->encoder.cfg.rc_target_bitrate = self->encoder.target_bitrate = (TMEDIA_CODEC_VIDEO(self)->out.width * TMEDIA_CODEC_VIDEO(self)->out.height * 256 / 352 / 288); - self->encoder.cfg.rc_end_usage = VPX_CBR; - self->encoder.cfg.g_w = (self->encoder.rotation == 90 || self->encoder.rotation == 270) ? TMEDIA_CODEC_VIDEO(self)->out.height : TMEDIA_CODEC_VIDEO(self)->out.width; - self->encoder.cfg.g_h = (self->encoder.rotation == 90 || self->encoder.rotation == 270) ? TMEDIA_CODEC_VIDEO(self)->out.width : TMEDIA_CODEC_VIDEO(self)->out.height; - self->encoder.cfg.kf_mode = VPX_KF_AUTO; - self->encoder.cfg.kf_min_dist = self->encoder.cfg.kf_max_dist = (TDAV_VP8_GOP_SIZE_IN_SECONDS * TMEDIA_CODEC_VIDEO(self)->out.fps); - self->encoder.cfg.g_error_resilient = 1; - self->encoder.cfg.g_lag_in_frames = 0; -#if TDAV_UNDER_WINDOWS - { - SYSTEM_INFO SystemInfo; - GetSystemInfo(&SystemInfo); - self->encoder.cfg.g_threads = SystemInfo.dwNumberOfProcessors; - } -#endif - self->encoder.cfg.g_pass = VPX_RC_ONE_PASS; - self->encoder.cfg.rc_min_quantizer = 0;//TSK_CLAMP(self->encoder.cfg.rc_min_quantizer, 10, self->encoder.cfg.rc_max_quantizer); - self->encoder.cfg.rc_max_quantizer = 63;//TSK_CLAMP(self->encoder.cfg.rc_min_quantizer, 51, self->encoder.cfg.rc_max_quantizer); - //self->encoder.cfg.rc_resize_allowed = 0; - self->encoder.cfg.g_profile = 0; - - enc_flags = 0; //VPX_EFLAG_XXX - - if((vpx_ret = vpx_codec_enc_init(&self->encoder.context, vp8_interface_enc, &self->encoder.cfg, enc_flags)) != VPX_CODEC_OK){ - TSK_DEBUG_ERROR("vpx_codec_enc_init failed with error =%s", vpx_codec_err_to_string(vpx_ret)); - return -3; - } - self->encoder.pic_id = (rand() ^ rand()) % 0x7FFF; - self->encoder.initialized = tsk_true; - - //vpx_codec_control(&self->encoder.context, VP8E_SET_CPUUSED, 0); - //vpx_codec_control(&self->encoder.context, VP8E_SET_SHARPNESS, 7); - //vpx_codec_control(&self->encoder.context, VP8E_SET_ENABLEAUTOALTREF, 1); - - return 0; -} - -int tdav_codec_vp8_open_decoder(tdav_codec_vp8_t* self) -{ - vpx_codec_err_t vpx_ret; - vpx_codec_caps_t dec_caps; - vpx_codec_flags_t dec_flags = 0; - static vp8_postproc_cfg_t __pp = { VP8_DEBLOCK | VP8_DEMACROBLOCK, 4, 0}; - - if(self->decoder.initialized){ - TSK_DEBUG_ERROR("VP8 decoder already initialized"); - return -1; - } - - self->decoder.cfg.w = TMEDIA_CODEC_VIDEO(self)->out.width; - self->decoder.cfg.h = TMEDIA_CODEC_VIDEO(self)->out.height; -#if TDAV_UNDER_WINDOWS - { - SYSTEM_INFO SystemInfo; - GetSystemInfo(&SystemInfo); - self->decoder.cfg.threads = SystemInfo.dwNumberOfProcessors; - } -#endif - - dec_caps = vpx_codec_get_caps(&vpx_codec_vp8_dx_algo); - if(dec_caps & VPX_CODEC_CAP_POSTPROC){ - dec_flags |= VPX_CODEC_USE_POSTPROC; - } - //--if(dec_caps & VPX_CODEC_CAP_ERROR_CONCEALMENT){ - //-- dec_flags |= VPX_CODEC_USE_ERROR_CONCEALMENT; - //--} - - if((vpx_ret = vpx_codec_dec_init(&self->decoder.context, vp8_interface_dec, &self->decoder.cfg, dec_flags)) != VPX_CODEC_OK){ - TSK_DEBUG_ERROR("vpx_codec_dec_init failed with error =%s", vpx_codec_err_to_string(vpx_ret)); - return -4; - } - - if((vpx_ret = vpx_codec_control(&self->decoder.context, VP8_SET_POSTPROC, &__pp))){ - TSK_DEBUG_WARN("vpx_codec_dec_init failed with error =%s", vpx_codec_err_to_string(vpx_ret)); - } - self->decoder.initialized = tsk_true; - - return 0; -} - -int tdav_codec_vp8_close_encoder(tdav_codec_vp8_t* self) -{ - if(self->encoder.initialized){ - vpx_codec_destroy(&self->encoder.context); - self->encoder.initialized = tsk_false; - } - return 0; -} - -int tdav_codec_vp8_close_decoder(tdav_codec_vp8_t* self) -{ - if(self->decoder.initialized){ - vpx_codec_destroy(&self->decoder.context); - self->decoder.initialized = tsk_false; - } - - return 0; -} - -/* ============ VP8 RTP packetizer/depacketizer ================= */ - - -static void tdav_codec_vp8_encap(tdav_codec_vp8_t* self, const vpx_codec_cx_pkt_t *pkt) -{ - tsk_bool_t non_ref, is_keyframe, part_start; - uint8_t *frame_ptr; - uint32_t part_size, part_ID, pkt_size, index; - - if(!self || !pkt || !pkt->data.frame.buf || !pkt->data.frame.sz){ - TSK_DEBUG_ERROR("Invalid parameter"); - return; - } - - index = 0; - frame_ptr = pkt->data.frame.buf ; - pkt_size = pkt->data.frame.sz; - non_ref = (pkt->data.frame.flags & VPX_FRAME_IS_DROPPABLE); - is_keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY); - - // check P bit validity - if((is_keyframe && (*frame_ptr & 0x01)) || (!is_keyframe && !(*frame_ptr & 0x01))){// 4.3. VP8 Payload Header - TSK_DEBUG_ERROR("Invalid payload header"); - return; - } - - // first partition (contains modes and motion vectors) - part_ID = 0; // The first VP8 partition(containing modes and motion vectors) MUST be labeled with PartID = 0 - part_size = (frame_ptr[2] << 16) | (frame_ptr[1] << 8) | frame_ptr[0]; - part_size = (part_size >> 5) & 0x7FFFF; - if(part_size > pkt_size){ - TSK_DEBUG_ERROR("part_size is > pkt_size(%u,%u)", part_size, pkt_size); - return; - } - - part_start = tsk_true; - -#if 0 // The first partition could be as big as 10kb for HD 720p video frames => we have to split it - tdav_codec_vp8_rtp_callback(self, &frame_ptr[index], part_size, part_ID, part_start, non_ref, (index + part_size)==pkt_size); - index += part_size; -#else - // first,first,....partitions (or fragment if part_size > TDAV_VP8_RTP_PAYLOAD_MAX_SIZE) - while(index TDAV_VP8_RTP_PAYLOAD_MAX_SIZE) - // FIXME: low FEC - part_start = tsk_true; - while(indexencoder.rtp.size < (size + paydesc_and_hdr_size)){ - if(!(self->encoder.rtp.ptr = tsk_realloc(self->encoder.rtp.ptr, (size + paydesc_and_hdr_size)))){ - TSK_DEBUG_ERROR("Failed to allocate new buffer"); - return; - } - self->encoder.rtp.size = (size + paydesc_and_hdr_size); - } - memcpy((self->encoder.rtp.ptr + paydesc_and_hdr_size), data, size); - - /* VP8 Payload Descriptor */ - // |X|R|N|S|PartID| - self->encoder.rtp.ptr[0] = (partID & 0x0F) // PartID - | ((part_start << 4) & 0x10)// S - | ((non_ref << 5) & 0x20) // N - // R = 0 -#if TDAV_VP8_DISABLE_EXTENSION - | (0x00) // X=0 -#else - | (0x80) // X=1 -#endif - ; - -#if !TDAV_VP8_DISABLE_EXTENSION - // X: |I|L|T|K| RSV | - self->encoder.rtp.ptr[1] = 0x80; // I = 1, L = 0, T = 0, K = 0, RSV = 0 - // I: |M| PictureID | - self->encoder.rtp.ptr[2] = (0x80 | (self->encoder.pic_id >> 9)); // M = 1 (PictureID on 15 bits) - self->encoder.rtp.ptr[3] = (self->encoder.pic_id & 0xFF); -#endif - - /* 4.2. VP8 Payload Header */ - if(has_hdr){ - // already part of the encoded stream - } - - // Send data over the network - if(TMEDIA_CODEC_VIDEO(self)->out.callback){ - TMEDIA_CODEC_VIDEO(self)->out.result.buffer.ptr = self->encoder.rtp.ptr; - TMEDIA_CODEC_VIDEO(self)->out.result.buffer.size = (size + TDAV_VP8_PAY_DESC_SIZE); - TMEDIA_CODEC_VIDEO(self)->out.result.duration = (3003* (30/TMEDIA_CODEC_VIDEO(self)->out.fps)); - TMEDIA_CODEC_VIDEO(self)->out.result.last_chunck = last; - TMEDIA_CODEC_VIDEO(self)->out.callback(&TMEDIA_CODEC_VIDEO(self)->out.result); - } -} - -#endif /* HAVE_LIBVPX */ +/* +* Copyright (C) 2011 Doubango Telecom +* +* Contact: Mamadou Diop +* +* This file is part of Open Source Doubango Framework. +* +* DOUBANGO 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 3 of the License, or +* (at your option) any later version. +* +* DOUBANGO 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 DOUBANGO. +* +*/ + +/**@file tdav_codec_vp8.c + * @brief VP8 codec + * The RTP packetizer/depacketizer follows draft-ietf-payload-vp8 and draft-bankoski-vp8-bitstream-05 + * Google's VP8 (http://www.webmproject.org/) encoder/decoder + * + * @author Mamadou Diop + * + */ +#include "tinydav/codecs/vpx/tdav_codec_vp8.h" + +#if HAVE_LIBVPX + +#if TDAV_UNDER_WINDOWS +# include +#endif + +#include "tinyrtp/rtp/trtp_rtp_packet.h" + +#include "tinymedia/tmedia_params.h" + +#include "tsk_string.h" +#include "tsk_memory.h" +#include "tsk_time.h" +#include "tsk_debug.h" + +#if !defined(TDAV_VP8_DISABLE_EXTENSION) +# define TDAV_VP8_DISABLE_EXTENSION 0 /* Set X fied value to zero */ +#endif + +#if TDAV_VP8_DISABLE_EXTENSION +# define TDAV_VP8_PAY_DESC_SIZE 1 +#else +# define TDAV_VP8_PAY_DESC_SIZE 4 +#endif +#define TDAV_SYSTEM_CORES_COUNT 0 +#define TDAV_VP8_GOP_SIZE_IN_SECONDS 60 +#define TDAV_VP8_RTP_PAYLOAD_MAX_SIZE 1050 +#if !defined(TDAV_VP8_MAX_BANDWIDTH_KB) +# define TDAV_VP8_MAX_BANDWIDTH_KB 6000 +#endif +#if !defined(TDAV_VP8_MIN_BANDWIDTH_KB) +# define TDAV_VP8_MIN_BANDWIDTH_KB 100 +#endif + +/* VP8 codec */ +typedef struct tdav_codec_vp8_s +{ + TMEDIA_DECLARE_CODEC_VIDEO; + + // Encoder + struct{ + vpx_codec_enc_cfg_t cfg; + tsk_bool_t initialized; + vpx_codec_pts_t pts; + vpx_codec_ctx_t context; + unsigned pic_id:15; + uint64_t frame_count; + tsk_bool_t force_idr; + uint32_t target_bitrate; + int rotation; + + struct{ + uint8_t* ptr; + tsk_size_t size; + } rtp; + } encoder; + + // decoder + struct{ + vpx_codec_dec_cfg_t cfg; + unsigned initialized:1; + vpx_codec_ctx_t context; + void* accumulator; + tsk_size_t accumulator_pos; + tsk_size_t accumulator_size; + uint16_t last_seq; + uint32_t last_timestamp; + tsk_bool_t idr; + } decoder; +} +tdav_codec_vp8_t; + +#define vp8_interface_enc (vpx_codec_vp8_cx()) +#define vp8_interface_dec (vpx_codec_vp8_dx()) + +static int tdav_codec_vp8_open_encoder(tdav_codec_vp8_t* self); +static int tdav_codec_vp8_open_decoder(tdav_codec_vp8_t* self); +static int tdav_codec_vp8_close_encoder(tdav_codec_vp8_t* self); +static int tdav_codec_vp8_close_decoder(tdav_codec_vp8_t* self); + +static void tdav_codec_vp8_encap(tdav_codec_vp8_t* self, const vpx_codec_cx_pkt_t *pkt); +static void tdav_codec_vp8_rtp_callback(tdav_codec_vp8_t *self, const void *data, tsk_size_t size, uint32_t partID, tsk_bool_t part_start, tsk_bool_t non_ref, tsk_bool_t last); + +/* ============ VP8 Plugin interface ================= */ + +static int tdav_codec_vp8_set(tmedia_codec_t* self, const tmedia_param_t* param) +{ + tdav_codec_vp8_t* vp8 = (tdav_codec_vp8_t*)self; + vpx_codec_err_t vpx_ret; + + if(!vp8->encoder.initialized){ + TSK_DEBUG_ERROR("Codec not initialized"); + return -1; + } + if(param->value_type == tmedia_pvt_int32){ + if(tsk_striequals(param->key, "action")){ + tmedia_codec_action_t action = (tmedia_codec_action_t)TSK_TO_INT32((uint8_t*)param->value); + tsk_bool_t reconf = tsk_false; + switch(action){ + case tmedia_codec_action_encode_idr: + { + vp8->encoder.force_idr = tsk_true; + break; + } + case tmedia_codec_action_bw_down: + { + vp8->encoder.cfg.rc_target_bitrate = ((vp8->encoder.cfg.rc_target_bitrate << 1) / 3); + TSK_DEBUG_INFO("New target bitrate = %d kbps", vp8->encoder.cfg.rc_target_bitrate); + reconf = tsk_true; + break; + } + case tmedia_codec_action_bw_up: + { + vp8->encoder.cfg.rc_target_bitrate = ((vp8->encoder.cfg.rc_target_bitrate * 3) >> 1); + TSK_DEBUG_INFO("New target bitrate = %d kbps", vp8->encoder.cfg.rc_target_bitrate); + reconf = tsk_true; + break; + } + } + + if(reconf){ + if((vpx_ret = vpx_codec_enc_config_set(&vp8->encoder.context, &vp8->encoder.cfg)) != VPX_CODEC_OK){ + TSK_DEBUG_ERROR("vpx_codec_enc_config_set failed with error =%s", vpx_codec_err_to_string(vpx_ret)); + } + } + } + else if(tsk_striequals(param->key, "rotation")){ + // IMPORTANT: changing resolution requires at least libvpx v1.1.0 "Eider" + int rotation = *((int32_t*)param->value); + if(vp8->encoder.rotation != rotation){ + vp8->encoder.rotation = rotation; + if(vp8->encoder.initialized){ +#if 1 + vp8->encoder.cfg.g_w = (rotation == 90 || rotation == 270) ? TMEDIA_CODEC_VIDEO(vp8)->out.height : TMEDIA_CODEC_VIDEO(vp8)->out.width; + vp8->encoder.cfg.g_h = (rotation == 90 || rotation == 270) ? TMEDIA_CODEC_VIDEO(vp8)->out.width : TMEDIA_CODEC_VIDEO(vp8)->out.height; + if((vpx_ret = vpx_codec_enc_config_set(&vp8->encoder.context, &vp8->encoder.cfg)) != VPX_CODEC_OK){ + TSK_DEBUG_ERROR("vpx_codec_enc_config_set failed with error =%s", vpx_codec_err_to_string(vpx_ret)); + return -1; + } +#else + int ret; + if((ret = tdav_codec_vp8_close_encoder(vp8))){ + return ret; + } + if((ret = tdav_codec_vp8_open_encoder(vp8))){ + return ret; + } +#endif + } + return 0; + } + } + } + return -1; +} + +static int tdav_codec_vp8_open(tmedia_codec_t* self) +{ + tdav_codec_vp8_t* vp8 = (tdav_codec_vp8_t*)self; + int ret; + + if(!vp8){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* the caller (base class) already checked that the codec is not opened */ + + + // Encoder + if((ret = tdav_codec_vp8_open_encoder(vp8))){ + return ret; + } + + // Decoder + if((ret = tdav_codec_vp8_open_decoder(vp8))){ + return ret; + } + + return ret; +} + +static int tdav_codec_vp8_close(tmedia_codec_t* self) +{ + tdav_codec_vp8_t* vp8 = (tdav_codec_vp8_t*)self; + + if(!vp8){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + tdav_codec_vp8_close_encoder(vp8); + tdav_codec_vp8_close_decoder(vp8); + + return 0; +} + +static tsk_size_t tdav_codec_vp8_encode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size) +{ + tdav_codec_vp8_t* vp8 = (tdav_codec_vp8_t*)self; + vpx_enc_frame_flags_t flags = 0; + vpx_codec_err_t vpx_ret; + const vpx_codec_cx_pkt_t *pkt; + vpx_codec_iter_t iter = tsk_null; + vpx_image_t image; + + if(!vp8 || !in_data || !in_size){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + if(in_size != (vp8->encoder.context.config.enc->g_w * vp8->encoder.context.config.enc->g_h * 3)>>1){ + TSK_DEBUG_ERROR("Invalid size"); + return 0; + } + + // wrap yuv420 buffer + if(!vpx_img_wrap(&image, VPX_IMG_FMT_I420, vp8->encoder.context.config.enc->g_w, vp8->encoder.context.config.enc->g_h, 1, (unsigned char*)in_data)){ + TSK_DEBUG_ERROR("vpx_img_wrap failed"); + return 0; + } + + // encode data + ++vp8->encoder.pts; + if(vp8->encoder.force_idr){ + flags |= VPX_EFLAG_FORCE_KF; + vp8->encoder.force_idr = tsk_false; + } + if((vpx_ret = vpx_codec_encode(&vp8->encoder.context, &image, vp8->encoder.pts, 1, flags, VPX_DL_REALTIME)) != VPX_CODEC_OK){ + TSK_DEBUG_ERROR("vpx_codec_encode failed with error =%s", vpx_codec_err_to_string(vpx_ret)); + vpx_img_free(&image); + return 0; + } + + ++vp8->encoder.frame_count; + ++vp8->encoder.pic_id; + + while((pkt = vpx_codec_get_cx_data(&vp8->encoder.context, &iter))){ + switch(pkt->kind){ + case VPX_CODEC_CX_FRAME_PKT: + { + tdav_codec_vp8_encap(vp8, pkt); + break; + } + default: + case VPX_CODEC_STATS_PKT: /**< Two-pass statistics for this frame */ + case VPX_CODEC_PSNR_PKT: /**< PSNR statistics for this frame */ + case VPX_CODEC_CUSTOM_PKT: /**< Algorithm extensions */ + { + TSK_DEBUG_INFO("pkt->kind=%d not supported", (int)pkt->kind); + break; + } + } + } + + vpx_img_free(&image); + return 0; +} + +static tsk_size_t tdav_codec_vp8_decode(tmedia_codec_t* self, const void* in_data, tsk_size_t in_size, void** out_data, tsk_size_t* out_max_size, const tsk_object_t* proto_hdr) +{ + tdav_codec_vp8_t* vp8 = (tdav_codec_vp8_t*)self; + const trtp_rtp_header_t* rtp_hdr = proto_hdr; + const uint8_t* pdata = in_data; + const uint8_t* pdata_end = (pdata + in_size); + tsk_size_t ret = 0; + static const tsk_size_t xmax_size = (1920 * 1080 * 3) >> 3; + uint8_t S, PartID; + + if(!self || !in_data || in_size<1 || !out_data || !vp8->decoder.initialized){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + + { /* 4.2. VP8 Payload Descriptor */ + uint8_t X, R, N, I, L, T, K;//FIXME: store + + X = (*pdata & 0x80)>>7; + R = (*pdata & 0x40)>>6; + if(R){ + TSK_DEBUG_ERROR("R<>0"); + return 0; + } + N = (*pdata & 0x20)>>5; + S = (*pdata & 0x10)>>4; + PartID = (*pdata & 0x0F); + // skip "REQUIRED" header + if(++pdata >= pdata_end){ + TSK_DEBUG_ERROR("Too short"); goto bail; + } + // check "OPTIONAL" headers + if(X){ + I = (*pdata & 0x80); + L = (*pdata & 0x40); + T = (*pdata & 0x20); + K = (*pdata & 0x10); + if(++pdata >= pdata_end){ + TSK_DEBUG_ERROR("Too short"); goto bail; + } + + if(I){ + if(*pdata & 0x80){ // M + // PictureID on 16bits + if((pdata += 2) >= pdata_end){ + TSK_DEBUG_ERROR("Too short"); goto bail; + } + } + else{ + // PictureID on 8bits + if(++pdata >= pdata_end){ + TSK_DEBUG_ERROR("Too short"); goto bail; + } + } + } + if(L){ + if(++pdata >= pdata_end){ + TSK_DEBUG_ERROR("Too short"); goto bail; + } + } + if(T || K){ + if(++pdata >= pdata_end){ + TSK_DEBUG_ERROR("Too short"); goto bail; + } + } + } + } + + in_size = (pdata_end - pdata); + + // New frame ? + if(vp8->decoder.last_timestamp != rtp_hdr->timestamp){ + /* 4.3. VP8 Payload Header + Note that the header is present only in packets + which have the S bit equal to one and the PartID equal to zero in the + payload descriptor. Subsequent packets for the same frame do not + carry the payload header. + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |Size0|H| VER |P| + +-+-+-+-+-+-+-+-+ + | Size1 | + +-+-+-+-+-+-+-+-+ + | Size2 | + +-+-+-+-+-+-+-+-+ + | Bytes 4..N of | + | VP8 payload | + : : + +-+-+-+-+-+-+-+-+ + | OPTIONAL RTP | + | padding | + : : + +-+-+-+-+-+-+-+-+ + P: Inverse key frame flag. When set to 0 the current frame is a key + frame. When set to 1 the current frame is an interframe. Defined + in [RFC6386] + */ + if(PartID == 0 && S == 1 && in_size > 0){ + vp8->decoder.idr = !(*pdata & 0x01); + } + else{ + vp8->decoder.idr = tsk_false; + } + vp8->decoder.last_timestamp = rtp_hdr->timestamp; + } + + // Packet lost? + if(vp8->decoder.last_seq && (vp8->decoder.last_seq + 1) != rtp_hdr->seq_num){ + TSK_DEBUG_INFO("Packet lost, seq_num=%d", (vp8->decoder.last_seq + 1)); + } + vp8->decoder.last_seq = rtp_hdr->seq_num; + + if(in_size > xmax_size){ + TSK_DEBUG_ERROR("%u too big to contain valid encoded data. xmax_size=%u", in_size, xmax_size); + goto bail; + } + // start-accumulator + if(!vp8->decoder.accumulator){ + if(!(vp8->decoder.accumulator = tsk_calloc(in_size, sizeof(uint8_t)))){ + TSK_DEBUG_ERROR("Failed to allocated new buffer"); + goto bail; + } + vp8->decoder.accumulator_size = in_size; + } + if((vp8->decoder.accumulator_pos + in_size) >= xmax_size){ + TSK_DEBUG_ERROR("BufferOverflow"); + vp8->decoder.accumulator_pos = 0; + goto bail; + } + if((vp8->decoder.accumulator_pos + in_size) > vp8->decoder.accumulator_size){ + if(!(vp8->decoder.accumulator = tsk_realloc(vp8->decoder.accumulator, (vp8->decoder.accumulator_pos + in_size)))){ + TSK_DEBUG_ERROR("Failed to reallocated new buffer"); + vp8->decoder.accumulator_pos = 0; + vp8->decoder.accumulator_size = 0; + goto bail; + } + vp8->decoder.accumulator_size = (vp8->decoder.accumulator_pos + in_size); + } + + memcpy(&((uint8_t*)vp8->decoder.accumulator)[vp8->decoder.accumulator_pos], pdata, in_size); + vp8->decoder.accumulator_pos += in_size; + // end-accumulator + + // FIXME: First partition is decodable + // for better error handling we should decode it + // (vp8->decoder.last_PartID == 0 && vp8->decoder.last_S && S) => previous was "first decodable" and current is new one + if(rtp_hdr->marker /*|| (vp8->decoder.last_PartID == 0 && vp8->decoder.last_S)*/){ + vpx_image_t *img; + vpx_codec_iter_t iter = tsk_null; + vpx_codec_err_t vpx_ret; + const uint8_t* pay_ptr = (const uint8_t*)vp8->decoder.accumulator; + const tsk_size_t pay_size = vp8->decoder.accumulator_pos; + + // in all cases: reset accumulator + vp8->decoder.accumulator_pos = 0; + +#if 0 /* http://groups.google.com/a/webmproject.org/group/apps-devel/browse_thread/thread/c84438e70fe122fa/2dfc322018aa22a8 */ + // libvpx will crash very ofen when the frame is corrupted => for now we decided not to decode such frame + // according to the latest release there is a function to check if the frame + // is corrupted or not => To be checked + if(vp8->decoder.frame_corrupted){ + vp8->decoder.frame_corrupted = tsk_false; + goto bail; + } +#endif + + vpx_ret = vpx_codec_decode(&vp8->decoder.context, pay_ptr, pay_size, tsk_null, 0); + + if(vpx_ret != VPX_CODEC_OK){ + TSK_DEBUG_INFO("vpx_codec_decode failed with error =%s", vpx_codec_err_to_string(vpx_ret)); + if(TMEDIA_CODEC_VIDEO(self)->in.callback){ + TMEDIA_CODEC_VIDEO(self)->in.result.type = tmedia_video_decode_result_type_error; + TMEDIA_CODEC_VIDEO(self)->in.result.proto_hdr = proto_hdr; + TMEDIA_CODEC_VIDEO(self)->in.callback(&TMEDIA_CODEC_VIDEO(self)->in.result); + } + goto bail; + } + else if(vp8->decoder.idr){ + TSK_DEBUG_INFO("Decoded VP8 IDR"); + if(TMEDIA_CODEC_VIDEO(self)->in.callback){ + TMEDIA_CODEC_VIDEO(self)->in.result.type = tmedia_video_decode_result_type_idr; + TMEDIA_CODEC_VIDEO(self)->in.result.proto_hdr = proto_hdr; + TMEDIA_CODEC_VIDEO(self)->in.callback(&TMEDIA_CODEC_VIDEO(self)->in.result); + } + } + + // copy decoded data + ret = 0; + while((img = vpx_codec_get_frame(&vp8->decoder.context, &iter))){ + unsigned int plane, y; + tsk_size_t xsize; + + // update sizes + TMEDIA_CODEC_VIDEO(vp8)->in.width = img->d_w; + TMEDIA_CODEC_VIDEO(vp8)->in.height = img->d_h; + xsize = (TMEDIA_CODEC_VIDEO(vp8)->in.width * TMEDIA_CODEC_VIDEO(vp8)->in.height * 3) >> 1; + // allocate destination buffer + if(*out_max_size < xsize){ + if(!(*out_data = tsk_realloc(*out_data, xsize))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + vp8->decoder.accumulator_pos = 0; + *out_max_size = 0; + goto bail; + } + *out_max_size = xsize; + } + + // layout picture + for(plane=0; plane < 3; plane++) { + unsigned char *buf =img->planes[plane]; + for(y=0; yd_h >> (plane ? 1 : 0); y++) { + unsigned int w_count = img->d_w >> (plane ? 1 : 0); + if((ret + w_count) > *out_max_size){ + TSK_DEBUG_ERROR("BufferOverflow"); + ret = 0; + goto bail; + } + memcpy(((uint8_t*)*out_data) + ret, buf, w_count); + ret += w_count; + buf += img->stride[plane]; + } + } + } + } + +bail: + +// vp8->decoder.last_PartID = PartID; +// vp8->decoder.last_S = S; +// vp8->decoder.last_N = N; + return ret; +} + +static tsk_bool_t tdav_codec_vp8_sdp_att_match(const tmedia_codec_t* codec, const char* att_name, const char* att_value) +{ +#if 0 + if(tsk_striequals(att_name, "fmtp")){ + unsigned width, height, fps; + if(tmedia_parse_video_fmtp(att_value, TMEDIA_CODEC_VIDEO(codec)->pref_size, &width, &height, &fps)){ + TSK_DEBUG_ERROR("Failed to match fmtp=%s", att_value); + return tsk_false; + } + TMEDIA_CODEC_VIDEO(codec)->in.width = TMEDIA_CODEC_VIDEO(codec)->out.width = width; + TMEDIA_CODEC_VIDEO(codec)->in.height = TMEDIA_CODEC_VIDEO(codec)->out.height = height; + TMEDIA_CODEC_VIDEO(codec)->in.fps = TMEDIA_CODEC_VIDEO(codec)->out.fps = fps; + } + else +#endif + if(tsk_striequals(att_name, "imageattr")){ + unsigned in_width, in_height, out_width, out_height; + if(tmedia_parse_video_imageattr(att_value, TMEDIA_CODEC_VIDEO(codec)->pref_size, &in_width, &in_height, &out_width, &out_height) != 0){ + return tsk_false; + } + TMEDIA_CODEC_VIDEO(codec)->in.width = in_width; + TMEDIA_CODEC_VIDEO(codec)->in.height = in_height; + TMEDIA_CODEC_VIDEO(codec)->out.width = out_width; + TMEDIA_CODEC_VIDEO(codec)->out.height = out_height; + } + + return tsk_true; +} + +static char* tdav_codec_vp8_sdp_att_get(const tmedia_codec_t* codec, const char* att_name) +{ +#if 0 + if(tsk_striequals(att_name, "fmtp")){ + return tmedia_get_video_fmtp(TMEDIA_CODEC_VIDEO(codec)->pref_size); + } + else +#endif + if(tsk_striequals(att_name, "imageattr")){ + return tmedia_get_video_imageattr(TMEDIA_CODEC_VIDEO(codec)->pref_size, + TMEDIA_CODEC_VIDEO(codec)->in.width, TMEDIA_CODEC_VIDEO(codec)->in.height, TMEDIA_CODEC_VIDEO(codec)->out.width, TMEDIA_CODEC_VIDEO(codec)->out.height); + } + return tsk_null; +} + +/* ============ VP8 object definition ================= */ + +/* constructor */ +static tsk_object_t* tdav_codec_vp8_ctor(tsk_object_t * self, va_list * app) +{ + tdav_codec_vp8_t *vp8 = self; + if(vp8){ + /* init base: called by tmedia_codec_create() */ + /* init self */ + + } + return self; +} +/* destructor */ +static tsk_object_t* tdav_codec_vp8_dtor(tsk_object_t * self) +{ + tdav_codec_vp8_t *vp8 = self; + TSK_DEBUG_INFO("*** tdav_codec_vp8_dtor destroyed ***"); + if(vp8){ + /* deinit base */ + tmedia_codec_video_deinit(vp8); + /* deinit self */ + if(vp8->encoder.rtp.ptr){ + TSK_FREE(vp8->encoder.rtp.ptr); + vp8->encoder.rtp.size = 0; + } + if(vp8->encoder.initialized){ + vpx_codec_destroy(&vp8->encoder.context); + vp8->encoder.initialized = tsk_false; + } + if(vp8->decoder.initialized){ + vpx_codec_destroy(&vp8->decoder.context); + vp8->decoder.initialized = tsk_false; + } + if(vp8->decoder.accumulator){ + TSK_FREE(vp8->decoder.accumulator); + vp8->decoder.accumulator_pos = 0; + } + } + + return self; +} +/* object definition */ +static const tsk_object_def_t tdav_codec_vp8_def_s = +{ + sizeof(tdav_codec_vp8_t), + tdav_codec_vp8_ctor, + tdav_codec_vp8_dtor, + tmedia_codec_cmp, +}; +/* plugin definition*/ +static const tmedia_codec_plugin_def_t tdav_codec_vp8_plugin_def_s = +{ + &tdav_codec_vp8_def_s, + + tmedia_video, + tmedia_codec_id_vp8, + "VP8", + "VP8 codec", + TMEDIA_CODEC_FORMAT_VP8, + tsk_true, + 90000, // rate + + /* audio */ + { 0 }, + + /* video (defaul width,height,fps) */ + {176, 144, 15}, + + tdav_codec_vp8_set, + tdav_codec_vp8_open, + tdav_codec_vp8_close, + tdav_codec_vp8_encode, + tdav_codec_vp8_decode, + tdav_codec_vp8_sdp_att_match, + tdav_codec_vp8_sdp_att_get +}; +const tmedia_codec_plugin_def_t *tdav_codec_vp8_plugin_def_t = &tdav_codec_vp8_plugin_def_s; + +/* ============ Internal functions ================= */ + +int tdav_codec_vp8_open_encoder(tdav_codec_vp8_t* self) +{ + vpx_codec_err_t vpx_ret; + vpx_enc_frame_flags_t enc_flags; + + if(self->encoder.initialized){ + TSK_DEBUG_ERROR("VP8 encoder already inialized"); + return -1; + } + + if((vpx_ret = vpx_codec_enc_config_default(vp8_interface_enc, &self->encoder.cfg, 0)) != VPX_CODEC_OK){ + TSK_DEBUG_ERROR("vpx_codec_enc_config_default failed with error =%s", vpx_codec_err_to_string(vpx_ret)); + return -2; + } + self->encoder.cfg.g_timebase.num = 1; + self->encoder.cfg.g_timebase.den = TMEDIA_CODEC_VIDEO(self)->out.fps; + self->encoder.cfg.rc_target_bitrate = self->encoder.target_bitrate = (TMEDIA_CODEC_VIDEO(self)->out.width * TMEDIA_CODEC_VIDEO(self)->out.height * 256 / 352 / 288); + self->encoder.cfg.g_w = (self->encoder.rotation == 90 || self->encoder.rotation == 270) ? TMEDIA_CODEC_VIDEO(self)->out.height : TMEDIA_CODEC_VIDEO(self)->out.width; + self->encoder.cfg.g_h = (self->encoder.rotation == 90 || self->encoder.rotation == 270) ? TMEDIA_CODEC_VIDEO(self)->out.width : TMEDIA_CODEC_VIDEO(self)->out.height; + self->encoder.cfg.kf_mode = VPX_KF_AUTO; + /*self->encoder.cfg.kf_min_dist =*/ self->encoder.cfg.kf_max_dist = (TDAV_VP8_GOP_SIZE_IN_SECONDS * TMEDIA_CODEC_VIDEO(self)->out.fps); +#if defined(VPX_ERROR_RESILIENT_DEFAULT) + self->encoder.cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT; +#else + self->encoder.cfg.g_error_resilient = 1; +#endif +#if defined(VPX_ERROR_RESILIENT_PARTITIONS) + self->encoder.cfg.g_error_resilient |= VPX_ERROR_RESILIENT_PARTITIONS; +#endif + self->encoder.cfg.g_lag_in_frames = 0; +#if TDAV_UNDER_WINDOWS + { + SYSTEM_INFO SystemInfo; + GetSystemInfo(&SystemInfo); + self->encoder.cfg.g_threads = SystemInfo.dwNumberOfProcessors; + } +#endif + self->encoder.cfg.rc_dropframe_thresh = 30; + self->encoder.cfg.rc_end_usage = VPX_CBR; + self->encoder.cfg.g_pass = VPX_RC_ONE_PASS; + self->encoder.cfg.rc_resize_allowed = 0; + self->encoder.cfg.rc_min_quantizer = 8; + self->encoder.cfg.rc_max_quantizer = 56; + self->encoder.cfg.rc_undershoot_pct = 100; + self->encoder.cfg.rc_overshoot_pct = 15; + self->encoder.cfg.rc_buf_initial_sz = 500; + self->encoder.cfg.rc_buf_optimal_sz = 600; + self->encoder.cfg.rc_buf_sz = 1000; + + enc_flags = 0; //VPX_EFLAG_XXX + + if((vpx_ret = vpx_codec_enc_init(&self->encoder.context, vp8_interface_enc, &self->encoder.cfg, enc_flags)) != VPX_CODEC_OK){ + TSK_DEBUG_ERROR("vpx_codec_enc_init failed with error =%s", vpx_codec_err_to_string(vpx_ret)); + return -3; + } + self->encoder.pic_id = /*(rand() ^ rand()) % 0x7FFF*/0/*Use zero: why do you want to make your life harder?*/; + self->encoder.initialized = tsk_true; + + vpx_codec_control(&self->encoder.context, VP8E_SET_STATIC_THRESHOLD, 800); +#if !TDAV_UNDER_MOBILE /* must not remove: crash on Android for sure and probably on iOS also (all ARM devices ?) */ + vpx_codec_control(&self->encoder.context, VP8E_SET_NOISE_SENSITIVITY, 2); +#endif + /* vpx_codec_control(&self->encoder.context, VP8E_SET_CPUUSED, 0); */ + + return 0; +} + +int tdav_codec_vp8_open_decoder(tdav_codec_vp8_t* self) +{ + vpx_codec_err_t vpx_ret; + vpx_codec_caps_t dec_caps; + vpx_codec_flags_t dec_flags = 0; +#if !TDAV_UNDER_MOBILE + static vp8_postproc_cfg_t __pp = { VP8_DEBLOCK | VP8_DEMACROBLOCK, 4, 0}; +#endif + + if(self->decoder.initialized){ + TSK_DEBUG_ERROR("VP8 decoder already initialized"); + return -1; + } + + self->decoder.cfg.w = TMEDIA_CODEC_VIDEO(self)->out.width; + self->decoder.cfg.h = TMEDIA_CODEC_VIDEO(self)->out.height; +#if TDAV_UNDER_WINDOWS + { + SYSTEM_INFO SystemInfo; + GetSystemInfo(&SystemInfo); + self->decoder.cfg.threads = SystemInfo.dwNumberOfProcessors; + } +#endif + + dec_caps = vpx_codec_get_caps(&vpx_codec_vp8_dx_algo); +#if !TDAV_UNDER_MOBILE + if(dec_caps & VPX_CODEC_CAP_POSTPROC){ + dec_flags |= VPX_CODEC_USE_POSTPROC; + } +#endif + if(dec_caps & VPX_CODEC_CAP_ERROR_CONCEALMENT){ + dec_flags |= VPX_CODEC_USE_ERROR_CONCEALMENT; + } + + if((vpx_ret = vpx_codec_dec_init(&self->decoder.context, vp8_interface_dec, &self->decoder.cfg, dec_flags)) != VPX_CODEC_OK){ + TSK_DEBUG_ERROR("vpx_codec_dec_init failed with error =%s", vpx_codec_err_to_string(vpx_ret)); + return -4; + } +#if !TDAV_UNDER_MOBILE + if((vpx_ret = vpx_codec_control(&self->decoder.context, VP8_SET_POSTPROC, &__pp))){ + TSK_DEBUG_WARN("vpx_codec_dec_init failed with error =%s", vpx_codec_err_to_string(vpx_ret)); + } +#endif + self->decoder.initialized = tsk_true; + + return 0; +} + +int tdav_codec_vp8_close_encoder(tdav_codec_vp8_t* self) +{ + TSK_DEBUG_INFO("tdav_codec_vp8_close_encoder(begin)"); + if(self->encoder.initialized){ + vpx_codec_destroy(&self->encoder.context); + self->encoder.initialized = tsk_false; + } + TSK_DEBUG_INFO("tdav_codec_vp8_close_encoder(end)"); + return 0; +} + +int tdav_codec_vp8_close_decoder(tdav_codec_vp8_t* self) +{ + TSK_DEBUG_INFO("tdav_codec_vp8_close_decoder(begin)"); + if(self->decoder.initialized){ + vpx_codec_destroy(&self->decoder.context); + self->decoder.initialized = tsk_false; + } + TSK_DEBUG_INFO("tdav_codec_vp8_close_decoder(end)"); + + return 0; +} + +/* ============ VP8 RTP packetizer/depacketizer ================= */ + + +static void tdav_codec_vp8_encap(tdav_codec_vp8_t* self, const vpx_codec_cx_pkt_t *pkt) +{ + tsk_bool_t non_ref, is_keyframe, part_start; + uint8_t *frame_ptr; + uint32_t part_size, part_ID, pkt_size, index; + + if(!self || !pkt || !pkt->data.frame.buf || !pkt->data.frame.sz){ + TSK_DEBUG_ERROR("Invalid parameter"); + return; + } + + index = 0; + frame_ptr = pkt->data.frame.buf ; + pkt_size = pkt->data.frame.sz; + non_ref = (pkt->data.frame.flags & VPX_FRAME_IS_DROPPABLE); + is_keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY); + + // check P bit validity +#if 0 + if((is_keyframe && (*frame_ptr & 0x01)) || (!is_keyframe && !(*frame_ptr & 0x01))){// 4.3. VP8 Payload Header + TSK_DEBUG_ERROR("Invalid payload header"); + return; + } + if(is_keyframe){ + TSK_DEBUG_INFO("Sending VP8 keyframe..."); + } +#endif + + // first partition (contains modes and motion vectors) + part_ID = 0; // The first VP8 partition(containing modes and motion vectors) MUST be labeled with PartID = 0 + part_size = (frame_ptr[2] << 16) | (frame_ptr[1] << 8) | frame_ptr[0]; + part_size = (part_size >> 5) & 0x7FFFF; + if(part_size > pkt_size){ + TSK_DEBUG_ERROR("part_size is > pkt_size(%u,%u)", part_size, pkt_size); + return; + } + + part_start = tsk_true; + +#if 0 // The first partition could be as big as 10kb for HD 720p video frames => we have to split it + tdav_codec_vp8_rtp_callback(self, &frame_ptr[index], part_size, part_ID, part_start, non_ref, (index + part_size)==pkt_size); + index += part_size; +#else + // first,first,....partitions (or fragment if part_size > TDAV_VP8_RTP_PAYLOAD_MAX_SIZE) + while(index TDAV_VP8_RTP_PAYLOAD_MAX_SIZE) + // FIXME: low FEC + part_start = tsk_true; + while(indexencoder.rtp.size < (size + paydesc_and_hdr_size)){ + if(!(self->encoder.rtp.ptr = tsk_realloc(self->encoder.rtp.ptr, (size + paydesc_and_hdr_size)))){ + TSK_DEBUG_ERROR("Failed to allocate new buffer"); + return; + } + self->encoder.rtp.size = (size + paydesc_and_hdr_size); + } + memcpy((self->encoder.rtp.ptr + paydesc_and_hdr_size), data, size); + + /* VP8 Payload Descriptor */ + // |X|R|N|S|PartID| + self->encoder.rtp.ptr[0] = (partID & 0x0F) // PartID + | ((part_start << 4) & 0x10)// S + | ((non_ref << 5) & 0x20) // N + // R = 0 +#if TDAV_VP8_DISABLE_EXTENSION + | (0x00) // X=0 +#else + | (0x80) // X=1 +#endif + ; + +#if !TDAV_VP8_DISABLE_EXTENSION + // X: |I|L|T|K| RSV | + self->encoder.rtp.ptr[1] = 0x80; // I = 1, L = 0, T = 0, K = 0, RSV = 0 + // I: |M| PictureID | + self->encoder.rtp.ptr[2] = (0x80 | ((self->encoder.pic_id >> 8) & 0x7F)); // M = 1 (PictureID on 15 bits) + self->encoder.rtp.ptr[3] = (self->encoder.pic_id & 0xFF); +#endif + + /* 4.2. VP8 Payload Header */ + //if(has_hdr){ + // already part of the encoded stream + //} + + // Send data over the network + if(TMEDIA_CODEC_VIDEO(self)->out.callback){ + TMEDIA_CODEC_VIDEO(self)->out.result.buffer.ptr = self->encoder.rtp.ptr; + TMEDIA_CODEC_VIDEO(self)->out.result.buffer.size = (size + TDAV_VP8_PAY_DESC_SIZE); + TMEDIA_CODEC_VIDEO(self)->out.result.duration = (1./(double)TMEDIA_CODEC_VIDEO(self)->out.fps) * TMEDIA_CODEC(self)->plugin->rate; + TMEDIA_CODEC_VIDEO(self)->out.result.last_chunck = last; + TMEDIA_CODEC_VIDEO(self)->out.callback(&TMEDIA_CODEC_VIDEO(self)->out.result); + } +} + +#endif /* HAVE_LIBVPX */ diff --git a/branches/2.0/doubango/tinyDAV/src/tdav_session_av.c b/branches/2.0/doubango/tinyDAV/src/tdav_session_av.c index 34f217de..6ded5fb7 100644 --- a/branches/2.0/doubango/tinyDAV/src/tdav_session_av.c +++ b/branches/2.0/doubango/tinyDAV/src/tdav_session_av.c @@ -1026,11 +1026,11 @@ const tsdp_header_M_t* tdav_session_av_get_lo(tdav_session_av_t* self, tsk_bool_ // draft-lennox-mmusic-sdp-source-attributes-01 if(self->media_type == tmedia_audio || self->media_type == tmedia_video){ char* str = tsk_null; - tsk_sprintf(&str, "%u cname:%s", self->rtp_manager->rtp.ssrc.local, "ldjWoB60jbyQlR6e"); + tsk_sprintf(&str, "%u cname:%s", self->rtp_manager->rtp.ssrc.local, self->rtp_manager->rtcp.cname); // also defined in RTCP session tsdp_header_M_add_headers(base->M.lo, TSDP_HEADER_A_VA_ARGS("ssrc", str), tsk_null); tsk_sprintf(&str, "%u mslabel:%s", self->rtp_manager->rtp.ssrc.local, "6994f7d1-6ce9-4fbd-acfd-84e5131ca2e2"); tsdp_header_M_add_headers(base->M.lo, TSDP_HEADER_A_VA_ARGS("ssrc", str), tsk_null); - tsk_sprintf(&str, "%u label:%s", self->rtp_manager->rtp.ssrc.local, (self->media_type == tmedia_audio) ? "Doubango.Audio" : "Doubango.Video"); /* https://groups.google.com/group/discuss-webrtc/browse_thread/thread/6c44106c8ce7d6dc */ + tsk_sprintf(&str, "%u label:%s", self->rtp_manager->rtp.ssrc.local, (self->media_type == tmedia_audio) ? "doubango@audio" : "doubango@video"); /* https://groups.google.com/group/discuss-webrtc/browse_thread/thread/6c44106c8ce7d6dc */ tsdp_header_M_add_headers(base->M.lo, TSDP_HEADER_A_VA_ARGS("ssrc", str), tsk_null); TSK_FREE(str); } diff --git a/branches/2.0/doubango/tinyDAV/src/video/jb/tdav_video_frame.c b/branches/2.0/doubango/tinyDAV/src/video/jb/tdav_video_frame.c index ac862ba4..ffb7c531 100644 --- a/branches/2.0/doubango/tinyDAV/src/video/jb/tdav_video_frame.c +++ b/branches/2.0/doubango/tinyDAV/src/video/jb/tdav_video_frame.c @@ -90,6 +90,7 @@ tdav_video_frame_t* tdav_video_frame_create(trtp_rtp_packet_t* rtp_pkt) frame->payload_type = rtp_pkt->header->payload_type; frame->timestamp = rtp_pkt->header->timestamp; frame->highest_seq_num = rtp_pkt->header->seq_num; + frame->ssrc = rtp_pkt->header->ssrc; tsk_list_push_ascending_data(frame->pkts, (void**)&rtp_pkt); } return frame; @@ -109,6 +110,12 @@ int tdav_video_frame_put(tdav_video_frame_t* self, trtp_rtp_packet_t* rtp_pkt) TSK_DEBUG_ERROR("Payload Type mismatch"); return -2; } +#if 0 + if(self->ssrc != rtp_pkt->header->ssrc){ + TSK_DEBUG_ERROR("SSRC mismatch"); + return -2; + } +#endif rtp_pkt = tsk_object_ref(rtp_pkt); self->highest_seq_num = TSK_MAX(self->highest_seq_num, rtp_pkt->header->seq_num); @@ -184,4 +191,46 @@ bail: tsk_list_unlock(self->pkts); return ret_size; -} \ No newline at end of file +} + + +/** +Checks if the frame is complete (no gap/loss) or not. +IMPORTANT: This function assume that the RTP packets use the marker bit to signal end of sequences. +*@param self The frame with all rtp packets to check +*@param last_seq_num_with_mark The last seq num value of the packet with the mark bit set. Use negative value to ignore. +*@param missing_seq_num A missing seq num if any. This value is set only if the function returns False. +*@return True if the frame is complete and False otherwise. If False is returned then, missing_seq_num is set. +*/ +tsk_bool_t tdav_video_frame_is_complete(const tdav_video_frame_t* self, int32_t last_seq_num_with_mark, uint16_t* missing_seq_num) +{ + const trtp_rtp_packet_t* pkt; + const tsk_list_item_t *item; + uint16_t i; + tsk_bool_t is_complete = tsk_false; + + if(!self || !missing_seq_num){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_false; + } + + i = 0; + tsk_list_lock(self->pkts); + tsk_list_foreach(item, self->pkts){ + if(!(pkt = item->data)){ + continue; + } + if(last_seq_num_with_mark >= 0 && pkt->header->seq_num != (last_seq_num_with_mark + ++i)){ + *missing_seq_num = (pkt->header->seq_num - 1); + break; + } + if(item == self->pkts->tail){ + if(!(is_complete = (pkt->header->marker))){ + *missing_seq_num = (pkt->header->seq_num + 1); + } + } + } + tsk_list_unlock(self->pkts); + + return is_complete; +} diff --git a/branches/2.0/doubango/tinyDAV/src/video/jb/tdav_video_jb.c b/branches/2.0/doubango/tinyDAV/src/video/jb/tdav_video_jb.c index 1cd21329..2ebae270 100644 --- a/branches/2.0/doubango/tinyDAV/src/video/jb/tdav_video_jb.c +++ b/branches/2.0/doubango/tinyDAV/src/video/jb/tdav_video_jb.c @@ -32,31 +32,48 @@ #include "tsk_time.h" #include "tsk_memory.h" -#include "tsk_timer.h" +#include "tsk_thread.h" +#include "tsk_condwait.h" #include "tsk_debug.h" +#if TSK_UNDER_WINDOWS +# include +#endif + // default frame rate // the corret fps will be computed using the RTP timestamps #define TDAV_VIDEO_JB_FPS TDAV_VIDEO_JB_FPS_MAX -#define TDAV_VIDEO_JB_FPS_MIN 1 -#define TDAV_VIDEO_JB_FPS_MAX 30 +#define TDAV_VIDEO_JB_FPS_MIN 15 +#define TDAV_VIDEO_JB_FPS_MAX 60 // Number of correct consecutive RTP packets to receive before computing the FPS #define TDAV_VIDEO_JB_FPS_PROB (TDAV_VIDEO_JB_FPS >> 1) // Max number of frames to allow in jitter buffer -#define TDAV_VIDEO_JB_TAIL_MAX (TDAV_VIDEO_JB_FPS << 2) +//#define TDAV_VIDEO_JB_TAIL_MAX /*FIXME:(TDAV_VIDEO_JB_FPS << 2)*/100 // Min number of frames required before requesting a full decode // This is required because of the FEC and NACK functions // Will be updated using the RTT value from RTCP and probation -#define TDAV_VIDEO_JB_TAIL_MIN_MIN 2 -#define TDAV_VIDEO_JB_TAIL_MIN_MAX 4 +#define TDAV_VIDEO_JB_TAIL_MIN_MIN 10 +#define TDAV_VIDEO_JB_TAIL_MIN_MAX 20 #define TDAV_VIDEO_JB_TAIL_MIN_PROB (TDAV_VIDEO_JB_FPS >> 2) #define TDAV_VIDEO_JB_MAX_DROPOUT 0xFD9B #define TDAV_VIDEO_JB_DISABLE 0 +#define TDAV_VIDEO_JB_TAIL_MAX_LOG2 1 +#if TDAV_UNDER_MOBILE /* to avoid too high memory usage */ +# define TDAV_VIDEO_JB_TAIL_MAX (TDAV_VIDEO_JB_FPS_MIN << TDAV_VIDEO_JB_TAIL_MAX_LOG2) +#else +# define TDAV_VIDEO_JB_TAIL_MAX (TDAV_VIDEO_JB_FPS_MAX << TDAV_VIDEO_JB_TAIL_MAX_LOG2) +#endif + +#define TDAV_VIDEO_JB_RATE 90 /* KHz */ + +#define TDAV_VIDEO_JB_LATENCY_MIN 2 /* Must be > 0 */ +#define TDAV_VIDEO_JB_LATENCY_MAX 10 + static const tdav_video_frame_t* _tdav_video_jb_get_frame(struct tdav_video_jb_s* self, uint32_t timestamp, uint8_t pt, tsk_bool_t *pt_matched); -static int _tdav_video_jb_timer_callback(const void* arg, tsk_timer_id_t timer_id); +static void* TSK_STDCALL _tdav_video_jb_decode_thread_func(void *arg); typedef struct tdav_video_jb_s { @@ -66,6 +83,7 @@ typedef struct tdav_video_jb_s int32_t fps; int32_t fps_prob; int32_t avg_duration; + int32_t rate; // in Khz uint32_t last_timestamp; int32_t conseq_frame_drop; int32_t tail_max; @@ -73,8 +91,16 @@ typedef struct tdav_video_jb_s int32_t tail_prob; tdav_video_frames_L_t *frames; int64_t frames_count; - tsk_timer_manager_handle_t *h_timer; - tsk_timer_id_t timer_decode; + + tsk_size_t latency_min; + tsk_size_t latency_max; + + uint32_t decode_last_timestamp; + int32_t decode_last_seq_num_with_mark; // -1 = unset + uint64_t decode_last_time; + tsk_thread_handle_t* decode_thread[1]; + tsk_condwait_handle_t* decode_thread_cond; + uint16_t seq_nums[0xFF]; tdav_video_jb_cb_f callback; const void* callback_data; @@ -102,13 +128,22 @@ static tsk_object_t* tdav_video_jb_ctor(tsk_object_t * self, va_list * app) TSK_DEBUG_ERROR("Failed to create list"); return tsk_null; } - if(!(jb->h_timer = tsk_timer_manager_create())){ - TSK_DEBUG_ERROR("Failed to create timer manager"); + if(!(jb->decode_thread_cond = tsk_condwait_create())){ + TSK_DEBUG_ERROR("Failed to create condition var"); return tsk_null; } jb->cb_data_fdd.type = tdav_video_jb_cb_data_type_fdd; jb->cb_data_rtp.type = tdav_video_jb_cb_data_type_rtp; + jb->decode_last_seq_num_with_mark = -1; + + jb->fps = TDAV_VIDEO_JB_FPS_MAX; + + jb->rate = TDAV_VIDEO_JB_RATE; + + jb->latency_min = TDAV_VIDEO_JB_LATENCY_MIN; + jb->latency_max = TDAV_VIDEO_JB_LATENCY_MAX; + tsk_safeobj_init(jb); } return self; @@ -121,8 +156,8 @@ static tsk_object_t* tdav_video_jb_dtor(tsk_object_t * self) tdav_video_jb_stop(jb); } TSK_OBJECT_SAFE_FREE(jb->frames); - if(jb->h_timer){ - tsk_timer_manager_destroy(&jb->h_timer); + if(jb->decode_thread_cond){ + tsk_condwait_destroy(&jb->decode_thread_cond); } TSK_SAFE_FREE(jb->buffer.ptr); tsk_safeobj_deinit(jb); @@ -177,7 +212,7 @@ int tdav_video_jb_set_callback(tdav_video_jb_t* self, tdav_video_jb_cb_f callbac int tdav_video_jb_start(tdav_video_jb_t* self) { - int ret; + int ret = 0; if(!self){ TSK_DEBUG_ERROR("Invalid parameter"); return -1; @@ -185,11 +220,17 @@ int tdav_video_jb_start(tdav_video_jb_t* self) if(self->started){ return 0; } - - if((ret = tsk_timer_manager_start(self->h_timer)) == 0){ - self->timer_decode = tsk_timer_manager_schedule(self->h_timer, (1000 / self->fps), _tdav_video_jb_timer_callback, self); - self->started = tsk_true; + + self->started = tsk_true; + + if(!self->decode_thread[0]){ + ret = tsk_thread_create(&self->decode_thread[0], _tdav_video_jb_decode_thread_func, self); + if(ret != 0 || !self->decode_thread[0]){ + TSK_DEBUG_ERROR("Failed to create new thread"); + } + ret = tsk_thread_set_priority(self->decode_thread[0], TSK_THREAD_PRIORITY_TIME_CRITICAL); } + return ret; } @@ -208,14 +249,29 @@ int tdav_video_jb_put(tdav_video_jb_t* self, trtp_rtp_packet_t* rtp_pkt) return -1; } + if(!self->started){ + TSK_DEBUG_INFO("Video jitter buffer not started"); + return 0; + } + seq_num = &self->seq_nums[rtp_pkt->header->payload_type]; tsk_safeobj_lock(self); + //TSK_DEBUG_INFO("receive seqnum=%u", rtp_pkt->header->seq_num); + + if(self->decode_last_timestamp && (self->decode_last_timestamp > rtp_pkt->header->timestamp)){ + if((self->decode_last_timestamp - rtp_pkt->header->timestamp) < TDAV_VIDEO_JB_MAX_DROPOUT){ + TSK_DEBUG_INFO("--------Frame already Decoded [seqnum=%u]------------", rtp_pkt->header->seq_num); + tsk_safeobj_unlock(self); + return 0; + } + } + old_frame = _tdav_video_jb_get_frame(self, rtp_pkt->header->timestamp, rtp_pkt->header->payload_type, &pt_matched); - if((*seq_num && *seq_num != 0xFFFF) && (*seq_num + 1) != rtp_pkt->header->seq_num){ // FIXME: check if seq_num wrapped - int32_t diff = (rtp_pkt->header->seq_num - *seq_num); + if((*seq_num && *seq_num != 0xFFFF) && (*seq_num + 1) != rtp_pkt->header->seq_num){ + int32_t diff = ((int32_t)rtp_pkt->header->seq_num - (int32_t)*seq_num); tsk_bool_t is_frame_loss = (diff > 0); is_restarted = (TSK_ABS(diff) > TDAV_VIDEO_JB_MAX_DROPOUT); is_frame_late_or_dup = !is_frame_loss; @@ -247,7 +303,7 @@ int tdav_video_jb_put(tdav_video_jb_t* self, trtp_rtp_packet_t* rtp_pkt) if((new_frame = tdav_video_frame_create(rtp_pkt))){ // compute avg frame duration if(self->last_timestamp && self->last_timestamp < rtp_pkt->header->timestamp){ - uint32_t duration = (rtp_pkt->header->timestamp - self->last_timestamp); + uint32_t duration = (rtp_pkt->header->timestamp - self->last_timestamp)/self->rate; self->avg_duration = self->avg_duration ? ((self->avg_duration + duration) >> 1) : duration; --self->fps_prob; } @@ -255,8 +311,8 @@ int tdav_video_jb_put(tdav_video_jb_t* self, trtp_rtp_packet_t* rtp_pkt) tsk_list_lock(self->frames); if(self->frames_count >= self->tail_max){ - if(++self->conseq_frame_drop >= self->fps){ - TSK_DEBUG_INFO("Too many frames dropped and fps=%d", self->fps); + if(++self->conseq_frame_drop >= self->tail_max){ + TSK_DEBUG_ERROR("Too many frames dropped and fps=%d", self->fps); tsk_list_clear_items(self->frames); self->conseq_frame_drop = 0; self->frames_count = 1; @@ -268,7 +324,6 @@ int tdav_video_jb_put(tdav_video_jb_t* self, trtp_rtp_packet_t* rtp_pkt) } else{ tsk_list_remove_first_item(self->frames); - // self->frames_count += 0; } tdav_video_jb_reset_fps_prob(self); } @@ -279,10 +334,11 @@ int tdav_video_jb_put(tdav_video_jb_t* self, trtp_rtp_packet_t* rtp_pkt) tsk_list_unlock(self->frames); } if(self->fps_prob <= 0 && self->avg_duration){ - // compute FPS - self->fps = TSK_CLAMP(TDAV_VIDEO_JB_FPS_MIN, ((3003 * 30) / self->avg_duration), TDAV_VIDEO_JB_FPS_MAX); - //self->fps = ((3003 * 30) / self->avg_duration); - self->tail_max = (self->fps << 1); // maximum delay = 2 seconds + // compute FPS using timestamp values + int32_t fps = (1000 / self->avg_duration); + self->fps = TSK_CLAMP(TDAV_VIDEO_JB_FPS_MIN, fps, TDAV_VIDEO_JB_FPS_MAX); + self->tail_max = (self->fps << TDAV_VIDEO_JB_TAIL_MAX_LOG2); // maximum delay = 2 seconds + TSK_DEBUG_INFO("According to rtp-timestamps ...FPS = %d (clipped to %d) and max jb tail will be = %d", fps, self->fps, self->tail_max); tdav_video_jb_reset_fps_prob(self); } } @@ -310,10 +366,17 @@ int tdav_video_jb_stop(tdav_video_jb_t* self) if(!self->started){ return 0; } + + TSK_DEBUG_INFO("tdav_video_jb_stop()"); + + self->started = tsk_false; + + ret = tsk_condwait_broadcast(self->decode_thread_cond); - if((ret = tsk_timer_manager_stop(self->h_timer)) == 0){ - self->started = tsk_false; + if(self->decode_thread[0]){ + ret = tsk_thread_join(&self->decode_thread[0]); } + return ret; } @@ -340,57 +403,116 @@ static const tdav_video_frame_t* _tdav_video_jb_get_frame(tdav_video_jb_t* self, return ret; } -static int _tdav_video_jb_timer_callback(const void* arg, tsk_timer_id_t timer_id) +static void* TSK_STDCALL _tdav_video_jb_decode_thread_func(void *arg) { -#if !TDAV_VIDEO_JB_DISABLE - tdav_video_jb_t* jb = (tdav_video_jb_t*)arg; - - if(!jb->started){ - return 0; - } + tdav_video_jb_t* jb = (tdav_video_jb_t*)arg; + uint64_t delay; + uint16_t missing_seq_num; + const tdav_video_frame_t* frame; + tsk_list_item_t* item; + uint64_t next_decode_duration = (1000 / jb->fps), now; + uint64_t x_decode_duration = (1000 / jb->fps); // expected + uint64_t x_decode_time = tsk_time_now();//expected + tsk_bool_t postpone; + static const uint64_t __toomuch_delay_to_be_valid = 10000; // guard against systems with buggy "tsk_time_now()" -Won't say Windows ...but :)- + + jb->decode_last_seq_num_with_mark = -1; // -1 -> unset + jb->decode_last_time = tsk_time_now(); + + (now); + (delay); - if(jb->timer_decode == timer_id){ - uint64_t next_timeout = (1000 / jb->fps) - 15/*time spent for various tasks (mutexes, timer init, ...)*/; - - if(jb->frames_count >= jb->tail_min){ - tsk_list_item_t* item; - uint64_t decode_start = tsk_time_now(), decode_duration; + TSK_DEBUG_INFO("Video jitter buffer thread - ENTER"); + + while(jb->started){ + tsk_condwait_timedwait(jb->decode_thread_cond, next_decode_duration); + + if(!jb->started){ + break; + } + + // TSK_DEBUG_INFO("Frames count = %d", jb->frames_count); + + if(jb->frames_count >= jb->latency_min){ + item = tsk_null; + postpone = tsk_false; tsk_safeobj_lock(jb); // against get_frame() - tsk_list_lock(jb->frames); - item = tsk_list_pop_first_item(jb->frames); - --jb->frames_count; + tsk_list_lock(jb->frames); // against put() + + // is it still acceptable to wait for missing packets? + if(jb->frames_count < jb->latency_max){ + frame = (const tdav_video_frame_t*)jb->frames->head->data; + if(!tdav_video_frame_is_complete(frame, jb->decode_last_seq_num_with_mark, &missing_seq_num)){ + TSK_DEBUG_INFO("Time to decode frame...but some RTP packets are missing (seqnum=%u). Postpone :(", missing_seq_num); + // signal to the session that a sequence number is missing (will send a NACK) + if(jb->callback){ + jb->cb_data_any.type = tdav_video_jb_cb_data_type_fl; + jb->cb_data_any.ssrc = frame->ssrc; + jb->cb_data_any.fl.seq_num = missing_seq_num; + jb->cb_data_any.fl.count = 1; + jb->callback(&jb->cb_data_any); + postpone = tsk_true; + } + } + } + else{ + jb->decode_last_seq_num_with_mark = -1; // unset() + } + if(!postpone){ + item = tsk_list_pop_first_item(jb->frames); + --jb->frames_count; + } tsk_list_unlock(jb->frames); tsk_safeobj_unlock(jb); - if(jb->callback){ - trtp_rtp_packet_t* pkt; - const tsk_list_item_t* _item = item; // save memory address as "tsk_list_foreach() will change it for each loop" - const tdav_video_frame_t* frame = _item->data; - int32_t last_seq_num = -1; // guard against duplicated packets - tsk_list_foreach(_item, frame->pkts){ - if(!(pkt = _item->data) || !pkt->payload.size || !pkt->header || pkt->header->seq_num == last_seq_num){ - continue; + if(item){ + jb->decode_last_timestamp = ((const tdav_video_frame_t*)item->data)->timestamp; + if(jb->callback){ + trtp_rtp_packet_t* pkt; + const tsk_list_item_t* _item = item; // save memory address as "tsk_list_foreach() will change it for each loop" + int32_t last_seq_num = -1; // guard against duplicated packets + frame = _item->data; + tsk_list_foreach(_item, frame->pkts){ + if(!(pkt = _item->data) || !pkt->payload.size || !pkt->header || pkt->header->seq_num == last_seq_num || !jb->started){ + TSK_DEBUG_ERROR("Skipping invalid rtp packet (do not decode!)"); + continue; + } + jb->cb_data_rtp.rtp.pkt = pkt; + jb->callback(&jb->cb_data_rtp); + if(pkt->header->marker){ + jb->decode_last_seq_num_with_mark = pkt->header->seq_num; + } } - // pkt->header->marker = (_item == frame->pkts->tail); // break the accumulator - jb->cb_data_rtp.rtp.pkt = pkt; - jb->callback(&jb->cb_data_rtp); } + + TSK_OBJECT_SAFE_FREE(item); } - - TSK_OBJECT_SAFE_FREE(item); - decode_duration = (tsk_time_now() - decode_start); - next_timeout = (decode_duration > next_timeout) ? 0 : (next_timeout - decode_duration); - //if(!next_timeout)TSK_DEBUG_INFO("next_timeout=%llu", next_timeout); } - else{ - //TSK_DEBUG_INFO("Not enought frames"); - next_timeout >>= 1; - } - - jb->timer_decode = tsk_timer_manager_schedule(jb->h_timer, next_timeout, _tdav_video_jb_timer_callback, jb); - } + +#if 1 + now = tsk_time_now(); + // comparison used as guard against time wrapping + delay = (now - x_decode_time);//(now > x_decode_time) ? (now - x_decode_time) : x_decode_duration/* do not use zero to avoid endless loop when there is no frame to display */; + if(delay > __toomuch_delay_to_be_valid){ + TSK_DEBUG_INFO("Too much delay (%llu) in video jb. Reseting...", delay); + x_decode_time = now; + next_decode_duration = 0; + } + else{ + next_decode_duration = (delay > x_decode_duration) ? 0 : (x_decode_duration - delay); + x_decode_duration = (1000 / jb->fps); + x_decode_time += x_decode_duration; + } + + + //TSK_DEBUG_INFO("next_decode_timeout=%llu, delay = %llu", next_decode_duration, delay); +#else + next_decode_duration = (1000 / jb->fps); #endif - - return 0; -} \ No newline at end of file + } + + TSK_DEBUG_INFO("Video jitter buffer thread - EXIT"); + + return tsk_null; +} diff --git a/branches/2.0/doubango/tinyDAV/src/video/tdav_session_video.c b/branches/2.0/doubango/tinyDAV/src/video/tdav_session_video.c index b0f653e5..cc16c35b 100644 --- a/branches/2.0/doubango/tinyDAV/src/video/tdav_session_video.c +++ b/branches/2.0/doubango/tinyDAV/src/video/tdav_session_video.c @@ -49,7 +49,12 @@ #include "tsk_memory.h" #include "tsk_debug.h" -#define TDAV_SESSION_VIDEO_AVPF_FIR_INTERVAL_MIN 800 // millis +// Minimum time between two incoming FIR. If smaller, the request from the remote party will be ignored +// Tell the encoder to send IDR frame if condition is met +#define TDAV_SESSION_VIDEO_AVPF_FIR_HONOR_INTERVAL_MIN 1500 // millis +// Minimum time between two outgoing FIR. If smaller, the request from the remote party will be ignored +// Tell the RTCP session to request IDR if condition is met +#define TDAV_SESSION_VIDEO_AVPF_FIR_REQUEST_INTERVAL_MIN 3000 // millis #define TDAV_SESSION_VIDEO_PKT_LOSS_PROB_BAD 2 #define TDAV_SESSION_VIDEO_PKT_LOSS_PROB_GOOD 6 @@ -59,6 +64,9 @@ #define TDAV_SESSION_VIDEO_PKT_LOSS_MEDIUM 22 #define TDAV_SESSION_VIDEO_PKT_LOSS_HIGH 63 +// The maximum number of pakcet loss allowed +#define TDAV_SESSION_VIDEO_PKT_LOSS_MAX_COUNT_TO_REQUEST_FIR 50 + static const tmedia_codec_action_t __action_encode_idr = tmedia_codec_action_encode_idr; static const tmedia_codec_action_t __action_encode_bw_up = tmedia_codec_action_bw_up; static const tmedia_codec_action_t __action_encode_bw_down = tmedia_codec_action_bw_down; @@ -85,7 +93,7 @@ static const tmedia_codec_action_t __action_encode_bw_down = tmedia_codec_action #define _tdav_session_video_remote_requested_idr(__self, __ssrc_media) { \ uint64_t __now = tsk_time_now(); \ - if((__now - (__self)->avpf.last_fir_time) > TDAV_SESSION_VIDEO_AVPF_FIR_INTERVAL_MIN){ /* guard to avoid sending too many FIR */ \ + if((__now - (__self)->avpf.last_fir_time) > TDAV_SESSION_VIDEO_AVPF_FIR_HONOR_INTERVAL_MIN){ /* guard to avoid sending too many FIR */ \ _tdav_session_video_codec_set((__self), "action", __action_encode_idr); \ } \ if((__self)->cb_rtcpevent.func){ \ @@ -154,21 +162,42 @@ static int tdav_session_video_raw_cb(const tmedia_video_encode_result_xt* result packet = rtp_header ? trtp_rtp_packet_create_2(rtp_header) : trtp_rtp_packet_create(base->rtp_manager->rtp.ssrc.local, base->rtp_manager->rtp.seq_num, base->rtp_manager->rtp.timestamp, base->rtp_manager->rtp.payload_type, result->last_chunck); - + if(packet ){ tsk_size_t rtp_hdr_size; + if(!video->encoder.last_frame_time){ + video->encoder.last_frame_time = tsk_time_now(); + } if(result->last_chunck){ +#if 0 +#if 0 + /* http://www.cs.columbia.edu/~hgs/rtp/faq.html#timestamp-computed + For video, time clock rate is fixed at 90 kHz. The timestamps generated depend on whether the application can determine the frame number or not. + If it can or it can be sure that it is transmitting every frame with a fixed frame rate, the timestamp is governed by the nominal frame rate. Thus, for a 30 f/s video, timestamps would increase by 3,000 for each frame, for a 25 f/s video by 3,600 for each frame. + If a frame is transmitted as several RTP packets, these packets would all bear the same timestamp. + If the frame number cannot be determined or if frames are sampled aperiodically, as is typically the case for software codecs, the timestamp has to be computed from the system clock (e.g., gettimeofday()) + */ + uint64_t now = tsk_time_now(); + uint32_t duration = (uint32_t)(now - video->encoder.last_frame_time); + base->rtp_manager->rtp.timestamp += (duration * 90/* 90KHz */); + video->encoder.last_frame_time = now; +#else + base->rtp_manager->rtp.timestamp = (uint32_t)(tsk_gettimeofday_ms() * 90/* 90KHz */); +#endif +#else base->rtp_manager->rtp.timestamp += result->duration; +#endif + } packet->payload.data_const = result->buffer.ptr; packet->payload.size = result->buffer.size; s = trtp_manager_send_rtp_packet(base->rtp_manager, packet, tsk_false); // encrypt and send data + ++base->rtp_manager->rtp.seq_num; // seq_num must be incremented here (before the bail) because already used by SRTP context if(s < TRTP_RTP_HEADER_MIN_SIZE) { - TSK_DEBUG_ERROR("Failed to send packet. %u expected but only %u sent", packet->payload.size, s); - goto bail; + TSK_DEBUG_ERROR("Failed to send packet with seqnum=%u. %u expected but only %u sent", packet->header->seq_num, packet->payload.size, s); + goto bail; } - ++base->rtp_manager->rtp.seq_num; rtp_hdr_size = TRTP_RTP_HEADER_MIN_SIZE + (packet->header->csrc_count << 2); // Save packet // FIXME: only if AVPF is enabled @@ -191,7 +220,13 @@ static int tdav_session_video_raw_cb(const tmedia_video_encode_result_xt* result ++video->avpf.count; } - tsk_list_push_ascending_data(video->avpf.packets, (void**)&packet_avpf); // filtered per seqnum + // The packet must not added 'ascending' but 'back' because the sequence number coult wrap + // For example: + // - send(65533, 65534, 65535, 0, 1) + // - will be stored as (if added 'ascending'): 0, 1, 65533, 65534, 65535 + // - this means there is no benefit (if added 'ascending') as we cannot make 'smart search' using seqnums + // tsk_list_push_ascending_data(video->avpf.packets, (void**)&packet_avpf); // filtered per seqnum + tsk_list_push_back_data(video->avpf.packets, (void**)&packet_avpf); tsk_list_unlock(video->avpf.packets); } @@ -253,8 +288,20 @@ bail: static int tdav_session_video_decode_cb(const tmedia_video_decode_result_xt* result) { tdav_session_av_t* base = (tdav_session_av_t*)result->usr_data; + tdav_session_video_t* video = (tdav_session_video_t*)base; switch(result->type){ + case tmedia_video_decode_result_type_idr: + { + if(video->decoder.last_corrupted_timestamp != ((const trtp_rtp_header_t*)result->proto_hdr)->timestamp){ + TSK_DEBUG_INFO("IDR frame decoded"); + video->decoder.stream_corrupted = tsk_false; + } + else{ + TSK_DEBUG_INFO("IDR frame decoded but corrupted :("); + } + break; + } case tmedia_video_decode_result_type_error: { TSK_DEBUG_INFO("Decoding failed -> send Full Intra Refresh (FIR)"); @@ -528,21 +575,29 @@ static int tdav_session_video_rtcp_cb(const void* callback_data, const trtp_rtcp const tsk_list_item_t* item; const trtp_rtp_packet_t* pkt_rtp; for(i = 0; i < rtpfb->nack.count; ++i){ - static const int32_t __Pow2[16] = { 0x8000, 0x4000, 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100, 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1 }; + static const int32_t __Pow2[16] = { 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000 }; int32_t blp_count; blp = rtpfb->nack.blp[i]; blp_count = blp ? 16 : 0; - for(j = -1; j < blp_count; ++j){ + for(j = -1/*Packet ID (PID)*/; j < blp_count; ++j){ if(j == -1 || (blp & __Pow2[j])){ pid = (rtpfb->nack.pid[i] + (j + 1)); tsk_list_lock(video->avpf.packets); tsk_list_foreach(item, video->avpf.packets){ if(!(pkt_rtp = item->data)){ continue; - } - - if(pkt_rtp->header->seq_num > pid){ + } + + // Very Important: the seq_nums are not consecutive because of wrapping. + // For example, '65533, 65534, 65535, 0, 1' is a valid sequences which means we have to check all packets (probaly need somthing smarter) + if(pkt_rtp->header->seq_num == pid){ + TSK_DEBUG_INFO("NACK Found, pid=%d, blp=%u", pid, blp); + trtp_manager_send_rtp_packet(base->rtp_manager, pkt_rtp, tsk_true); + break; + } + if(item == video->avpf.packets->tail){ + // should never be called unless the tail is too small int32_t old_max = video->avpf.max; int32_t len_drop = (pkt_rtp->header->seq_num - pid); video->avpf.max = TSK_CLAMP((int32_t)tmedia_defaults_get_avpf_tail_min(), (old_max + len_drop), (int32_t)tmedia_defaults_get_avpf_tail_max()); @@ -552,32 +607,21 @@ static int tdav_session_video_rtcp_cb(const void* callback_data, const trtp_rtcp pid, video->avpf.max, video->avpf.count); - // FIR not really requested but needed - /*_tdav_session_video_remote_requested_idr(video, ((const trtp_rtcp_report_fb_t*)rtpfb)->ssrc_media); - tsk_list_clear_items(video->avpf.packets); - video->avpf.count = 0;*/ - goto done; - } - if(pkt_rtp->header->seq_num == pid){ - TSK_DEBUG_INFO("NACK Found=%d", pid); - trtp_manager_send_rtp_packet(base->rtp_manager, pkt_rtp, tsk_true); - break; - } - if(item == video->avpf.packets->tail){ - // must never be called - TSK_DEBUG_INFO("**NACK Not Found=%d", pid); - } - } -done: + // FIR not really requested but needed + /*_tdav_session_video_remote_requested_idr(video, ((const trtp_rtcp_report_fb_t*)rtpfb)->ssrc_media); + tsk_list_clear_items(video->avpf.packets); + video->avpf.count = 0;*/ + } // if(last_item) + }// foreach(pkt) tsk_list_unlock(video->avpf.packets); - } - } - } - } + }// if(BLP is set) + }// foreach(BIT in BLP) + }// foreach(nack) + }// if(nack-blp and nack-pid are set) break; - } - } - } + }// case + }// switch + }// while(rtcp-pkt) return 0; } @@ -600,13 +644,20 @@ static int _tdav_session_video_jb_cb(const tdav_video_jb_cb_data_xt* data) } case tdav_video_jb_cb_data_type_fl: { - tsk_size_t i, j, k; - uint16_t seq_nums[16]; - for(i = 0; i < data->fl.count; i+=16){ - for(j = 0, k = i; j < 16 && k < data->fl.count; ++j, ++k){ - seq_nums[j] = (data->fl.seq_num + i + j); + if(data->fl.count > TDAV_SESSION_VIDEO_PKT_LOSS_MAX_COUNT_TO_REQUEST_FIR){ + TSK_DEBUG_INFO("Packet loss too high (%u) -> Requesting FIR", data->fl.count); + trtp_manager_signal_frame_corrupted(base->rtp_manager, data->ssrc); + } + else{ + tsk_size_t i, j, k; + uint16_t seq_nums[16]; + for(i = 0; i < data->fl.count; i+=16){ + for(j = 0, k = i; j < 16 && k < data->fl.count; ++j, ++k){ + seq_nums[j] = (data->fl.seq_num + i + j); + TSK_DEBUG_INFO("Request re-send(%u)", seq_nums[j]); + } + trtp_manager_signal_pkt_loss(base->rtp_manager, data->ssrc, seq_nums, j); } - trtp_manager_signal_pkt_loss(base->rtp_manager, data->ssrc, seq_nums, j); } break; } @@ -645,7 +696,8 @@ bail: static int _tdav_session_video_decode(tdav_session_video_t* self, const trtp_rtp_packet_t* packet) { tdav_session_av_t* base = (tdav_session_av_t*)self; - static const trtp_rtp_header_t* rtp_header = tsk_null; + static const trtp_rtp_header_t* __rtp_header = tsk_null; + static const tmedia_codec_id_t __codecs_supporting_zero_artifacts = (tmedia_codec_id_vp8 | tmedia_codec_id_h264_bp | tmedia_codec_id_h264_mp | tmedia_codec_id_h263); int ret = 0; if(!self || !packet || !packet->header){ @@ -658,6 +710,7 @@ static int _tdav_session_video_decode(tdav_session_video_t* self, const trtp_rtp if(base->consumer && base->consumer->is_started){ tsk_size_t out_size, _size; const void* _buffer; + tdav_session_video_t* video = (tdav_session_video_t*)base; // Find the codec to use to decode the RTP payload if(!self->decoder.codec || self->decoder.payload_type != packet->header->payload_type){ @@ -675,7 +728,30 @@ static int _tdav_session_video_decode(tdav_session_video_t* self, const trtp_rtp goto bail; } - + // Check if stream is corrupted or not + if(video->decoder.last_seqnum && (video->decoder.last_seqnum + 1) != packet->header->seq_num){ + TSK_DEBUG_INFO("/!\\Video stream corrupted because of packet loss [%u - %u]. Pause rendering if 'zero_artifacts' (supported = %s, enabled = %s).", + video->decoder.last_seqnum, + packet->header->seq_num, + (__codecs_supporting_zero_artifacts & self->decoder.codec->id) ? "yes" : "no", + self->zero_artifacts ? "yes" : "no" + ); + if(!video->decoder.stream_corrupted){ // do not do the job twice + if(self->zero_artifacts && (__codecs_supporting_zero_artifacts & self->decoder.codec->id)){ + // request IDR now and every time after 'TDAV_SESSION_VIDEO_AVPF_FIR_REQUEST_INTERVAL_MIN' ellapsed + // 'zero-artifacts' not enabled then, we'll request IDR when decoding fails + TSK_DEBUG_INFO("Sending FIR to request IDR..."); + trtp_manager_signal_frame_corrupted(base->rtp_manager, packet->header->ssrc); + } + // value will be updated when we decode an IDR frame + video->decoder.stream_corrupted = tsk_true; + video->decoder.stream_corrupted_since = tsk_time_now(); + } + // will be used as guard to avoid redering corrupted IDR + video->decoder.last_corrupted_timestamp = packet->header->timestamp; + } + video->decoder.last_seqnum = packet->header->seq_num; // update last seqnum + // Decode data out_size = self->decoder.codec->plugin->decode( self->decoder.codec, @@ -687,6 +763,16 @@ static int _tdav_session_video_decode(tdav_session_video_t* self, const trtp_rtp if(!out_size || !self->decoder.buffer){ goto bail; } + // check if stream is corrupted + // the above decoding process is required in order to reset stream corruption status when IDR frame is decoded + if(self->zero_artifacts && self->decoder.stream_corrupted && (__codecs_supporting_zero_artifacts & self->decoder.codec->id)){ + TSK_DEBUG_INFO("Do not render video frame because stream is corrupted and 'zero-artifacts' is enabled. Last seqnum=%u", video->decoder.last_seqnum); + if(video->decoder.stream_corrupted && (tsk_time_now() - video->decoder.stream_corrupted_since) > TDAV_SESSION_VIDEO_AVPF_FIR_REQUEST_INTERVAL_MIN){ + TSK_DEBUG_INFO("Sending FIR to request IDR because frame corrupted since %llu...", video->decoder.stream_corrupted_since); + trtp_manager_signal_frame_corrupted(base->rtp_manager, packet->header->ssrc); + } + goto bail; + } // important: do not override the display size (used by the end-user) unless requested if(base->consumer->video.display.auto_resize){ @@ -742,7 +828,7 @@ static int _tdav_session_video_decode(tdav_session_video_t* self, const trtp_rtp _size = out_size; } - ret = tmedia_consumer_consume(base->consumer, _buffer, _size, rtp_header); + ret = tmedia_consumer_consume(base->consumer, _buffer, _size, __rtp_header); } else if(!base->consumer->is_started){ TSK_DEBUG_INFO("Consumer not started"); @@ -1080,6 +1166,8 @@ static tsk_object_t* tdav_session_video_ctor(tsk_object_t * self, va_list * app) /* init() self */ video->jb_enabled = tmedia_defaults_get_videojb_enabled(); + video->zero_artifacts = tmedia_defaults_get_video_zeroartifacts_enabled(); + TSK_DEBUG_INFO("Video 'zero-artifacts' option = %s", video->zero_artifacts ? "yes" : "no"); if(!(video->encoder.h_mutex = tsk_mutex_create())){ TSK_DEBUG_ERROR("Failed to create encode mutex"); return tsk_null; diff --git a/branches/2.0/doubango/tinyMEDIA/include/tinymedia/tmedia_common.h b/branches/2.0/doubango/tinyMEDIA/include/tinymedia/tmedia_common.h index 6df77729..5f33afca 100644 --- a/branches/2.0/doubango/tinyMEDIA/include/tinymedia/tmedia_common.h +++ b/branches/2.0/doubango/tinyMEDIA/include/tinymedia/tmedia_common.h @@ -187,7 +187,7 @@ typedef enum tmedia_video_decode_result_type_e tmedia_video_decode_result_type_none, tmedia_video_decode_result_type_error, - tmedia_video_decode_result_type_success, + tmedia_video_decode_result_type_idr, } tmedia_video_decode_result_type_t; diff --git a/branches/2.0/doubango/tinyMEDIA/include/tinymedia/tmedia_defaults.h b/branches/2.0/doubango/tinyMEDIA/include/tinymedia/tmedia_defaults.h index a9268a15..8da1420e 100644 --- a/branches/2.0/doubango/tinyMEDIA/include/tinymedia/tmedia_defaults.h +++ b/branches/2.0/doubango/tinyMEDIA/include/tinymedia/tmedia_defaults.h @@ -90,8 +90,11 @@ TINYMEDIA_API int tmedia_defaults_set_bypass_encoding(tsk_bool_t enabled); TINYMEDIA_API tsk_bool_t tmedia_defaults_get_bypass_encoding(); TINYMEDIA_API int tmedia_defaults_set_bypass_decoding(tsk_bool_t enabled); TINYMEDIA_API tsk_bool_t tmedia_defaults_get_bypass_decoding(); + TINYMEDIA_API int tmedia_defaults_set_videojb_enabled(tsk_bool_t enabled); TINYMEDIA_API tsk_bool_t tmedia_defaults_get_videojb_enabled(); +TINYMEDIA_API int tmedia_defaults_set_video_zeroartifacts_enabled(tsk_bool_t enabled); +TINYMEDIA_API tsk_bool_t tmedia_defaults_get_video_zeroartifacts_enabled(); TINYMEDIA_API int tmedia_defaults_set_rtpbuff_size(tsk_size_t rtpbuff_size); TINYMEDIA_API tsk_size_t tmedia_defaults_get_rtpbuff_size(); TINYMEDIA_API int tmedia_defaults_set_avpf_tail(tsk_size_t tail_min, tsk_size_t tail_max); diff --git a/branches/2.0/doubango/tinyMEDIA/src/tmedia_defaults.c b/branches/2.0/doubango/tinyMEDIA/src/tmedia_defaults.c index 54d2c13b..5f755b2f 100644 --- a/branches/2.0/doubango/tinyMEDIA/src/tmedia_defaults.c +++ b/branches/2.0/doubango/tinyMEDIA/src/tmedia_defaults.c @@ -1,364 +1,373 @@ -/* - * Copyright (C) 2010-2011 Mamadou Diop. - * - * Contact: Mamadou Diop - * - * This file is part of Open Source Doubango Framework. - * - * DOUBANGO 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 3 of the License, or - * (at your option) any later version. - * - * DOUBANGO 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 DOUBANGO. - * - */ -#include "tinymedia/tmedia_defaults.h" - -#include "tsk_string.h" -#include "tsk_debug.h" - -// /!\ These are global values shared by all sessions and stacks. Could be set (update) per session using "session_set()" - -static tmedia_profile_t __profile = tmedia_profile_default; -static tmedia_bandwidth_level_t __bl = tmedia_bl_unrestricted; -static tmedia_pref_video_size_t __pref_video_size = tmedia_pref_video_size_cif; // 352 x 288: Android, iOS, WP7 -static int32_t __jb_margin_ms = -1; // disable -static int32_t __jb_max_late_rate_percent = -1; // -1: disable 4: default for speex -static uint32_t __echo_tail = 20; -static uint32_t __echo_skew = 0; -static tsk_bool_t __echo_supp_enabled; -static tsk_bool_t __agc_enabled = tsk_false; -static float __agc_level = 8000.0f; -static tsk_bool_t __vad_enabled = tsk_false; -static tsk_bool_t __noise_supp_enabled = tsk_true; -static int32_t __noise_supp_level = -30; -static tsk_bool_t __100rel_enabled = tsk_true; -static int32_t __sx = -1; -static int32_t __sy = -1; -static int32_t __audio_producer_gain = 0; -static int32_t __audio_consumer_gain = 0; -static uint16_t __rtp_port_range_start = 1024; -static uint16_t __rtp_port_range_stop = 65535; -static tsk_bool_t __rtp_symetric_enabled = tsk_false; // This option is force symetric RTP for remote size. Local: always ON -static tmedia_type_t __media_type = tmedia_audio; -static int32_t __volume = 100; -static int32_t __inv_session_expires = 0; // Session Timers: 0: disabled -static char* __inv_session_refresher = tsk_null; -static tmedia_srtp_mode_t __srtp_mode = tmedia_srtp_mode_none; -static tmedia_srtp_type_t __srtp_type = tmedia_srtp_type_sdes; -static tsk_bool_t __rtcp_enabled = tsk_true; -static tsk_bool_t __rtcpmux_enabled = tsk_true; -static tsk_bool_t __ice_enabled = tsk_false; -static tsk_bool_t __bypass_encoding_enabled = tsk_false; -static tsk_bool_t __bypass_decoding_enabled = tsk_false; -static tsk_bool_t __videojb_enabled = tsk_true; -static tsk_size_t __rtpbuff_size = 0x1FFFE; // Network buffer size use for RTP (SO_RCVBUF, SO_SNDBUF) -static tsk_size_t __avpf_tail_min = 20; // Min size for tail used to honor RTCP-NACK requests -static tsk_size_t __avpf_tail_max = 160; // Max size for tail used to honor RTCP-NACK requests - -int tmedia_defaults_set_profile(tmedia_profile_t profile){ - __profile = profile; +/* + * Copyright (C) 2010-2011 Mamadou Diop. + * + * Contact: Mamadou Diop + * + * This file is part of Open Source Doubango Framework. + * + * DOUBANGO 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 3 of the License, or + * (at your option) any later version. + * + * DOUBANGO 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 DOUBANGO. + * + */ +#include "tinymedia/tmedia_defaults.h" + +#include "tsk_string.h" +#include "tsk_debug.h" + +// /!\ These are global values shared by all sessions and stacks. Could be set (update) per session using "session_set()" + +static tmedia_profile_t __profile = tmedia_profile_default; +static tmedia_bandwidth_level_t __bl = tmedia_bl_unrestricted; +static tmedia_pref_video_size_t __pref_video_size = tmedia_pref_video_size_cif; // 352 x 288: Android, iOS, WP7 +static int32_t __jb_margin_ms = -1; // disable +static int32_t __jb_max_late_rate_percent = -1; // -1: disable 4: default for speex +static uint32_t __echo_tail = 20; +static uint32_t __echo_skew = 0; +static tsk_bool_t __echo_supp_enabled; +static tsk_bool_t __agc_enabled = tsk_false; +static float __agc_level = 8000.0f; +static tsk_bool_t __vad_enabled = tsk_false; +static tsk_bool_t __noise_supp_enabled = tsk_true; +static int32_t __noise_supp_level = -30; +static tsk_bool_t __100rel_enabled = tsk_true; +static int32_t __sx = -1; +static int32_t __sy = -1; +static int32_t __audio_producer_gain = 0; +static int32_t __audio_consumer_gain = 0; +static uint16_t __rtp_port_range_start = 1024; +static uint16_t __rtp_port_range_stop = 65535; +static tsk_bool_t __rtp_symetric_enabled = tsk_false; // This option is force symetric RTP for remote size. Local: always ON +static tmedia_type_t __media_type = tmedia_audio; +static int32_t __volume = 100; +static int32_t __inv_session_expires = 0; // Session Timers: 0: disabled +static char* __inv_session_refresher = tsk_null; +static tmedia_srtp_mode_t __srtp_mode = tmedia_srtp_mode_none; +static tmedia_srtp_type_t __srtp_type = tmedia_srtp_type_sdes; +static tsk_bool_t __rtcp_enabled = tsk_true; +static tsk_bool_t __rtcpmux_enabled = tsk_true; +static tsk_bool_t __ice_enabled = tsk_false; +static tsk_bool_t __bypass_encoding_enabled = tsk_false; +static tsk_bool_t __bypass_decoding_enabled = tsk_false; +static tsk_bool_t __videojb_enabled = tsk_true; +static tsk_bool_t __video_zeroartifacts_enabled = tsk_false; // Requires from remote parties to support AVPF (RTCP-FIR/NACK/PLI) +static tsk_size_t __rtpbuff_size = 0x1FFFE; // Network buffer size use for RTP (SO_RCVBUF, SO_SNDBUF) +static tsk_size_t __avpf_tail_min = 20; // Min size for tail used to honor RTCP-NACK requests +static tsk_size_t __avpf_tail_max = 160; // Max size for tail used to honor RTCP-NACK requests + +int tmedia_defaults_set_profile(tmedia_profile_t profile){ + __profile = profile; + return 0; +} +tmedia_profile_t tmedia_defaults_get_profile(){ + return __profile; +} + +// @deprecated +int tmedia_defaults_set_bl(tmedia_bandwidth_level_t bl){ + __bl = bl; + return 0; +} +// @deprecated +tmedia_bandwidth_level_t tmedia_defaults_get_bl(){ + return __bl; +} + +int tmedia_defaults_set_pref_video_size(tmedia_pref_video_size_t pref_video_size){ + __pref_video_size = pref_video_size; + return 0; +} +tmedia_pref_video_size_t tmedia_defaults_get_pref_video_size(){ + return __pref_video_size; +} + +int tmedia_defaults_set_jb_margin(int32_t jb_margin_ms){ + __jb_margin_ms = jb_margin_ms; + return __jb_margin_ms; +} + +int32_t tmedia_defaults_get_jb_margin(){ + return __jb_margin_ms; +} + +int tmedia_defaults_set_jb_max_late_rate(int32_t jb_max_late_rate_percent){ + __jb_max_late_rate_percent = jb_max_late_rate_percent; + return 0; +} + +int32_t tmedia_defaults_get_jb_max_late_rate(){ + return __jb_max_late_rate_percent; +} + +int tmedia_defaults_set_echo_tail(uint32_t echo_tail){ + __echo_tail = echo_tail; + return 0; +} + +int tmedia_defaults_set_echo_skew(uint32_t echo_skew){ + __echo_skew = echo_skew; + return 0; +} + +uint32_t tmedia_defaults_get_echo_tail() +{ + return __echo_tail; +} + +uint32_t tmedia_defaults_get_echo_skew(){ + return __echo_skew; +} + +int tmedia_defaults_set_echo_supp_enabled(tsk_bool_t echo_supp_enabled){ + __echo_supp_enabled = echo_supp_enabled; + return 0; +} + +tsk_bool_t tmedia_defaults_get_echo_supp_enabled(){ + return __echo_supp_enabled; +} + +int tmedia_defaults_set_agc_enabled(tsk_bool_t agc_enabled){ + __agc_enabled = agc_enabled; + return 0; +} + +tsk_bool_t tmedia_defaults_get_agc_enabled(){ + return __agc_enabled; +} + +int tmedia_defaults_set_agc_level(float agc_level){ + __agc_level = agc_level; + return 0; +} + +float tmedia_defaults_get_agc_level() +{ + return __agc_level; +} + +int tmedia_defaults_set_vad_enabled(tsk_bool_t vad_enabled){ + __vad_enabled = vad_enabled; + return 0; +} + +tsk_bool_t tmedia_defaults_get_vad_enabled(){ + return __vad_enabled; +} + +int tmedia_defaults_set_noise_supp_enabled(tsk_bool_t noise_supp_enabled){ + __noise_supp_enabled = noise_supp_enabled; + return 0; +} + +tsk_bool_t tmedia_defaults_get_noise_supp_enabled(){ + return __noise_supp_enabled; +} + +int tmedia_defaults_set_noise_supp_level(int32_t noise_supp_level){ + __noise_supp_level = noise_supp_level; + return 0; +} + +int32_t tmedia_defaults_get_noise_supp_level(){ + return __noise_supp_level; +} + +int tmedia_defaults_set_100rel_enabled(tsk_bool_t _100rel_enabled){ + __100rel_enabled = _100rel_enabled; + return 0; +} +tsk_bool_t tmedia_defaults_get_100rel_enabled(){ + return __100rel_enabled; +} + +int tmedia_defaults_set_screen_size(int32_t sx, int32_t sy){ + __sx = sx; + __sy = sy; + return 0; +} + +int32_t tmedia_defaults_get_screen_x(){ + return __sx; +} + +int32_t tmedia_defaults_get_screen_y(){ + return __sy; +} + +int tmedia_defaults_set_audio_gain(int32_t audio_producer_gain, int32_t audio_consumer_gain){ + __audio_producer_gain = audio_producer_gain; + __audio_consumer_gain = audio_consumer_gain; + return 0; +} + +int32_t tmedia_defaults_get_audio_producer_gain(){ + return __audio_producer_gain; +} + +int32_t tmedia_defaults_get_audio_consumer_gain(){ + return __audio_consumer_gain; +} + +uint16_t tmedia_defaults_get_rtp_port_range_start(){ + return __rtp_port_range_start; +} + +uint16_t tmedia_defaults_get_rtp_port_range_stop(){ + return __rtp_port_range_stop; +} +int tmedia_defaults_set_rtp_port_range(uint16_t start, uint16_t stop){ + if(start < 1024 || stop < 1024 || start >= stop){ + TSK_DEBUG_ERROR("Invalid parameter: (%u < 1024 || %u < 1024 || %u >= %u)", start, stop, start, stop); + return -1; + } + __rtp_port_range_start = start; + __rtp_port_range_stop = stop; + return 0; +} + +int tmedia_defaults_set_rtp_symetric_enabled(tsk_bool_t enabled){ + __rtp_symetric_enabled = enabled; + return 0; +} +tsk_bool_t tmedia_defaults_get_rtp_symetric_enabled(){ + return __rtp_symetric_enabled; +} + +tmedia_type_t tmedia_defaults_get_media_type(){ + return __media_type; +} + +int tmedia_defaults_set_media_type(tmedia_type_t media_type){ + __media_type = media_type; + return 0; +} + +int tmedia_defaults_set_volume(int32_t volume){ + __volume = TSK_CLAMP(0, volume, 100); + return 0; +} +int32_t tmedia_defaults_get_volume(){ + return __volume; +} + +int32_t tmedia_defaults_get_inv_session_expires(){ + return __inv_session_expires; +} +int tmedia_defaults_set_inv_session_expires(int32_t timeout){ + if(timeout >= 0){ + __inv_session_expires = timeout; + return 0; + } + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; +} + +const char* tmedia_defaults_get_inv_session_refresher(){ + return __inv_session_refresher; +} +int tmedia_defaults_set_inv_session_refresher(const char* refresher){ + tsk_strupdate(&__inv_session_refresher, refresher); + return 0; +} + +tmedia_srtp_mode_t tmedia_defaults_get_srtp_mode(){ + return __srtp_mode; +} +int tmedia_defaults_set_srtp_mode(tmedia_srtp_mode_t mode){ + __srtp_mode = mode; + return 0; +} + +tmedia_srtp_type_t tmedia_defaults_get_srtp_type(){ + return __srtp_type; +} +int tmedia_defaults_set_srtp_type(tmedia_srtp_type_t srtp_type){ + __srtp_type = srtp_type; + return 0; +} + +tsk_bool_t tmedia_defaults_get_rtcp_enabled(){ + return __rtcp_enabled; +} +int tmedia_defaults_set_rtcp_enabled(tsk_bool_t rtcp_enabled){ + __rtcp_enabled = rtcp_enabled; + return 0; +} + +tsk_bool_t tmedia_defaults_get_rtcpmux_enabled(){ + return __rtcpmux_enabled; +} +int tmedia_defaults_set_rtcpmux_enabled(tsk_bool_t rtcpmux_enabled){ + __rtcpmux_enabled = rtcpmux_enabled; + return 0; +} + +int tmedia_defaults_set_ice_enabled(tsk_bool_t ice_enabled){ + __ice_enabled = ice_enabled; + return 0; +} +tsk_bool_t tmedia_defaults_get_ice_enabled(){ + return __ice_enabled; +} + +int tmedia_defaults_set_bypass_encoding(tsk_bool_t enabled){ + __bypass_encoding_enabled = enabled; + return 0; +} +tsk_bool_t tmedia_defaults_get_bypass_encoding(){ + return __bypass_encoding_enabled; +} + +int tmedia_defaults_set_bypass_decoding(tsk_bool_t enabled){ + __bypass_decoding_enabled = enabled; + return 0; +} +tsk_bool_t tmedia_defaults_get_bypass_decoding(){ + return __bypass_decoding_enabled; +} + +int tmedia_defaults_set_videojb_enabled(tsk_bool_t enabled){ + __videojb_enabled = enabled; + return 0; +} +tsk_bool_t tmedia_defaults_get_videojb_enabled(){ + return __videojb_enabled; +} + +int tmedia_defaults_set_video_zeroartifacts_enabled(tsk_bool_t enabled){ + __video_zeroartifacts_enabled = enabled; return 0; } -tmedia_profile_t tmedia_defaults_get_profile(){ - return __profile; -} - -// @deprecated -int tmedia_defaults_set_bl(tmedia_bandwidth_level_t bl){ - __bl = bl; - return 0; -} -// @deprecated -tmedia_bandwidth_level_t tmedia_defaults_get_bl(){ - return __bl; -} - -int tmedia_defaults_set_pref_video_size(tmedia_pref_video_size_t pref_video_size){ - __pref_video_size = pref_video_size; - return 0; -} -tmedia_pref_video_size_t tmedia_defaults_get_pref_video_size(){ - return __pref_video_size; -} - -int tmedia_defaults_set_jb_margin(int32_t jb_margin_ms){ - __jb_margin_ms = jb_margin_ms; - return __jb_margin_ms; -} - -int32_t tmedia_defaults_get_jb_margin(){ - return __jb_margin_ms; -} - -int tmedia_defaults_set_jb_max_late_rate(int32_t jb_max_late_rate_percent){ - __jb_max_late_rate_percent = jb_max_late_rate_percent; - return 0; -} - -int32_t tmedia_defaults_get_jb_max_late_rate(){ - return __jb_max_late_rate_percent; -} - -int tmedia_defaults_set_echo_tail(uint32_t echo_tail){ - __echo_tail = echo_tail; - return 0; -} - -int tmedia_defaults_set_echo_skew(uint32_t echo_skew){ - __echo_skew = echo_skew; - return 0; -} - -uint32_t tmedia_defaults_get_echo_tail() -{ - return __echo_tail; -} - -uint32_t tmedia_defaults_get_echo_skew(){ - return __echo_skew; -} - -int tmedia_defaults_set_echo_supp_enabled(tsk_bool_t echo_supp_enabled){ - __echo_supp_enabled = echo_supp_enabled; - return 0; -} - -tsk_bool_t tmedia_defaults_get_echo_supp_enabled(){ - return __echo_supp_enabled; -} - -int tmedia_defaults_set_agc_enabled(tsk_bool_t agc_enabled){ - __agc_enabled = agc_enabled; - return 0; -} - -tsk_bool_t tmedia_defaults_get_agc_enabled(){ - return __agc_enabled; -} - -int tmedia_defaults_set_agc_level(float agc_level){ - __agc_level = agc_level; - return 0; -} - -float tmedia_defaults_get_agc_level() -{ - return __agc_level; -} - -int tmedia_defaults_set_vad_enabled(tsk_bool_t vad_enabled){ - __vad_enabled = vad_enabled; - return 0; -} - -tsk_bool_t tmedia_defaults_get_vad_enabled(){ - return __vad_enabled; -} - -int tmedia_defaults_set_noise_supp_enabled(tsk_bool_t noise_supp_enabled){ - __noise_supp_enabled = noise_supp_enabled; - return 0; -} - -tsk_bool_t tmedia_defaults_get_noise_supp_enabled(){ - return __noise_supp_enabled; -} - -int tmedia_defaults_set_noise_supp_level(int32_t noise_supp_level){ - __noise_supp_level = noise_supp_level; - return 0; -} - -int32_t tmedia_defaults_get_noise_supp_level(){ - return __noise_supp_level; -} - -int tmedia_defaults_set_100rel_enabled(tsk_bool_t _100rel_enabled){ - __100rel_enabled = _100rel_enabled; - return 0; -} -tsk_bool_t tmedia_defaults_get_100rel_enabled(){ - return __100rel_enabled; -} - -int tmedia_defaults_set_screen_size(int32_t sx, int32_t sy){ - __sx = sx; - __sy = sy; - return 0; -} - -int32_t tmedia_defaults_get_screen_x(){ - return __sx; -} - -int32_t tmedia_defaults_get_screen_y(){ - return __sy; -} - -int tmedia_defaults_set_audio_gain(int32_t audio_producer_gain, int32_t audio_consumer_gain){ - __audio_producer_gain = audio_producer_gain; - __audio_consumer_gain = audio_consumer_gain; - return 0; -} - -int32_t tmedia_defaults_get_audio_producer_gain(){ - return __audio_producer_gain; -} - -int32_t tmedia_defaults_get_audio_consumer_gain(){ - return __audio_consumer_gain; -} - -uint16_t tmedia_defaults_get_rtp_port_range_start(){ - return __rtp_port_range_start; -} - -uint16_t tmedia_defaults_get_rtp_port_range_stop(){ - return __rtp_port_range_stop; -} -int tmedia_defaults_set_rtp_port_range(uint16_t start, uint16_t stop){ - if(start < 1024 || stop < 1024 || start >= stop){ - TSK_DEBUG_ERROR("Invalid parameter: (%u < 1024 || %u < 1024 || %u >= %u)", start, stop, start, stop); - return -1; - } - __rtp_port_range_start = start; - __rtp_port_range_stop = stop; - return 0; -} - -int tmedia_defaults_set_rtp_symetric_enabled(tsk_bool_t enabled){ - __rtp_symetric_enabled = enabled; - return 0; -} -tsk_bool_t tmedia_defaults_get_rtp_symetric_enabled(){ - return __rtp_symetric_enabled; -} - -tmedia_type_t tmedia_defaults_get_media_type(){ - return __media_type; -} - -int tmedia_defaults_set_media_type(tmedia_type_t media_type){ - __media_type = media_type; - return 0; -} - -int tmedia_defaults_set_volume(int32_t volume){ - __volume = TSK_CLAMP(0, volume, 100); - return 0; -} -int32_t tmedia_defaults_get_volume(){ - return __volume; -} - -int32_t tmedia_defaults_get_inv_session_expires(){ - return __inv_session_expires; -} -int tmedia_defaults_set_inv_session_expires(int32_t timeout){ - if(timeout >= 0){ - __inv_session_expires = timeout; - return 0; - } - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; -} - -const char* tmedia_defaults_get_inv_session_refresher(){ - return __inv_session_refresher; -} -int tmedia_defaults_set_inv_session_refresher(const char* refresher){ - tsk_strupdate(&__inv_session_refresher, refresher); - return 0; -} - -tmedia_srtp_mode_t tmedia_defaults_get_srtp_mode(){ - return __srtp_mode; -} -int tmedia_defaults_set_srtp_mode(tmedia_srtp_mode_t mode){ - __srtp_mode = mode; - return 0; -} - -tmedia_srtp_type_t tmedia_defaults_get_srtp_type(){ - return __srtp_type; -} -int tmedia_defaults_set_srtp_type(tmedia_srtp_type_t srtp_type){ - __srtp_type = srtp_type; - return 0; -} - -tsk_bool_t tmedia_defaults_get_rtcp_enabled(){ - return __rtcp_enabled; -} -int tmedia_defaults_set_rtcp_enabled(tsk_bool_t rtcp_enabled){ - __rtcp_enabled = rtcp_enabled; - return 0; -} - -tsk_bool_t tmedia_defaults_get_rtcpmux_enabled(){ - return __rtcpmux_enabled; -} -int tmedia_defaults_set_rtcpmux_enabled(tsk_bool_t rtcpmux_enabled){ - __rtcpmux_enabled = rtcpmux_enabled; - return 0; -} - -int tmedia_defaults_set_ice_enabled(tsk_bool_t ice_enabled){ - __ice_enabled = ice_enabled; - return 0; -} -tsk_bool_t tmedia_defaults_get_ice_enabled(){ - return __ice_enabled; -} - -int tmedia_defaults_set_bypass_encoding(tsk_bool_t enabled){ - __bypass_encoding_enabled = enabled; - return 0; -} -tsk_bool_t tmedia_defaults_get_bypass_encoding(){ - return __bypass_encoding_enabled; -} - -int tmedia_defaults_set_bypass_decoding(tsk_bool_t enabled){ - __bypass_decoding_enabled = enabled; - return 0; -} -tsk_bool_t tmedia_defaults_get_bypass_decoding(){ - return __bypass_decoding_enabled; -} - -int tmedia_defaults_set_videojb_enabled(tsk_bool_t enabled){ - __videojb_enabled = enabled; - return 0; -} -tsk_bool_t tmedia_defaults_get_videojb_enabled(){ - return __videojb_enabled; -} - -int tmedia_defaults_set_rtpbuff_size(tsk_size_t rtpbuff_size){ - __rtpbuff_size = rtpbuff_size; - return 0; -} -tsk_size_t tmedia_defaults_get_rtpbuff_size(){ - return __rtpbuff_size; -} - -int tmedia_defaults_set_avpf_tail(tsk_size_t tail_min, tsk_size_t tail_max){ - __avpf_tail_min = tail_min; - __avpf_tail_max = tail_max; - return 0; -} -tsk_size_t tmedia_defaults_get_avpf_tail_min(){ - return __avpf_tail_min; -} -tsk_size_t tmedia_defaults_get_avpf_tail_max(){ - return __avpf_tail_max; -} \ No newline at end of file +tsk_bool_t tmedia_defaults_get_video_zeroartifacts_enabled(){ + return __video_zeroartifacts_enabled; +} + +int tmedia_defaults_set_rtpbuff_size(tsk_size_t rtpbuff_size){ + __rtpbuff_size = rtpbuff_size; + return 0; +} +tsk_size_t tmedia_defaults_get_rtpbuff_size(){ + return __rtpbuff_size; +} + +int tmedia_defaults_set_avpf_tail(tsk_size_t tail_min, tsk_size_t tail_max){ + __avpf_tail_min = tail_min; + __avpf_tail_max = tail_max; + return 0; +} +tsk_size_t tmedia_defaults_get_avpf_tail_min(){ + return __avpf_tail_min; +} +tsk_size_t tmedia_defaults_get_avpf_tail_max(){ + return __avpf_tail_max; +} diff --git a/branches/2.0/doubango/tinyMEDIA/src/tmedia_session.c b/branches/2.0/doubango/tinyMEDIA/src/tmedia_session.c index 25cbe117..c6b87868 100644 --- a/branches/2.0/doubango/tinyMEDIA/src/tmedia_session.c +++ b/branches/2.0/doubango/tinyMEDIA/src/tmedia_session.c @@ -1,1954 +1,1956 @@ -/* -* Copyright (C) 2010-2011 Mamadou Diop. -* -* Contact: Mamadou Diop -* -* This file is part of Open Source Doubango Framework. -* -* DOUBANGO 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 3 of the License, or -* (at your option) any later version. -* -* DOUBANGO 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 DOUBANGO. -* -*/ - -/**@file tmedia_session.h - * @brief Base session object. - * - * @author Mamadou Diop - * - - */ -#include "tinymedia/tmedia_session.h" - -#include "tinymedia/tmedia_session_ghost.h" -#include "tinymedia/tmedia_defaults.h" - -#include "tinysdp/headers/tsdp_header_O.h" - -#include "tsk_memory.h" -#include "tsk_debug.h" - -/**@defgroup tmedia_session_group Media Session -* For more information about the SOA, please refer to http://betelco.blogspot.com/2010/03/sdp-offeranswer-soa_2993.html -*/ - -#if !defined(va_copy) -# define va_copy(D, S) ((D) = (S)) -#endif - -extern const tmedia_codec_plugin_def_t* __tmedia_codec_plugins[TMED_CODEC_MAX_PLUGINS]; - -/* pointer to all registered sessions */ -const tmedia_session_plugin_def_t* __tmedia_session_plugins[TMED_SESSION_MAX_PLUGINS] = {0}; - -/* === local functions === */ -static int _tmedia_session_mgr_load_sessions(tmedia_session_mgr_t* self); -static int _tmedia_session_mgr_clear_sessions(tmedia_session_mgr_t* self); -static int _tmedia_session_mgr_apply_params(tmedia_session_mgr_t* self); -static int _tmedia_session_prepare(tmedia_session_t* self); -static int _tmedia_session_set_ro(tmedia_session_t* self, const tsdp_header_M_t* m); -static int _tmedia_session_load_codecs(tmedia_session_t* self); - -const char* tmedia_session_get_media(const tmedia_session_t* self); -const tsdp_header_M_t* tmedia_session_get_lo(tmedia_session_t* self); -int tmedia_session_set_ro(tmedia_session_t* self, const tsdp_header_M_t* m); - - -/*== Predicate function to find session object by media */ -static int __pred_find_session_by_media(const tsk_list_item_t *item, const void *media) -{ - if(item && item->data){ - return tsk_stricmp(tmedia_session_get_media((const tmedia_session_t *)item->data), (const char*)media); - } - return -1; -} - -/*== Predicate function to find session object by type */ -static int __pred_find_session_by_type(const tsk_list_item_t *item, const void *type) -{ - if(item && item->data){ - return ((const tmedia_session_t *)item->data)->type - *((tmedia_type_t*)type); - } - return -1; -} - -/*== Predicate function to find codec object by address */ -int __pred_find_codec_by_format(const tsk_list_item_t *item, const void *codec) -{ - if(item && item->data && codec){ - return tsk_stricmp(((const tmedia_codec_t*)item->data)->format, ((const tmedia_codec_t*)codec)->format); - } - return -1; -} - -uint64_t tmedia_session_get_unique_id(){ - static uint64_t __UniqueId = 1; // MUST not be equal to zero - return __UniqueId++; -} - -/**@ingroup tmedia_session_group -* Initializes a newly created media session. -* @param self the media session to initialize. -* @param type the type of the session to initialize. -* @retval Zero if succeed and non-zero error code otherwise. -*/ -int tmedia_session_init(tmedia_session_t* self, tmedia_type_t type) -{ - int ret = 0; - - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - if(!self->initialized){ - /* set values */ - if(!self->id){ - self->id = tmedia_session_get_unique_id(); - } - self->type = type; - self->initialized = tsk_true; - self->bl = tmedia_defaults_get_bl(); - self->codecs_allowed = tmedia_codec_id_all; - self->bypass_encoding = tmedia_defaults_get_bypass_encoding(); - self->bypass_decoding = tmedia_defaults_get_bypass_decoding(); - /* load associated codecs */ - ret = _tmedia_session_load_codecs(self); - } - - return 0; -} - -int tmedia_session_set(tmedia_session_t* self, ...) -{ - va_list ap; - tmedia_params_L_t* params; - - if(!self || !self->plugin || !self->plugin->set){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - va_start(ap, self); - if((params = tmedia_params_create_2(&ap))){ - const tsk_list_item_t *item; - const tmedia_param_t* param; - tsk_list_foreach(item, params){ - if(!(param = item->data)){ - continue; - } - if((self->type & param->media_type)){ - self->plugin->set(self, param); - } - } - TSK_OBJECT_SAFE_FREE(params); - } - va_end(ap); - return 0; -} - -tsk_bool_t tmedia_session_set_2(tmedia_session_t* self, const tmedia_param_t* param) -{ - if(!self || !param){ - TSK_DEBUG_ERROR("Invalid parameter"); - return tsk_false; - } - - if(param->plugin_type == tmedia_ppt_session){ - if(param->value_type == tmedia_pvt_int32){ - if(tsk_striequals(param->key, "codecs-supported")){ - //if(self->M.lo){ - // TSK_DEBUG_WARN("Cannot change codec values at this stage"); - //} - //else{ - self->codecs_allowed = *((int32_t*)param->value); - return (_tmedia_session_load_codecs(self) == 0); - //} - return tsk_true; - } - else if(tsk_striequals(param->key, "bypass-encoding")){ - self->bypass_encoding = *((int32_t*)param->value); - return tsk_true; - } - else if(tsk_striequals(param->key, "bypass-decoding")){ - self->bypass_decoding = *((int32_t*)param->value); - return tsk_true; - } - else if(tsk_striequals(param->key, "dtls-cert-verify")){ - self->dtls.verify = *((int32_t*)param->value) ? tsk_true : tsk_false; - return tsk_true; - } - } - else if(param->value_type == tmedia_pvt_pchar){ - if(tsk_striequals(param->key, "dtls-file-ca")){ - tsk_strupdate(&self->dtls.file_ca, param->value); - return tsk_true; - } - else if(tsk_striequals(param->key, "dtls-file-pbk")){ - tsk_strupdate(&self->dtls.file_pbk, param->value); - return tsk_true; - } - else if(tsk_striequals(param->key, "dtls-file-pvk")){ - tsk_strupdate(&self->dtls.file_pvk, param->value); - return tsk_true; - } - } - } - - return tsk_false; -} - -tsk_bool_t tmedia_session_get(tmedia_session_t* self, tmedia_param_t* param) -{ - if(!self || !param){ - TSK_DEBUG_ERROR("Invalid parameter"); - return tsk_false; - } - - if(param->plugin_type == tmedia_ppt_session){ - if(param->value_type == tmedia_pvt_int32){ - if(tsk_striequals(param->key, "codecs-negotiated")){ // negotiated codecs - tmedia_codecs_L_t* neg_codecs = tsk_object_ref(self->neg_codecs); - if(neg_codecs){ - const tsk_list_item_t* item; - tsk_list_foreach(item, neg_codecs){ - ((int32_t*)param->value)[0] |= TMEDIA_CODEC(item->data)->id; - } - TSK_OBJECT_SAFE_FREE(neg_codecs); - } - return tsk_true; - } - } - } - - return tsk_false; -} - -/**@ingroup tmedia_session_group -* Generic function to compare two sessions. -* @param sess1 The first session to compare. -* @param sess2 The second session to compare. -* @retval Returns an integral value indicating the relationship between the two sessions: -* <0 : @a sess1 less than @a sess2.
-* 0 : @a sess1 identical to @a sess2.
-* >0 : @a sess1 greater than @a sess2.
-*/ -int tmedia_session_cmp(const tsk_object_t* sess1, const tsk_object_t* sess2) -{ - return (TMEDIA_SESSION(sess1) - TMEDIA_SESSION(sess2)); -} - -/**@ingroup tmedia_session_group -* Registers a session plugin. -* @param plugin the definition of the plugin. -* @retval Zero if succeed and non-zero error code otherwise. -* @sa @ref tmedia_session_create() -*/ -int tmedia_session_plugin_register(const tmedia_session_plugin_def_t* plugin) -{ - tsk_size_t i; - if(!plugin){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - /* add or replace the plugin */ - for(i = 0; imedia, media)){ - return __tmedia_session_plugins[i]; - } - i++; - } - return tsk_null; -} - -/**@ingroup tmedia_session_group -* UnRegisters a session plugin. -* @param plugin the definition of the plugin. -* @retval Zero if succeed and non-zero error code otherwise. -*/ -int tmedia_session_plugin_unregister(const tmedia_session_plugin_def_t* plugin) -{ - tsk_size_t i; - tsk_bool_t found = tsk_false; - if(!plugin){ - TSK_DEBUG_ERROR("Invalid Parameter"); - return -1; - } - - /* find the plugin to unregister */ - for(i = 0; iobjdef && (plugin->type == type)){ - if((session = tsk_object_new(plugin->objdef))){ - if(!session->initialized){ - tmedia_session_init(session, type); - } - session->plugin = plugin; - } - break; - } - } - return session; -} - -/* internal funtion: prepare lo */ -static int _tmedia_session_prepare(tmedia_session_t* self) -{ - int ret; - if(!self || !self->plugin || !self->plugin->prepare){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - if(self->prepared){ - TSK_DEBUG_WARN("Session already prepared"); - return 0; - } - if((ret = self->plugin->prepare(self))){ - TSK_DEBUG_ERROR("Failed to prepare the session"); - } - else{ - self->prepared = tsk_true; - } - return ret; -} - -/* internal function used to set remote offer */ -int _tmedia_session_set_ro(tmedia_session_t* self, const tsdp_header_M_t* m) -{ - int ret; - if(!self || !self->plugin || !self->plugin->set_remote_offer){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - if(!(ret = self->plugin->set_remote_offer(self, m))){ - self->ro_changed = tsk_true; - self->ro_held = tsdp_header_M_is_held(m, tsk_false); - } - return ret; -} - -/* internal function: get media */ -const char* tmedia_session_get_media(const tmedia_session_t* self) -{ - if(!self || !self->plugin){ - TSK_DEBUG_ERROR("Invalid parameter"); - return tsk_null; - } - - /* ghost? */ - if(self->plugin == tmedia_session_ghost_plugin_def_t){ - return ((const tmedia_session_ghost_t*)self)->media; - } - else{ - return self->plugin->media; - } -} -/* internal function: get local offer */ -const tsdp_header_M_t* tmedia_session_get_lo(tmedia_session_t* self) -{ - const tsdp_header_M_t* m; - - if(!self || !self->plugin || !self->plugin->get_local_offer){ - TSK_DEBUG_ERROR("Invalid parameter"); - return tsk_null; - } - - if((m = self->plugin->get_local_offer(self))){ - self->ro_changed = tsk_false; /* we should have a fresh local offer (based on the latest ro) */ - } - return m; -} - -/* Match a codec */ -tmedia_codecs_L_t* tmedia_session_match_codec(tmedia_session_t* self, const tsdp_header_M_t* M) -{ - const tmedia_codec_t *codec; - char *rtpmap = tsk_null, *fmtp = tsk_null, *image_attr = tsk_null, *name = tsk_null; - const tsdp_fmt_t* fmt; - const tsk_list_item_t *it1, *it2; - tsk_bool_t found = tsk_false; - tmedia_codecs_L_t* matchingCodecs = tsk_null; - - if(!self || !M){ - TSK_DEBUG_ERROR("Invalid parameter"); - return tsk_null; - } - - - /* foreach format */ - tsk_list_foreach(it1, M->FMTs){ - fmt = it1->data; - - /* foreach codec */ - tsk_list_foreach(it2, self->codecs){ - /* 'tmedia_codec_id_none' is used for fake codecs (e.g. dtmf or msrp) and should not be filtered beacuse of backward compatibility*/ - if(!(codec = it2->data) || !codec->plugin || !(codec->id == tmedia_codec_id_none || (codec->id & self->codecs_allowed))){ - continue; - } - - // Guard to avoid matching a codec more than once - // For example, H.264 codecs without profiles (Jitsi, Tiscali PC client) to distinguish them could match more than once - if(matchingCodecs && tsk_list_find_object_by_pred(matchingCodecs, __pred_find_codec_by_format, codec)){ - continue; - } - - // Dyn. payload type - if(codec->dyn && (rtpmap = tsdp_header_M_get_rtpmap(M, fmt->value))){ - int32_t rate, channels; - /* parse rtpmap */ - if(tmedia_parse_rtpmap(rtpmap, &name, &rate, &channels)){ - goto next; - } - - /* compare name and rate... what about channels? */ - if(tsk_striequals(name, codec->name) && (!rate || !codec->plugin->rate || (codec->plugin->rate == rate))){ - goto compare_fmtp; - } - } - // Fixed payload type - else{ - if(tsk_striequals(fmt->value, codec->format)){ - goto compare_fmtp; - } - } - - /* rtpmap do not match: free strings and try next codec */ - goto next; - -compare_fmtp: - if((fmtp = tsdp_header_M_get_fmtp(M, fmt->value))){ /* remote have fmtp? */ - if(tmedia_codec_sdp_att_match(codec, "fmtp", fmtp)){ /* fmtp matches? */ - if(codec->type & tmedia_video) goto compare_imageattr; - else found = tsk_true; - } - else goto next; - } - else{ /* no fmtp -> always match */ - if(codec->type & tmedia_video) goto compare_imageattr; - else found = tsk_true; - } - -compare_imageattr: - if(codec->type & tmedia_video){ - if((image_attr = tsdp_header_M_get_imageattr(M, fmt->value))){ - if(tmedia_codec_sdp_att_match(codec, "imageattr", image_attr)) found = tsk_true; - } - else found = tsk_true; - } - - // update neg. format - if(found) tsk_strupdate((char**)&codec->neg_format, fmt->value); - -next: - TSK_FREE(name); - TSK_FREE(fmtp); - TSK_FREE(rtpmap); - TSK_FREE(image_attr); - if(found){ - tmedia_codec_t * copy; - if(!matchingCodecs){ - matchingCodecs = tsk_list_create(); - } - copy = tsk_object_ref((void*)codec); - tsk_list_push_back_data(matchingCodecs, (void**)©); - - found = tsk_false; - break; - } - } - } - - - return matchingCodecs; -} - -int tmedia_session_set_onrtcp_cbfn(tmedia_session_t* self, const void* context, tmedia_session_rtcp_onevent_cb_f func) -{ - if(self && self->plugin && self->plugin->rtcp.set_onevent_cbfn){ - return self->plugin->rtcp.set_onevent_cbfn(self, context, func); - } - return -1; -} - -int tmedia_session_send_rtcp_event(tmedia_session_t* self, tmedia_rtcp_event_type_t event_type, uint32_t ssrc_media) -{ - if(self && self->plugin && self->plugin->rtcp.send_event){ - return self->plugin->rtcp.send_event(self, event_type, ssrc_media); - } - TSK_DEBUG_INFO("Not sending RTCP event with SSRC = %u because no callback function found", ssrc_media); - return -1; -} - -int tmedia_session_set_onerror_cbfn(tmedia_session_t* self, const void* usrdata, tmedia_session_onerror_cb_f fun) -{ - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - self->onerror_cb.fun = fun; - self->onerror_cb.usrdata = usrdata; - return 0; -} - -/**@ingroup tmedia_session_group -* DeInitializes a media session. -* @param self the media session to deinitialize. -* @retval Zero if succeed and non-zero error code otherwise. -*/ -int tmedia_session_deinit(tmedia_session_t* self) -{ - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - /* free codecs */ - TSK_OBJECT_SAFE_FREE(self->codecs); - TSK_OBJECT_SAFE_FREE(self->neg_codecs); - - /* free lo, no and ro */ - TSK_OBJECT_SAFE_FREE(self->M.lo); - TSK_OBJECT_SAFE_FREE(self->M.ro); - - /* QoS */ - TSK_OBJECT_SAFE_FREE(self->qos); - - /* DTLS */ - TSK_FREE(self->dtls.file_ca); - TSK_FREE(self->dtls.file_pbk); - TSK_FREE(self->dtls.file_pvk); - - return 0; -} - -/**@ingroup tmedia_session_group -* Send DTMF event -* @param self the audio session to use to send a DTMF event -* @param event the DTMF event to send (should be between 0-15) -* @retval Zero if succeed and non-zero error code otherwise. -*/ -int tmedia_session_audio_send_dtmf(tmedia_session_audio_t* self, uint8_t event) -{ - if(!self || !TMEDIA_SESSION(self)->plugin || !TMEDIA_SESSION(self)->plugin->audio.send_dtmf){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - return TMEDIA_SESSION(self)->plugin->audio.send_dtmf(TMEDIA_SESSION(self), event); -} - -int tmedia_session_t140_set_ondata_cbfn(tmedia_session_t* self, const void* context, tmedia_session_t140_ondata_cb_f func) -{ - if(self && self->plugin && self->plugin->t140.set_ondata_cbfn){ - return self->plugin->t140.set_ondata_cbfn(self, context, func); - } - return -1; -} - -int tmedia_session_t140_send_data(tmedia_session_t* self, enum tmedia_t140_data_type_e data_type, const void* data_ptr, unsigned data_size) -{ - if(self && self->plugin && self->plugin->t140.send_data){ - return self->plugin->t140.send_data(self, data_type, data_ptr, data_size); - } - return -1; -} - -/* internal function used to prepare a session */ -int _tmedia_session_load_codecs(tmedia_session_t* self) -{ - tsk_size_t i = 0; - tmedia_codec_t* codec; - const tmedia_codec_plugin_def_t* plugin; - - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - if(!self->codecs && !(self->codecs = tsk_list_create())){ - TSK_DEBUG_ERROR("Failed to create new list"); - return -1; - } - - tsk_list_lock(self->codecs); - - /* remove old codecs */ - tsk_list_clear_items(self->codecs); - - /* for each registered plugin create a session instance */ - while((i < TMED_CODEC_MAX_PLUGINS) && (plugin = __tmedia_codec_plugins[i++])){ - /* 'tmedia_codec_id_none' is used for fake codecs (e.g. dtmf or msrp) and should not be filtered beacuse of backward compatibility*/ - if((plugin->type & self->type) && (plugin->codec_id == tmedia_codec_id_none || (plugin->codec_id & self->codecs_allowed))){ - if((codec = tmedia_codec_create(plugin->format))){ - if(!self->codecs){ - self->codecs = tsk_list_create(); - } - tsk_list_push_back_data(self->codecs, (void**)(&codec)); - } - } - } - - tsk_list_unlock(self->codecs); - - return 0; -} - - -/**@ingroup tmedia_session_group -* Creates new session manager. -* @param type the type of the session to create. For example, (@ref tmed_sess_type_audio | @ref tmed_sess_type_video). -* @param addr the local ip address or FQDN to use in the sdp message. -* @param ipv6 indicates whether @a addr is IPv6 address or not. Useful when @a addr is a FQDN. -* @param load_sessions Whether the offerer or not. -* will create an audio/video session. -* @retval new @ref tmedia_session_mgr_t object -*/ -tmedia_session_mgr_t* tmedia_session_mgr_create(tmedia_type_t type, const char* addr, tsk_bool_t ipv6, tsk_bool_t offerer) -{ - tmedia_session_mgr_t* mgr; - - if(!(mgr = tsk_object_new(tmedia_session_mgr_def_t))){ - TSK_DEBUG_ERROR("Failed to create Media Session manager"); - return tsk_null; - } - - /* init */ - mgr->type = type; - mgr->addr = tsk_strdup(addr); - mgr->ipv6 = ipv6; - - /* load sessions (will allow us to generate lo) */ - if(offerer){ - mgr->offerer = tsk_true; - //if(_tmedia_session_mgr_load_sessions(mgr)){ - /* Do nothing */ - // TSK_DEBUG_ERROR("Failed to load sessions"); - //} - } - - return mgr; -} - -/**@ingroup tmedia_session_group - */ -int tmedia_session_mgr_set_media_type(tmedia_session_mgr_t* self, tmedia_type_t type) -{ - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - if(self->type != type){ - self->mediaType_changed = tsk_true; - self->type = type; - } - return 0; -} - -/**@ingroup tmedia_session_group -*/ -tmedia_session_t* tmedia_session_mgr_find(tmedia_session_mgr_t* self, tmedia_type_t type) -{ - tmedia_session_t* session; - - tsk_list_lock(self->sessions); - session = (tmedia_session_t*)tsk_list_find_object_by_pred(self->sessions, __pred_find_session_by_type, &type); - tsk_list_unlock(self->sessions); - - return tsk_object_ref(session); -} - -/**@ingroup tmedia_session_group -*/ -int tmedia_session_mgr_set_natt_ctx(tmedia_session_mgr_t* self, tnet_nat_context_handle_t* natt_ctx, const char* public_addr) -{ - if(!self || !natt_ctx){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - TSK_OBJECT_SAFE_FREE(self->natt_ctx); - self->natt_ctx = tsk_object_ref(natt_ctx); - tsk_strupdate(&self->public_addr, public_addr); - - tmedia_session_mgr_set(self, - TMEDIA_SESSION_SET_POBJECT(self->type, "natt-ctx", self->natt_ctx), - TMEDIA_SESSION_SET_NULL()); - return 0; -} - -int tmedia_session_mgr_set_ice_ctx(tmedia_session_mgr_t* self, struct tnet_ice_ctx_s* ctx_audio, struct tnet_ice_ctx_s* ctx_video) -{ - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - TSK_OBJECT_SAFE_FREE(self->ice.ctx_audio); - TSK_OBJECT_SAFE_FREE(self->ice.ctx_video); - - self->ice.ctx_audio = tsk_object_ref(ctx_audio); - self->ice.ctx_video = tsk_object_ref(ctx_video); - - if(self->type & tmedia_audio){ - tmedia_session_mgr_set(self, - TMEDIA_SESSION_SET_POBJECT(tmedia_audio, "ice-ctx", self->ice.ctx_audio), - TMEDIA_SESSION_SET_NULL()); - } - - if(self->type & tmedia_video){ - tmedia_session_mgr_set(self, - TMEDIA_SESSION_SET_POBJECT(tmedia_video, "ice-ctx", self->ice.ctx_video), - TMEDIA_SESSION_SET_NULL()); - } - - return 0; -} - -/**@ingroup tmedia_session_group -* Starts the session manager by starting all underlying sessions. -* You should set both remote and local offers before calling this function. -* @param self The session manager to start. -* @retval Zero if succced and non-zero error code otherwise. -* -* @sa @ref tmedia_session_mgr_stop -*/ -int tmedia_session_mgr_start(tmedia_session_mgr_t* self) -{ - int ret = 0; - tsk_list_item_t* item; - tmedia_session_t* session; - - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - tsk_safeobj_lock(self); - - if(self->started){ - goto bail; - } - - tsk_list_foreach(item, self->sessions){ - if(!(session = item->data) || !session->plugin || !session->plugin->start){ - TSK_DEBUG_ERROR("Invalid session"); - ret = -2; - goto bail; - } - if((ret = session->plugin->start(session))){ - TSK_DEBUG_ERROR("Failed to start %s session", session->plugin->media); - continue; - } - } - - self->started = tsk_true; - -bail: - tsk_safeobj_unlock(self); - return ret; -} - -/**@ingroup tmedia_session_group -* sets parameters for one or several sessions. -* @param self The session manager -* @param ... Any TMEDIA_SESSION_SET_*() macros -* @retval Zero if succeed and non-zero error code otherwise -*/ -int tmedia_session_mgr_set(tmedia_session_mgr_t* self, ...) -{ - va_list ap; - int ret; - - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - va_start(ap, self); - ret = tmedia_session_mgr_set_2(self, &ap); - va_end(ap); - - return ret; -} - -/**@ingroup tmedia_session_group -* sets parameters for one or several sessions. -* @param self The session manager -* @param app List of parameters. -* @retval Zero if succeed and non-zero error code otherwise -*/ -int tmedia_session_mgr_set_2(tmedia_session_mgr_t* self, va_list *app) -{ - tmedia_params_L_t* params; - - if(!self || !app){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - if((params = tmedia_params_create_2(app))){ - if(!self->params){ - self->params = tsk_object_ref(params); - } - else{ - tsk_list_pushback_list(self->params, params); - } - TSK_OBJECT_SAFE_FREE(params); - } - - /* load params if we already have sessions */ - if(!TSK_LIST_IS_EMPTY(self->sessions)){ - _tmedia_session_mgr_apply_params(self); - } - - return 0; -} - -/**@ingroup tmedia_session_group -* sets parameters for one or several sessions. -* @param self The session manager -* @param params List of parameters to set -* @retval Zero if succeed and non-zero error code otherwise -*/ -int tmedia_session_mgr_set_3(tmedia_session_mgr_t* self, const tmedia_params_L_t* params) -{ - if(!self || !params){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - if(!self->params){ - self->params = tsk_list_create(); - } - tsk_list_pushback_list(self->params, params); - - /* load params if we already have sessions */ - if(!TSK_LIST_IS_EMPTY(self->sessions)){ - _tmedia_session_mgr_apply_params(self); - } - - return 0; -} - -int tmedia_session_mgr_get(tmedia_session_mgr_t* self, ...) -{ - va_list ap; - int ret = 0; - tmedia_params_L_t* params; - const tsk_list_item_t *item1, *item2; - - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - va_start(ap, self); - - if((params = tmedia_params_create_2(&ap))){ - tmedia_session_t* session; - tmedia_param_t* param; - tsk_list_foreach(item2, params){ - if((param = item2->data)){ - tsk_list_foreach(item1, self->sessions){ - if(!(session = (tmedia_session_t*)item1->data) || !session->plugin){ - continue; - } - if((session->type & param->media_type) == session->type && session->plugin->set){ - ret = session->plugin->get(session, param); - } - } - } - } - TSK_OBJECT_SAFE_FREE(params); - } - - va_end(ap); - - return ret; -} - -/**@ingroup tmedia_session_group -* Stops the session manager by stopping all underlying sessions. -* @param self The session manager to stop. -* @retval Zero if succced and non-zero error code otherwise. -* -* @sa @ref tmedia_session_mgr_start -*/ -int tmedia_session_mgr_stop(tmedia_session_mgr_t* self) -{ - int ret = 0; - tsk_list_item_t* item; - tmedia_session_t* session; - - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - tsk_safeobj_lock(self); - - if(!self->started){ - goto bail; - } - - tsk_list_foreach(item, self->sessions){ - if(!(session = item->data) || !session->plugin || !session->plugin->stop){ - TSK_DEBUG_ERROR("Invalid session"); - ret = -2; - goto bail; - } - if((ret = session->plugin->stop(session))){ - TSK_DEBUG_ERROR("Failed to stop session"); - continue; - } - } - self->started = tsk_false; - -bail: - tsk_safeobj_unlock(self); - return ret; -} - -/**@ingroup tmedia_session_group -* Gets local offer. -*/ -const tsdp_message_t* tmedia_session_mgr_get_lo(tmedia_session_mgr_t* self) -{ - const tsk_list_item_t* item; - const tmedia_session_t* ms; - const tsdp_header_M_t* m; - const tsdp_message_t* ret = tsk_null; - - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return tsk_null; - } - - tsk_safeobj_lock(self); - - /* prepare the session manager if not already done (create all sessions) */ - if(TSK_LIST_IS_EMPTY(self->sessions)){ - if(_tmedia_session_mgr_load_sessions(self)){ - TSK_DEBUG_ERROR("Failed to prepare the session manager"); - goto bail; - } - } - - /* creates local sdp if not already done or update it's value (because of set_ro())*/ - if((self->ro_changed || self->state_changed || self->mediaType_changed) && self->sdp.lo){ - // delete current lo - TSK_OBJECT_SAFE_FREE(self->sdp.lo); - if(self->mediaType_changed){ - // reload session with new medias and keep the old one - _tmedia_session_mgr_load_sessions(self); - } - self->ro_changed = tsk_false; - self->ro_provisional = tsk_false; - self->state_changed = tsk_false; - self->mediaType_changed = tsk_false; - } - - if(self->sdp.lo){ - ret = self->sdp.lo; - goto bail; - } - else if((self->sdp.lo = tsdp_message_create_empty(self->public_addr ? self->public_addr : self->addr, self->ipv6, self->sdp.lo_ver++))){ - /* Set connection "c=" */ - tsdp_message_add_headers(self->sdp.lo, - TSDP_HEADER_C_VA_ARGS("IN", self->ipv6 ? "IP6" : "IP4", self->public_addr ? self->public_addr : self->addr),//FIXME - tsk_null); - }else{ - self->sdp.lo_ver--; - TSK_DEBUG_ERROR("Failed to create empty SDP message"); - goto bail; - } - - /* pass complete local sdp to the sessions to allow them to use session-level attributes */ - tmedia_session_mgr_set(self, - TMEDIA_SESSION_SET_POBJECT(self->type, "local-sdp-message", self->sdp.lo), - TMEDIA_SESSION_SET_NULL()); - - /* gets each "m=" line from the sessions and add them to the local sdp */ - tsk_list_foreach(item, self->sessions){ - if(!(ms = item->data) || !ms->plugin){ - TSK_DEBUG_ERROR("Invalid session"); - continue; - } - /* prepare the media session */ - if(!ms->prepared && (_tmedia_session_prepare(TMEDIA_SESSION(ms)))){ - TSK_DEBUG_ERROR("Failed to prepare session"); /* should never happen */ - continue; - } - - /* Add QoS lines to our local media */ - if((self->qos.type != tmedia_qos_stype_none) && !TMEDIA_SESSION(ms)->qos){ - TMEDIA_SESSION(ms)->qos = tmedia_qos_tline_create(self->qos.type, self->qos.strength); - } - - /* add "m=" line from the session to the local sdp */ - if((m = tmedia_session_get_lo(TMEDIA_SESSION(ms)))){ - tsdp_message_add_header(self->sdp.lo, TSDP_HEADER(m)); - } - else{ - TSK_DEBUG_ERROR("Failed to get m= line for [%s] media", ms->plugin->media); - } - } - - ret = self->sdp.lo; - -bail: - tsk_safeobj_unlock(self); - return ret; -} - - -/**@ingroup tmedia_session_group -* Sets remote offer. -*/ -int tmedia_session_mgr_set_ro(tmedia_session_mgr_t* self, const tsdp_message_t* sdp, tmedia_ro_type_t ro_type) -{ - const tmedia_session_t* ms; - const tsdp_header_M_t* M; - const tsdp_header_C_t* C; /* global "c=" line */ - const tsdp_header_O_t* O; - tsk_size_t index = 0; - tsk_size_t active_sessions_count = 0; - int ret = 0; - tsk_bool_t found; - tsk_bool_t stopped_to_reconf = tsk_false; - tsk_bool_t is_ro_network_info_changed = tsk_false; - tsk_bool_t is_ro_hold_resume_changed = tsk_false; - tsk_bool_t is_ro_loopback_address = tsk_false; - tsk_bool_t is_media_type_changed = tsk_false; - tsk_bool_t is_ro_media_lines_changed = tsk_false; - tsk_bool_t had_ro_sdp, had_ro_provisional, is_ro_provisional_final_matching = tsk_false; - tmedia_qos_stype_t qos_type = tmedia_qos_stype_none; - tmedia_type_t new_mediatype = tmedia_none; - - if(!self || !sdp){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - tsk_safeobj_lock(self); - - had_ro_sdp = (self->sdp.ro != tsk_null); - had_ro_provisional = (had_ro_sdp && self->ro_provisional); - - /* RFC 3264 subcaluse 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. If the version in the origin line - does not increment, the SDP MUST be identical to the SDP with that version number. The answerer MUST be prepared to receive - an offer that contains SDP with a version that has not changed; this is effectively a no-op. - */ - if((O = (const tsdp_header_O_t*)tsdp_message_get_header(sdp, tsdp_htype_O))){ - tsk_bool_t is_ro_provisional; - if(self->sdp.ro_ver == (int32_t)O->sess_version){ - TSK_DEBUG_INFO("Remote offer has not changed"); - ret = 0; - goto bail; - } - // Last provisional and new final sdp messages match only if: - // - session version diff is == 1 - // - previous sdp was provisional and new one is final - // - the new final sdp is inside an answer - is_ro_provisional = ((ro_type & tmedia_ro_type_provisional) == tmedia_ro_type_provisional); - is_ro_provisional_final_matching = ((had_ro_provisional && !is_ro_provisional) && ((self->sdp.ro_ver + 1) == O->sess_version) && ((ro_type & tmedia_ro_type_answer) == tmedia_ro_type_answer)); - self->sdp.ro_ver = (int32_t)O->sess_version; - } - else{ - TSK_DEBUG_ERROR("o= line is missing"); - ret = -2; - goto bail; - } - - /* SDP comparison */ - if(sdp && self->sdp.ro){ - const tsdp_header_M_t *M0, *M1; - const tsdp_header_C_t *C0, *C1; - index = 0; - while((M0 = (const tsdp_header_M_t*)tsdp_message_get_headerAt(self->sdp.ro, tsdp_htype_M, index))){ - M1 = (const tsdp_header_M_t*)tsdp_message_get_headerAt(sdp, tsdp_htype_M, index); - if(!M1 || !tsk_striequals(M1->media, M0->media)){ - // media lines must be at the same index - // (M1 == null) means media lines are not at the same index or new one have been added/removed - is_ro_media_lines_changed = tsk_true; - } - - // hold/resume - is_ro_hold_resume_changed |= !tsk_striequals(tsdp_header_M_get_holdresume_att(M0), tsdp_header_M_get_holdresume_att(M1)); - // media lines - if(!is_ro_media_lines_changed){ - is_ro_media_lines_changed - // (M1 == null) means media lines are not at the same index or new one have been added/removed - |= (!M1) - // same media (e.g. audio) - || !tsk_striequals(M1->media, M0->media) - // same protos (e.g. SRTP) - || !tsk_striequals(M1->proto, M0->proto); - } - // network ports - is_ro_network_info_changed |= ((M1 ? M1->port : 0) != (M0->port)); - - if(!is_ro_network_info_changed){ - C0 = (const tsdp_header_C_t*)tsdp_message_get_headerAt(self->sdp.ro, tsdp_htype_C, index); - C1 = (const tsdp_header_C_t*)tsdp_message_get_headerAt(sdp, tsdp_htype_C, index); - // Connection informations must be both "null" or "not-null" - if(!(is_ro_network_info_changed = !((C0 && C1) || (!C0 && !C1)))){ - if(C0){ - is_ro_network_info_changed = (!tsk_strequals(C1->addr, C0->addr) || !tsk_strequals(C1->nettype, C0->nettype) || !tsk_strequals(C1->addrtype, C0->addrtype)); - } - } - } - ++index; - } - // the index was used against current ro which means at this step there is no longer any media at "index" - // to be sure that new and old sdp have same number of media lines, we just check that there is no media in the new sdp at "index" - is_ro_media_lines_changed |= (tsdp_message_get_headerAt(sdp, tsdp_htype_M, index) != tsk_null); - } - - /* - * Make sure that the provisional response is an preview of the final as explained rfc6337 section 3.1.1. We only check the most important part (IP addr and ports). - * It's useless to check codecs or any other caps (SRTP, ICE, DTLS...) as our offer haven't changed - * If the preview is different than the final response than this is a bug on the remote party: - * As per rfc6337 section 3.1.1.: - * - [RFC3261] requires all SDP in the responses to the INVITE request to be identical. - * - After the UAS has sent the answer in a reliable provisional - response to the INVITE, the UAS should not include any SDPs in - subsequent responses to the INVITE. - * If the remote party is buggy, then the newly generated local SDP will be sent in the ACK request - */ - is_ro_provisional_final_matching &= !(is_ro_media_lines_changed || is_ro_network_info_changed); - - /* This is to hack fake forking from ZTE => ignore SDP with loopback address in order to not start/stop the camera several - * times which leads to more than ten seconds for session connection. - * Gets the global connection line: "c=" - * Loopback address is only invalid on - */ - if((C = (const tsdp_header_C_t*)tsdp_message_get_header(sdp, tsdp_htype_C)) && C->addr){ - is_ro_loopback_address = (tsk_striequals("IP4", C->addrtype) && tsk_striequals("127.0.0.1", C->addr)) - || (tsk_striequals("IP6", C->addrtype) && tsk_striequals("::1", C->addr)); - } - - /* Check if media type has changed or not - * For initial offer we don't need to check anything - */ - if(self->sdp.lo){ - new_mediatype = tmedia_type_from_sdp(sdp); - if((is_media_type_changed = (new_mediatype != self->type))){ - tmedia_session_mgr_set_media_type(self, new_mediatype); - TSK_DEBUG_INFO("media type has changed"); - } - } - - TSK_DEBUG_INFO( - "is_ro_provisional_final_matching=%d,\n" - "is_ro_media_lines_changed=%d,\n" - "is_ro_network_info_changed=%d,\n" - "is_ro_loopback_address=%d,\n" - "is_media_type_changed=%d\n", - is_ro_provisional_final_matching, - is_ro_media_lines_changed, - is_ro_network_info_changed, - is_ro_loopback_address, - is_media_type_changed - ); - - /* - * It's almost impossible to update the codecs, the connection information etc etc while the sessions are running - * For example, if the video producer is already started then, you probably cannot update its configuration - * without stoping it and restart again with the right config. Same for RTP Network config (ip addresses, NAT, ports, IP version, ...) - * "is_loopback_address" is used as a guard to avoid reconf for loopback address used for example by ZTE for fake forking. In all case - * loopback address won't work on embedded devices such as iOS and Android. - */ - if((self->started && !is_ro_loopback_address ) && (is_ro_network_info_changed || is_ro_media_lines_changed || is_media_type_changed)){ - TSK_DEBUG_INFO("stopped_to_reconf=true"); - if((ret = tmedia_session_mgr_stop(self))){ - TSK_DEBUG_ERROR("Failed to stop session manager"); - goto bail; - } - stopped_to_reconf = tsk_true; - } - - /* update remote offer */ - TSK_OBJECT_SAFE_FREE(self->sdp.ro); - self->sdp.ro = tsk_object_ref((void*)sdp); - - /* if the session is running this means no session update is required - this check must be done after the "ro" update - */ - if(self->started && !(is_ro_hold_resume_changed || is_ro_network_info_changed || is_ro_media_lines_changed)){ - goto end_of_sessions_update; - } - - /* prepare the session manager if not already done (create all sessions with their codecs) - * if network-initiated: think about tmedia_type_from_sdp() before creating the manager */ - if(_tmedia_session_mgr_load_sessions(self)){ - TSK_DEBUG_ERROR("Failed to prepare the session manager"); - ret = -3; - goto bail; - } - - /* get global connection line (common to all sessions) - * Each session should override this info if it has a different one in its "m=" line - * /!\ "remote-ip" is deprecated by "remote-sdp-message" and pending before complete remove - */ - if(C && C->addr){ - tmedia_session_mgr_set(self, - TMEDIA_SESSION_SET_STR(self->type, "remote-ip", C->addr), - TMEDIA_SESSION_SET_NULL()); - } - - /* pass complete remote sdp to the sessions to allow them to use session-level attributes - */ - tmedia_session_mgr_set(self, - TMEDIA_SESSION_SET_POBJECT(self->type, "remote-sdp-message", self->sdp.ro), - TMEDIA_SESSION_SET_NULL()); - - /* foreach "m=" line in the remote offer create/prepare a session (requires the session to be stopped)*/ - index = 0; - while((M = (const tsdp_header_M_t*)tsdp_message_get_headerAt(sdp, tsdp_htype_M, index++))){ - found = tsk_false; - /* Find session by media */ - if((ms = tsk_list_find_object_by_pred(self->sessions, __pred_find_session_by_media, M->media))){ - /* prepare the media session */ - if(!self->started){ - if(!ms->prepared && (_tmedia_session_prepare(TMEDIA_SESSION(ms)))){ - TSK_DEBUG_ERROR("Failed to prepare session"); /* should never happen */ - goto bail; - } - } - /* set remote ro at session-level */ - if((ret = _tmedia_session_set_ro(TMEDIA_SESSION(ms), M)) == 0){ - found = tsk_true; - ++active_sessions_count; - } - else{ - // will send 488 Not Acceptable - TSK_DEBUG_WARN("_tmedia_session_set_ro() failed"); - goto bail; - } - /* set QoS type (only if we are not the offerer) */ - if(/*!self->offerer ==> we suppose that the remote party respected our demand &&*/ qos_type == tmedia_qos_stype_none){ - tmedia_qos_tline_t* tline = tmedia_qos_tline_from_sdp(M); - if(tline){ - qos_type = tline->type; - TSK_OBJECT_SAFE_FREE(tline); - } - } - } - - if(!found && (self->sdp.lo == tsk_null)){ - /* Session not supported and we are not the initial offerer ==> add ghost session */ - /* - An offered stream MAY be rejected in the answer, for any reason. If - a stream is rejected, the offerer and answerer MUST NOT generate - media (or RTCP packets) for that stream. To reject an offered - stream, the port number in the corresponding stream in the answer - MUST be set to zero. Any media formats listed are ignored. AT LEAST - ONE MUST BE PRESENT, AS SPECIFIED BY SDP. - */ - tmedia_session_ghost_t* ghost; - if((ghost = (tmedia_session_ghost_t*)tmedia_session_create(tmedia_ghost))){ - tsk_strupdate(&ghost->media, M->media); /* copy media */ - tsk_strupdate(&ghost->proto, M->proto); /* copy proto */ - if(!TSK_LIST_IS_EMPTY(M->FMTs)){ - tsk_strupdate(&ghost->first_format, ((const tsdp_fmt_t*)TSK_LIST_FIRST_DATA(M->FMTs))->value); /* copy format */ - } - tsk_list_push_back_data(self->sessions, (void**)&ghost); - } - else{ - TSK_DEBUG_ERROR("Failed to create ghost session"); - continue; - } - } - } - -end_of_sessions_update: - - /* update QoS type */ - if(!self->offerer && (qos_type != tmedia_qos_stype_none)){ - self->qos.type = qos_type; - } - - /* signal that ro has changed (will be used to update lo) unless there was no ro_sdp */ - self->ro_changed = (had_ro_sdp && (is_ro_hold_resume_changed || is_ro_network_info_changed || is_ro_media_lines_changed)); - - /* update "provisional" info */ - self->ro_provisional = ((ro_type & tmedia_ro_type_provisional) == tmedia_ro_type_provisional); - - if(self->ro_changed){ - /* update local offer before restarting the session manager otherwise neg_codecs won't match if new codecs - have been added or removed */ - (tmedia_session_mgr_get_lo(self)); - } - /* manager was started and we stopped it in order to reconfigure it (codecs, network, ....) */ - if(stopped_to_reconf){ - if((ret = tmedia_session_mgr_start(self))){ - TSK_DEBUG_ERROR("Failed to re-start session manager"); - goto bail; - } - } - - // will send [488 Not Acceptable] / [BYE] if no active session - ret = (self->ro_changed && active_sessions_count <= 0) ? -0xFF : 0; - -bail: - tsk_safeobj_unlock(self); - return ret; -} - -const tsdp_message_t* tmedia_session_mgr_get_ro(tmedia_session_mgr_t* self) -{ - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return tsk_null; - } - return self->sdp.ro; -} - -/**@ingroup tmedia_session_group -* Holds the session as per 3GPP TS 34.610 -* @param self the session manager managing the session to hold. -* @param type the type of the sessions to hold (you can combine several medias. e.g. audio|video|msrp). -* @retval Zero if succeed and non zero error code otherwise. -* @sa @ref tmedia_session_mgr_resume -*/ -int tmedia_session_mgr_hold(tmedia_session_mgr_t* self, tmedia_type_t type) -{ - const tsk_list_item_t* item; - - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - tsk_list_foreach(item, self->sessions){ - tmedia_session_t* session = TMEDIA_SESSION(item->data); - if(((session->type & type) == session->type) && session->M.lo){ - if(tsdp_header_M_hold(session->M.lo, tsk_true) == 0){ - self->state_changed = tsk_true; - session->lo_held = tsk_true; - } - } - } - return 0; -} - -/**@ingroup tmedia_session_group -* Indicates whether the specified medias are held or not. -* @param self the session manager -* @param type the type of the medias to check (you can combine several medias. e.g. audio|video|msrp) -* @param local whether to check local or remote medias -*/ -tsk_bool_t tmedia_session_mgr_is_held(tmedia_session_mgr_t* self, tmedia_type_t type, tsk_bool_t local) -{ - const tsk_list_item_t* item; - tsk_bool_t have_these_sessions = tsk_false; - - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return tsk_false; - } - - tsk_list_foreach(item, self->sessions){ - tmedia_session_t* session = TMEDIA_SESSION(item->data); - if((session->type & type) == session->type){ - if(local && session->M.lo){ - have_these_sessions = tsk_true; - if(!tsdp_header_M_is_held(session->M.lo, tsk_true)){ - return tsk_false; - } - } - else if(!local && session->M.ro){ - have_these_sessions = tsk_true; - if(!tsdp_header_M_is_held(session->M.ro, tsk_false)){ - return tsk_false; - } - } - } - } - /* none is held */ - return have_these_sessions ? tsk_true : tsk_false; -} - -/**@ingroup tmedia_session_group -* Resumes the session as per 3GPP TS 34.610. Should be previously held -* by using @ref tmedia_session_mgr_hold. -* @param self the session manager managing the session to resume. -* @param type the type of the sessions to resume (you can combine several medias. e.g. audio|video|msrp). -* @retval Zero if succeed and non zero error code otherwise. -* @sa @ref tmedia_session_mgr_hold -*/ -int tmedia_session_mgr_resume(tmedia_session_mgr_t* self, tmedia_type_t type, tsk_bool_t local) -{ - const tsk_list_item_t* item; - int ret = 0; - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - tsk_list_foreach(item, self->sessions){ - tmedia_session_t* session = TMEDIA_SESSION(item->data); - if(((session->type & type) == session->type) && session->M.lo){ - if((ret = tsdp_header_M_resume(session->M.lo, local)) == 0){ - self->state_changed = tsk_true; - if(local){ - session->lo_held = tsk_false; - } - else{ - session->ro_held = tsk_false; - } - } - } - } - return ret; -} - -/**@ingroup tmedia_session_group -* Adds new medias to the manager. A media will only be added if it is missing -* or previously removed (slot with port equal to zero). -* @param self The session manager -* @param The types of the medias to add (ou can combine several medias. e.g. audio|video|msrp) -* @retval Zero if succeed and non zero error code otherwise. -*/ -int tmedia_session_mgr_add_media(tmedia_session_mgr_t* self, tmedia_type_t type) -{ - tsk_size_t i = 0; - tmedia_session_t* session; - const tmedia_session_plugin_def_t* plugin; - - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - /* for each registered plugin match with the supplied type */ - while((i < TMED_SESSION_MAX_PLUGINS) && (plugin = __tmedia_session_plugins[i++])){ - if((plugin->type & type) == plugin->type){ - /* check whether we already support this media */ - if((session = (tmedia_session_t*)tsk_list_find_object_by_pred(self->sessions, __pred_find_session_by_type, &plugin->type)) && session->plugin){ - if(session->prepared){ - TSK_DEBUG_WARN("[%s] already active", plugin->media); - } - else{ - /* exist but unprepared(port=0) */ - _tmedia_session_prepare(session); - if(self->started && session->plugin->start){ - session->plugin->start(session); - } - self->state_changed = tsk_true; - } - } - else{ - /* session not supported */ - self->state_changed = tsk_true; - if((session = tmedia_session_create(plugin->type))){ - if(self->started && session->plugin->start){ - session->plugin->start(session); - } - tsk_list_push_back_data(self->sessions, (void**)(&session)); - self->state_changed = tsk_true; - } - } - } - } - - return self->state_changed ? 0 : -2; -} - -/**@ingroup tmedia_session_group -* Removes medias from the manager. This action will stop the media and sets it's port value to zero (up to the session). -* @param self The session manager -* @param The types of the medias to remove (ou can combine several medias. e.g. audio|video|msrp) -* @retval Zero if succeed and non zero error code otherwise. -*/ -int tmedia_session_mgr_remove_media(tmedia_session_mgr_t* self, tmedia_type_t type) -{ - const tsk_list_item_t* item; - - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - tsk_list_foreach(item, self->sessions){ - tmedia_session_t* session = TMEDIA_SESSION(item->data); - if(((session->type & type) == session->type) && session->plugin->stop){ - if(!session->plugin->stop(session)){ - self->state_changed = tsk_true; - } - } - } - return 0; -} - -/**@ingroup tmedia_session_group -* Sets QoS type and strength -* @param self The session manager -* @param qos_type The QoS type -* @param qos_strength The QoS strength -* @retval Zero if succeed and non-zero error code otherwise -*/ -int tmedia_session_mgr_set_qos(tmedia_session_mgr_t* self, tmedia_qos_stype_t qos_type, tmedia_qos_strength_t qos_strength) -{ - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - self->qos.type = qos_type; - self->qos.strength = qos_strength; - return 0; -} - -/**@ingroup tmedia_session_group -* Indicates whether all preconditions are met -* @param self The session manager -* @retval @a tsk_true if all preconditions have been met and @a tsk_false otherwise -*/ -tsk_bool_t tmedia_session_mgr_canresume(tmedia_session_mgr_t* self) -{ - const tsk_list_item_t* item; - - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return tsk_true; - } - - tsk_list_foreach(item, self->sessions){ - tmedia_session_t* session = TMEDIA_SESSION(item->data); - if(session && session->qos && !tmedia_qos_tline_canresume(session->qos)){ - return tsk_false; - } - } - return tsk_true; -} - - -/**@ingroup tmedia_session_group -* Checks whether the manager holds at least one valid session (media port <> 0) -*/ -tsk_bool_t tmedia_session_mgr_has_active_session(tmedia_session_mgr_t* self) -{ - const tsk_list_item_t* item; - - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return tsk_false; - } - - tsk_list_foreach(item, self->sessions){ - tmedia_session_t* session = TMEDIA_SESSION(item->data); - if(session && session->M.lo && session->M.lo->port){ - return tsk_true; - } - } - return tsk_false; -} - -int tmedia_session_mgr_send_dtmf(tmedia_session_mgr_t* self, uint8_t event) -{ - tmedia_session_audio_t* session; - static const tmedia_type_t audio_type = tmedia_audio; - int ret = -3; - - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - session = (tmedia_session_audio_t*)tsk_list_find_object_by_pred(self->sessions, __pred_find_session_by_type, &audio_type); - if(session){ - session = tsk_object_ref(session); - ret = tmedia_session_audio_send_dtmf(TMEDIA_SESSION_AUDIO(session), event); - TSK_OBJECT_SAFE_FREE(session); - } - else{ - TSK_DEBUG_ERROR("No audio session associated to this manager"); - } - - return ret; -} - -int tmedia_session_mgr_set_t140_ondata_cbfn(tmedia_session_mgr_t* self, const void* context, tmedia_session_t140_ondata_cb_f func) -{ - tmedia_session_t* session; - int ret = -1; - if((session = tmedia_session_mgr_find(self, tmedia_t140))){ - ret = tmedia_session_t140_set_ondata_cbfn(session, context, func); - TSK_OBJECT_SAFE_FREE(session); - } - return ret; -} - -int tmedia_session_mgr_send_t140_data(tmedia_session_mgr_t* self, enum tmedia_t140_data_type_e data_type, const void* data_ptr, unsigned data_size) -{ - tmedia_session_t* session; - int ret = -1; - if((session = tmedia_session_mgr_find(self, tmedia_t140))){ - ret = tmedia_session_t140_send_data(session, data_type, data_ptr, data_size); - TSK_OBJECT_SAFE_FREE(session); - } - return ret; -} - -int tmedia_session_mgr_set_onrtcp_cbfn(tmedia_session_mgr_t* self, tmedia_type_t media_type, const void* context, tmedia_session_rtcp_onevent_cb_f fun) -{ - tmedia_session_t* session; - tsk_list_item_t *item; - - if(!self){ - TSK_DEBUG_ERROR("Invlid parameter"); - return -1; - } - - tsk_list_lock(self->sessions); - tsk_list_foreach(item, self->sessions){ - if(!(session = item->data) || !(session->type & media_type)){ - continue; - } - tmedia_session_set_onrtcp_cbfn(session, context, fun); - } - tsk_list_unlock(self->sessions); - - return 0; -} - -int tmedia_session_mgr_send_rtcp_event(tmedia_session_mgr_t* self, tmedia_type_t media_type, tmedia_rtcp_event_type_t event_type, uint32_t ssrc_media) -{ - tmedia_session_t* session; - tsk_list_item_t *item; - - if(!self){ - TSK_DEBUG_ERROR("Invlid parameter"); - return -1; - } - - tsk_list_lock(self->sessions); - tsk_list_foreach(item, self->sessions){ - if(!(session = item->data) || !(session->type & media_type)){ - continue; - } - tmedia_session_send_rtcp_event(session, event_type, ssrc_media); - } - tsk_list_unlock(self->sessions); - - return 0; -} - -int tmedia_session_mgr_send_file(tmedia_session_mgr_t* self, const char* path, ...) -{ - tmedia_session_msrp_t* session; - tmedia_type_t msrp_type = tmedia_msrp; - int ret = -3; - - if(!self || !path){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - session = (tmedia_session_msrp_t*)tsk_list_find_object_by_pred(self->sessions, __pred_find_session_by_type, &msrp_type); - if(session && session->send_file){ - va_list ap; - va_start(ap, path); - session = tsk_object_ref(session); - ret = session->send_file(TMEDIA_SESSION_MSRP(session), path, &ap); - TSK_OBJECT_SAFE_FREE(session); - va_end(ap); - } - else{ - TSK_DEBUG_ERROR("No MSRP session associated to this manager or session does not support file transfer"); - } - - return ret; -} - -int tmedia_session_mgr_send_message(tmedia_session_mgr_t* self, const void* data, tsk_size_t size, const tmedia_params_L_t *params) -{ - tmedia_session_msrp_t* session; - tmedia_type_t msrp_type = tmedia_msrp; - int ret = -3; - - if(!self || !size || !data){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - session = (tmedia_session_msrp_t*)tsk_list_find_object_by_pred(self->sessions, __pred_find_session_by_type, &msrp_type); - if(session && session->send_message){ - session = tsk_object_ref(session); - ret = session->send_message(TMEDIA_SESSION_MSRP(session), data, size, params); - TSK_OBJECT_SAFE_FREE(session); - } - else{ - TSK_DEBUG_ERROR("No MSRP session associated to this manager or session does not support file transfer"); - } - - return ret; -} - -int tmedia_session_mgr_set_msrp_cb(tmedia_session_mgr_t* self, const void* callback_data, tmedia_session_msrp_cb_f func) -{ - tmedia_session_msrp_t* session; - tmedia_type_t msrp_type = tmedia_msrp; - - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - if((session = (tmedia_session_msrp_t*)tsk_list_find_object_by_pred(self->sessions, __pred_find_session_by_type, &msrp_type))){ - session->callback.data = callback_data; - session->callback.func = func; - return 0; - } - else{ - TSK_DEBUG_ERROR("No MSRP session associated to this manager or session does not support file transfer"); - return -2; - } -} - -int tmedia_session_mgr_set_onerror_cbfn(tmedia_session_mgr_t* self, const void* usrdata, tmedia_session_onerror_cb_f fun) -{ - tmedia_session_t* session; - tsk_list_item_t *item; - - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - self->onerror_cb.fun = fun; - self->onerror_cb.usrdata = usrdata; - - tsk_list_lock(self->sessions); - tsk_list_foreach(item, self->sessions){ - if(!(session = item->data)){ - continue; - } - tmedia_session_set_onerror_cbfn(session, usrdata, fun); - } - tsk_list_unlock(self->sessions); - - return 0; -} - -/** internal function used to load sessions */ -static int _tmedia_session_mgr_load_sessions(tmedia_session_mgr_t* self) -{ - tsk_size_t i = 0; - tmedia_session_t* session; - const tmedia_session_plugin_def_t* plugin; - -#define has_media(media_type) (tsk_list_find_object_by_pred(self->sessions, __pred_find_session_by_type, &(media_type))) - - if(TSK_LIST_IS_EMPTY(self->sessions) || self->mediaType_changed){ - /* for each registered plugin create a session instance */ - while((i < TMED_SESSION_MAX_PLUGINS) && (plugin = __tmedia_session_plugins[i++])){ - if((plugin->type & self->type) == plugin->type && !has_media(plugin->type)){// we don't have a session with this media type yet - if((session = tmedia_session_create(plugin->type))){ - /* do not call "tmedia_session_mgr_set()" here to avoid applying parms before the creation of all session */ - - /* set other default values */ - - // set callback functions - tmedia_session_set_onerror_cbfn(session, self->onerror_cb.usrdata, self->onerror_cb.fun); - - /* push session */ - tsk_list_push_back_data(self->sessions, (void**)(&session)); - } - } - else if(!(plugin->type & self->type) && has_media(plugin->type)){// we have media session from previous call (before update) - tsk_list_remove_item_by_pred(self->sessions, __pred_find_session_by_type, &(plugin->type)); - } - } - /* set default values and apply params*/ - tmedia_session_mgr_set(self, - TMEDIA_SESSION_SET_POBJECT(tmedia_audio, "ice-ctx", self->ice.ctx_audio), - TMEDIA_SESSION_SET_POBJECT(tmedia_video, "ice-ctx", self->ice.ctx_video), - - TMEDIA_SESSION_SET_STR(self->type, "local-ip", self->addr), - TMEDIA_SESSION_SET_STR(self->type, "local-ipver", self->ipv6 ? "ipv6" : "ipv4"), - TMEDIA_SESSION_SET_INT32(self->type, "bandwidth-level", self->bl), - TMEDIA_SESSION_SET_NULL()); - } -#undef has_media - return 0; -} - -/* internal function */ -static int _tmedia_session_mgr_clear_sessions(tmedia_session_mgr_t* self) -{ - if(self && self->sessions){ - tsk_list_clear_items(self->sessions); - } - return 0; -} - -/* internal function */ -static int _tmedia_session_mgr_apply_params(tmedia_session_mgr_t* self) -{ - tsk_list_item_t *it1, *it2; - tmedia_param_t* param; - tmedia_session_t* session; - - if(!self){ - TSK_DEBUG_ERROR("Invalid parameter"); - return -1; - } - - /* If no parameters ==> do nothing (not error) */ - if(TSK_LIST_IS_EMPTY(self->params)){ - return 0; - } - - tsk_list_lock(self->params); - - tsk_list_foreach(it1, self->params){ - if(!(param = it1->data)){ - continue; - } - - /* For us */ - if(param->plugin_type == tmedia_ppt_manager){ - continue; - } - - /* For the session (or consumer or producer or codec) */ - tsk_list_foreach(it2, self->sessions){ - if(!(session = it2->data) || !session->plugin){ - continue; - } - if(session->plugin->set && (session->type & param->media_type) == session->type){ - session->plugin->set(session, param); - } - } - } - - /* Clean up params */ - tsk_list_clear_items(self->params); - - tsk_list_unlock(self->params); - - return 0; -} - -//================================================================================================= -// Media Session Manager object definition -// -static tsk_object_t* tmedia_session_mgr_ctor(tsk_object_t * self, va_list * app) -{ - tmedia_session_mgr_t *mgr = self; - if(mgr){ - mgr->sessions = tsk_list_create(); - - mgr->sdp.lo_ver = TSDP_HEADER_O_SESS_VERSION_DEFAULT; - mgr->sdp.ro_ver = -1; - - mgr->qos.type = tmedia_qos_stype_none; - mgr->qos.strength = tmedia_qos_strength_optional; - mgr->bl = tmedia_defaults_get_bl(); - - tsk_safeobj_init(mgr); - } - return self; -} - -static tsk_object_t* tmedia_session_mgr_dtor(tsk_object_t * self) -{ - tmedia_session_mgr_t *mgr = self; - if(mgr){ - TSK_OBJECT_SAFE_FREE(mgr->sessions); - - TSK_OBJECT_SAFE_FREE(mgr->sdp.lo); - TSK_OBJECT_SAFE_FREE(mgr->sdp.ro); - - TSK_OBJECT_SAFE_FREE(mgr->params); - - TSK_OBJECT_SAFE_FREE(mgr->natt_ctx); - TSK_FREE(mgr->public_addr); - - TSK_OBJECT_SAFE_FREE(mgr->ice.ctx_audio); - TSK_OBJECT_SAFE_FREE(mgr->ice.ctx_video); - - TSK_FREE(mgr->addr); - - tsk_safeobj_deinit(mgr); - } - - return self; -} - -static const tsk_object_def_t tmedia_session_mgr_def_s = -{ - sizeof(tmedia_session_mgr_t), - tmedia_session_mgr_ctor, - tmedia_session_mgr_dtor, - tsk_null, -}; -const tsk_object_def_t *tmedia_session_mgr_def_t = &tmedia_session_mgr_def_s; - +/* +* Copyright (C) 2010-2011 Mamadou Diop. +* +* Contact: Mamadou Diop +* +* This file is part of Open Source Doubango Framework. +* +* DOUBANGO 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 3 of the License, or +* (at your option) any later version. +* +* DOUBANGO 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 DOUBANGO. +* +*/ + +/**@file tmedia_session.h + * @brief Base session object. + * + * @author Mamadou Diop + * + + */ +#include "tinymedia/tmedia_session.h" + +#include "tinymedia/tmedia_session_ghost.h" +#include "tinymedia/tmedia_defaults.h" + +#include "tinysdp/headers/tsdp_header_O.h" + +#include "tsk_memory.h" +#include "tsk_debug.h" + +/**@defgroup tmedia_session_group Media Session +* For more information about the SOA, please refer to http://betelco.blogspot.com/2010/03/sdp-offeranswer-soa_2993.html +*/ + +#if !defined(va_copy) +# define va_copy(D, S) ((D) = (S)) +#endif + +extern const tmedia_codec_plugin_def_t* __tmedia_codec_plugins[TMED_CODEC_MAX_PLUGINS]; + +/* pointer to all registered sessions */ +const tmedia_session_plugin_def_t* __tmedia_session_plugins[TMED_SESSION_MAX_PLUGINS] = {0}; + +/* === local functions === */ +static int _tmedia_session_mgr_load_sessions(tmedia_session_mgr_t* self); +static int _tmedia_session_mgr_clear_sessions(tmedia_session_mgr_t* self); +static int _tmedia_session_mgr_apply_params(tmedia_session_mgr_t* self); +static int _tmedia_session_prepare(tmedia_session_t* self); +static int _tmedia_session_set_ro(tmedia_session_t* self, const tsdp_header_M_t* m); +static int _tmedia_session_load_codecs(tmedia_session_t* self); + +const char* tmedia_session_get_media(const tmedia_session_t* self); +const tsdp_header_M_t* tmedia_session_get_lo(tmedia_session_t* self); +int tmedia_session_set_ro(tmedia_session_t* self, const tsdp_header_M_t* m); + + +/*== Predicate function to find session object by media */ +static int __pred_find_session_by_media(const tsk_list_item_t *item, const void *media) +{ + if(item && item->data){ + return tsk_stricmp(tmedia_session_get_media((const tmedia_session_t *)item->data), (const char*)media); + } + return -1; +} + +/*== Predicate function to find session object by type */ +static int __pred_find_session_by_type(const tsk_list_item_t *item, const void *type) +{ + if(item && item->data){ + return ((const tmedia_session_t *)item->data)->type - *((tmedia_type_t*)type); + } + return -1; +} + +/*== Predicate function to find codec object by address */ +int __pred_find_codec_by_format(const tsk_list_item_t *item, const void *codec) +{ + if(item && item->data && codec){ + return tsk_stricmp(((const tmedia_codec_t*)item->data)->format, ((const tmedia_codec_t*)codec)->format); + } + return -1; +} + +uint64_t tmedia_session_get_unique_id(){ + static uint64_t __UniqueId = 1; // MUST not be equal to zero + return __UniqueId++; +} + +/**@ingroup tmedia_session_group +* Initializes a newly created media session. +* @param self the media session to initialize. +* @param type the type of the session to initialize. +* @retval Zero if succeed and non-zero error code otherwise. +*/ +int tmedia_session_init(tmedia_session_t* self, tmedia_type_t type) +{ + int ret = 0; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if(!self->initialized){ + /* set values */ + if(!self->id){ + self->id = tmedia_session_get_unique_id(); + } + self->type = type; + self->initialized = tsk_true; + self->bl = tmedia_defaults_get_bl(); + self->codecs_allowed = tmedia_codec_id_all; + self->bypass_encoding = tmedia_defaults_get_bypass_encoding(); + self->bypass_decoding = tmedia_defaults_get_bypass_decoding(); + /* load associated codecs */ + ret = _tmedia_session_load_codecs(self); + } + + return 0; +} + +int tmedia_session_set(tmedia_session_t* self, ...) +{ + va_list ap; + tmedia_params_L_t* params; + + if(!self || !self->plugin || !self->plugin->set){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + va_start(ap, self); + if((params = tmedia_params_create_2(&ap))){ + const tsk_list_item_t *item; + const tmedia_param_t* param; + tsk_list_foreach(item, params){ + if(!(param = item->data)){ + continue; + } + if((self->type & param->media_type)){ + self->plugin->set(self, param); + } + } + TSK_OBJECT_SAFE_FREE(params); + } + va_end(ap); + return 0; +} + +tsk_bool_t tmedia_session_set_2(tmedia_session_t* self, const tmedia_param_t* param) +{ + if(!self || !param){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_false; + } + + if(param->plugin_type == tmedia_ppt_session){ + if(param->value_type == tmedia_pvt_int32){ + if(tsk_striequals(param->key, "codecs-supported")){ + //if(self->M.lo){ + // TSK_DEBUG_WARN("Cannot change codec values at this stage"); + //} + //else{ + self->codecs_allowed = *((int32_t*)param->value); + return (_tmedia_session_load_codecs(self) == 0); + //} + return tsk_true; + } + else if(tsk_striequals(param->key, "bypass-encoding")){ + self->bypass_encoding = *((int32_t*)param->value); + return tsk_true; + } + else if(tsk_striequals(param->key, "bypass-decoding")){ + self->bypass_decoding = *((int32_t*)param->value); + return tsk_true; + } + else if(tsk_striequals(param->key, "dtls-cert-verify")){ + self->dtls.verify = *((int32_t*)param->value) ? tsk_true : tsk_false; + return tsk_true; + } + } + else if(param->value_type == tmedia_pvt_pchar){ + if(tsk_striequals(param->key, "dtls-file-ca")){ + tsk_strupdate(&self->dtls.file_ca, param->value); + return tsk_true; + } + else if(tsk_striequals(param->key, "dtls-file-pbk")){ + tsk_strupdate(&self->dtls.file_pbk, param->value); + return tsk_true; + } + else if(tsk_striequals(param->key, "dtls-file-pvk")){ + tsk_strupdate(&self->dtls.file_pvk, param->value); + return tsk_true; + } + } + } + + return tsk_false; +} + +tsk_bool_t tmedia_session_get(tmedia_session_t* self, tmedia_param_t* param) +{ + if(!self || !param){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_false; + } + + if(param->plugin_type == tmedia_ppt_session){ + if(param->value_type == tmedia_pvt_int32){ + if(tsk_striequals(param->key, "codecs-negotiated")){ // negotiated codecs + tmedia_codecs_L_t* neg_codecs = tsk_object_ref(self->neg_codecs); + if(neg_codecs){ + const tsk_list_item_t* item; + tsk_list_foreach(item, neg_codecs){ + ((int32_t*)param->value)[0] |= TMEDIA_CODEC(item->data)->id; + } + TSK_OBJECT_SAFE_FREE(neg_codecs); + } + return tsk_true; + } + } + } + + return tsk_false; +} + +/**@ingroup tmedia_session_group +* Generic function to compare two sessions. +* @param sess1 The first session to compare. +* @param sess2 The second session to compare. +* @retval Returns an integral value indicating the relationship between the two sessions: +* <0 : @a sess1 less than @a sess2.
+* 0 : @a sess1 identical to @a sess2.
+* >0 : @a sess1 greater than @a sess2.
+*/ +int tmedia_session_cmp(const tsk_object_t* sess1, const tsk_object_t* sess2) +{ + return (TMEDIA_SESSION(sess1) - TMEDIA_SESSION(sess2)); +} + +/**@ingroup tmedia_session_group +* Registers a session plugin. +* @param plugin the definition of the plugin. +* @retval Zero if succeed and non-zero error code otherwise. +* @sa @ref tmedia_session_create() +*/ +int tmedia_session_plugin_register(const tmedia_session_plugin_def_t* plugin) +{ + tsk_size_t i; + if(!plugin){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* add or replace the plugin */ + for(i = 0; imedia, media)){ + return __tmedia_session_plugins[i]; + } + i++; + } + return tsk_null; +} + +/**@ingroup tmedia_session_group +* UnRegisters a session plugin. +* @param plugin the definition of the plugin. +* @retval Zero if succeed and non-zero error code otherwise. +*/ +int tmedia_session_plugin_unregister(const tmedia_session_plugin_def_t* plugin) +{ + tsk_size_t i; + tsk_bool_t found = tsk_false; + if(!plugin){ + TSK_DEBUG_ERROR("Invalid Parameter"); + return -1; + } + + /* find the plugin to unregister */ + for(i = 0; iobjdef && (plugin->type == type)){ + if((session = tsk_object_new(plugin->objdef))){ + if(!session->initialized){ + tmedia_session_init(session, type); + } + session->plugin = plugin; + } + break; + } + } + return session; +} + +/* internal funtion: prepare lo */ +static int _tmedia_session_prepare(tmedia_session_t* self) +{ + int ret; + if(!self || !self->plugin || !self->plugin->prepare){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + if(self->prepared){ + TSK_DEBUG_WARN("Session already prepared"); + return 0; + } + if((ret = self->plugin->prepare(self))){ + TSK_DEBUG_ERROR("Failed to prepare the session"); + } + else{ + self->prepared = tsk_true; + } + return ret; +} + +/* internal function used to set remote offer */ +int _tmedia_session_set_ro(tmedia_session_t* self, const tsdp_header_M_t* m) +{ + int ret; + if(!self || !self->plugin || !self->plugin->set_remote_offer){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + if(!(ret = self->plugin->set_remote_offer(self, m))){ + self->ro_changed = tsk_true; + self->ro_held = tsdp_header_M_is_held(m, tsk_false); + } + return ret; +} + +/* internal function: get media */ +const char* tmedia_session_get_media(const tmedia_session_t* self) +{ + if(!self || !self->plugin){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_null; + } + + /* ghost? */ + if(self->plugin == tmedia_session_ghost_plugin_def_t){ + return ((const tmedia_session_ghost_t*)self)->media; + } + else{ + return self->plugin->media; + } +} +/* internal function: get local offer */ +const tsdp_header_M_t* tmedia_session_get_lo(tmedia_session_t* self) +{ + const tsdp_header_M_t* m; + + if(!self || !self->plugin || !self->plugin->get_local_offer){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_null; + } + + if((m = self->plugin->get_local_offer(self))){ + self->ro_changed = tsk_false; /* we should have a fresh local offer (based on the latest ro) */ + } + return m; +} + +/* Match a codec */ +tmedia_codecs_L_t* tmedia_session_match_codec(tmedia_session_t* self, const tsdp_header_M_t* M) +{ + const tmedia_codec_t *codec; + char *rtpmap = tsk_null, *fmtp = tsk_null, *image_attr = tsk_null, *name = tsk_null; + const tsdp_fmt_t* fmt; + const tsk_list_item_t *it1, *it2; + tsk_bool_t found = tsk_false; + tmedia_codecs_L_t* matchingCodecs = tsk_null; + + if(!self || !M){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_null; + } + + + /* foreach format */ + tsk_list_foreach(it1, M->FMTs){ + fmt = it1->data; + + /* foreach codec */ + tsk_list_foreach(it2, self->codecs){ + /* 'tmedia_codec_id_none' is used for fake codecs (e.g. dtmf or msrp) and should not be filtered beacuse of backward compatibility*/ + if(!(codec = it2->data) || !codec->plugin || !(codec->id == tmedia_codec_id_none || (codec->id & self->codecs_allowed))){ + continue; + } + + // Guard to avoid matching a codec more than once + // For example, H.264 codecs without profiles (Jitsi, Tiscali PC client) to distinguish them could match more than once + if(matchingCodecs && tsk_list_find_object_by_pred(matchingCodecs, __pred_find_codec_by_format, codec)){ + continue; + } + + // Dyn. payload type + if(codec->dyn && (rtpmap = tsdp_header_M_get_rtpmap(M, fmt->value))){ + int32_t rate, channels; + /* parse rtpmap */ + if(tmedia_parse_rtpmap(rtpmap, &name, &rate, &channels)){ + goto next; + } + + /* compare name and rate... what about channels? */ + if(tsk_striequals(name, codec->name) && (!rate || !codec->plugin->rate || (codec->plugin->rate == rate))){ + goto compare_fmtp; + } + } + // Fixed payload type + else{ + if(tsk_striequals(fmt->value, codec->format)){ + goto compare_fmtp; + } + } + + /* rtpmap do not match: free strings and try next codec */ + goto next; + +compare_fmtp: + if((fmtp = tsdp_header_M_get_fmtp(M, fmt->value))){ /* remote have fmtp? */ + if(tmedia_codec_sdp_att_match(codec, "fmtp", fmtp)){ /* fmtp matches? */ + if(codec->type & tmedia_video) goto compare_imageattr; + else found = tsk_true; + } + else goto next; + } + else{ /* no fmtp -> always match */ + if(codec->type & tmedia_video) goto compare_imageattr; + else found = tsk_true; + } + +compare_imageattr: + if(codec->type & tmedia_video){ + if((image_attr = tsdp_header_M_get_imageattr(M, fmt->value))){ + if(tmedia_codec_sdp_att_match(codec, "imageattr", image_attr)) found = tsk_true; + } + else found = tsk_true; + } + + // update neg. format + if(found) tsk_strupdate((char**)&codec->neg_format, fmt->value); + +next: + TSK_FREE(name); + TSK_FREE(fmtp); + TSK_FREE(rtpmap); + TSK_FREE(image_attr); + if(found){ + tmedia_codec_t * copy; + if(!matchingCodecs){ + matchingCodecs = tsk_list_create(); + } + copy = tsk_object_ref((void*)codec); + tsk_list_push_back_data(matchingCodecs, (void**)©); + + found = tsk_false; + break; + } + } + } + + + return matchingCodecs; +} + +int tmedia_session_set_onrtcp_cbfn(tmedia_session_t* self, const void* context, tmedia_session_rtcp_onevent_cb_f func) +{ + if(self && self->plugin && self->plugin->rtcp.set_onevent_cbfn){ + return self->plugin->rtcp.set_onevent_cbfn(self, context, func); + } + return -1; +} + +int tmedia_session_send_rtcp_event(tmedia_session_t* self, tmedia_rtcp_event_type_t event_type, uint32_t ssrc_media) +{ + if(self && self->plugin && self->plugin->rtcp.send_event){ + return self->plugin->rtcp.send_event(self, event_type, ssrc_media); + } + TSK_DEBUG_INFO("Not sending RTCP event with SSRC = %u because no callback function found", ssrc_media); + return -1; +} + +int tmedia_session_set_onerror_cbfn(tmedia_session_t* self, const void* usrdata, tmedia_session_onerror_cb_f fun) +{ + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + self->onerror_cb.fun = fun; + self->onerror_cb.usrdata = usrdata; + return 0; +} + +/**@ingroup tmedia_session_group +* DeInitializes a media session. +* @param self the media session to deinitialize. +* @retval Zero if succeed and non-zero error code otherwise. +*/ +int tmedia_session_deinit(tmedia_session_t* self) +{ + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* free codecs */ + TSK_OBJECT_SAFE_FREE(self->codecs); + TSK_OBJECT_SAFE_FREE(self->neg_codecs); + + /* free lo, no and ro */ + TSK_OBJECT_SAFE_FREE(self->M.lo); + TSK_OBJECT_SAFE_FREE(self->M.ro); + + /* QoS */ + TSK_OBJECT_SAFE_FREE(self->qos); + + /* DTLS */ + TSK_FREE(self->dtls.file_ca); + TSK_FREE(self->dtls.file_pbk); + TSK_FREE(self->dtls.file_pvk); + + return 0; +} + +/**@ingroup tmedia_session_group +* Send DTMF event +* @param self the audio session to use to send a DTMF event +* @param event the DTMF event to send (should be between 0-15) +* @retval Zero if succeed and non-zero error code otherwise. +*/ +int tmedia_session_audio_send_dtmf(tmedia_session_audio_t* self, uint8_t event) +{ + if(!self || !TMEDIA_SESSION(self)->plugin || !TMEDIA_SESSION(self)->plugin->audio.send_dtmf){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + return TMEDIA_SESSION(self)->plugin->audio.send_dtmf(TMEDIA_SESSION(self), event); +} + +int tmedia_session_t140_set_ondata_cbfn(tmedia_session_t* self, const void* context, tmedia_session_t140_ondata_cb_f func) +{ + if(self && self->plugin && self->plugin->t140.set_ondata_cbfn){ + return self->plugin->t140.set_ondata_cbfn(self, context, func); + } + return -1; +} + +int tmedia_session_t140_send_data(tmedia_session_t* self, enum tmedia_t140_data_type_e data_type, const void* data_ptr, unsigned data_size) +{ + if(self && self->plugin && self->plugin->t140.send_data){ + return self->plugin->t140.send_data(self, data_type, data_ptr, data_size); + } + return -1; +} + +/* internal function used to prepare a session */ +int _tmedia_session_load_codecs(tmedia_session_t* self) +{ + tsk_size_t i = 0; + tmedia_codec_t* codec; + const tmedia_codec_plugin_def_t* plugin; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if(!self->codecs && !(self->codecs = tsk_list_create())){ + TSK_DEBUG_ERROR("Failed to create new list"); + return -1; + } + + tsk_list_lock(self->codecs); + + /* remove old codecs */ + tsk_list_clear_items(self->codecs); + + /* for each registered plugin create a session instance */ + while((i < TMED_CODEC_MAX_PLUGINS) && (plugin = __tmedia_codec_plugins[i++])){ + /* 'tmedia_codec_id_none' is used for fake codecs (e.g. dtmf or msrp) and should not be filtered beacuse of backward compatibility*/ + if((plugin->type & self->type) && (plugin->codec_id == tmedia_codec_id_none || (plugin->codec_id & self->codecs_allowed))){ + if((codec = tmedia_codec_create(plugin->format))){ + if(!self->codecs){ + self->codecs = tsk_list_create(); + } + tsk_list_push_back_data(self->codecs, (void**)(&codec)); + } + } + } + + tsk_list_unlock(self->codecs); + + return 0; +} + + +/**@ingroup tmedia_session_group +* Creates new session manager. +* @param type the type of the session to create. For example, (@ref tmed_sess_type_audio | @ref tmed_sess_type_video). +* @param addr the local ip address or FQDN to use in the sdp message. +* @param ipv6 indicates whether @a addr is IPv6 address or not. Useful when @a addr is a FQDN. +* @param load_sessions Whether the offerer or not. +* will create an audio/video session. +* @retval new @ref tmedia_session_mgr_t object +*/ +tmedia_session_mgr_t* tmedia_session_mgr_create(tmedia_type_t type, const char* addr, tsk_bool_t ipv6, tsk_bool_t offerer) +{ + tmedia_session_mgr_t* mgr; + + if(!(mgr = tsk_object_new(tmedia_session_mgr_def_t))){ + TSK_DEBUG_ERROR("Failed to create Media Session manager"); + return tsk_null; + } + + /* init */ + mgr->type = type; + mgr->addr = tsk_strdup(addr); + mgr->ipv6 = ipv6; + + /* load sessions (will allow us to generate lo) */ + if(offerer){ + mgr->offerer = tsk_true; + //if(_tmedia_session_mgr_load_sessions(mgr)){ + /* Do nothing */ + // TSK_DEBUG_ERROR("Failed to load sessions"); + //} + } + + return mgr; +} + +/**@ingroup tmedia_session_group + */ +int tmedia_session_mgr_set_media_type(tmedia_session_mgr_t* self, tmedia_type_t type) +{ + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + if(self->type != type){ + self->mediaType_changed = tsk_true; + self->type = type; + } + return 0; +} + +/**@ingroup tmedia_session_group +*/ +tmedia_session_t* tmedia_session_mgr_find(tmedia_session_mgr_t* self, tmedia_type_t type) +{ + tmedia_session_t* session; + + tsk_list_lock(self->sessions); + session = (tmedia_session_t*)tsk_list_find_object_by_pred(self->sessions, __pred_find_session_by_type, &type); + tsk_list_unlock(self->sessions); + + return tsk_object_ref(session); +} + +/**@ingroup tmedia_session_group +*/ +int tmedia_session_mgr_set_natt_ctx(tmedia_session_mgr_t* self, tnet_nat_context_handle_t* natt_ctx, const char* public_addr) +{ + if(!self || !natt_ctx){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + TSK_OBJECT_SAFE_FREE(self->natt_ctx); + self->natt_ctx = tsk_object_ref(natt_ctx); + tsk_strupdate(&self->public_addr, public_addr); + + tmedia_session_mgr_set(self, + TMEDIA_SESSION_SET_POBJECT(self->type, "natt-ctx", self->natt_ctx), + TMEDIA_SESSION_SET_NULL()); + return 0; +} + +int tmedia_session_mgr_set_ice_ctx(tmedia_session_mgr_t* self, struct tnet_ice_ctx_s* ctx_audio, struct tnet_ice_ctx_s* ctx_video) +{ + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + TSK_OBJECT_SAFE_FREE(self->ice.ctx_audio); + TSK_OBJECT_SAFE_FREE(self->ice.ctx_video); + + self->ice.ctx_audio = tsk_object_ref(ctx_audio); + self->ice.ctx_video = tsk_object_ref(ctx_video); + + if(self->type & tmedia_audio){ + tmedia_session_mgr_set(self, + TMEDIA_SESSION_SET_POBJECT(tmedia_audio, "ice-ctx", self->ice.ctx_audio), + TMEDIA_SESSION_SET_NULL()); + } + + if(self->type & tmedia_video){ + tmedia_session_mgr_set(self, + TMEDIA_SESSION_SET_POBJECT(tmedia_video, "ice-ctx", self->ice.ctx_video), + TMEDIA_SESSION_SET_NULL()); + } + + return 0; +} + +/**@ingroup tmedia_session_group +* Starts the session manager by starting all underlying sessions. +* You should set both remote and local offers before calling this function. +* @param self The session manager to start. +* @retval Zero if succced and non-zero error code otherwise. +* +* @sa @ref tmedia_session_mgr_stop +*/ +int tmedia_session_mgr_start(tmedia_session_mgr_t* self) +{ + int ret = 0; + tsk_list_item_t* item; + tmedia_session_t* session; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + tsk_safeobj_lock(self); + + if(self->started){ + goto bail; + } + + tsk_list_foreach(item, self->sessions){ + if(!(session = item->data) || !session->plugin || !session->plugin->start){ + TSK_DEBUG_ERROR("Invalid session"); + ret = -2; + goto bail; + } + if((ret = session->plugin->start(session))){ + TSK_DEBUG_ERROR("Failed to start %s session", session->plugin->media); + continue; + } + } + + self->started = tsk_true; + +bail: + tsk_safeobj_unlock(self); + return ret; +} + +/**@ingroup tmedia_session_group +* sets parameters for one or several sessions. +* @param self The session manager +* @param ... Any TMEDIA_SESSION_SET_*() macros +* @retval Zero if succeed and non-zero error code otherwise +*/ +int tmedia_session_mgr_set(tmedia_session_mgr_t* self, ...) +{ + va_list ap; + int ret; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + va_start(ap, self); + ret = tmedia_session_mgr_set_2(self, &ap); + va_end(ap); + + return ret; +} + +/**@ingroup tmedia_session_group +* sets parameters for one or several sessions. +* @param self The session manager +* @param app List of parameters. +* @retval Zero if succeed and non-zero error code otherwise +*/ +int tmedia_session_mgr_set_2(tmedia_session_mgr_t* self, va_list *app) +{ + tmedia_params_L_t* params; + + if(!self || !app){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if((params = tmedia_params_create_2(app))){ + if(!self->params){ + self->params = tsk_object_ref(params); + } + else{ + tsk_list_pushback_list(self->params, params); + } + TSK_OBJECT_SAFE_FREE(params); + } + + /* load params if we already have sessions */ + if(!TSK_LIST_IS_EMPTY(self->sessions)){ + _tmedia_session_mgr_apply_params(self); + } + + return 0; +} + +/**@ingroup tmedia_session_group +* sets parameters for one or several sessions. +* @param self The session manager +* @param params List of parameters to set +* @retval Zero if succeed and non-zero error code otherwise +*/ +int tmedia_session_mgr_set_3(tmedia_session_mgr_t* self, const tmedia_params_L_t* params) +{ + if(!self || !params){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if(!self->params){ + self->params = tsk_list_create(); + } + tsk_list_pushback_list(self->params, params); + + /* load params if we already have sessions */ + if(!TSK_LIST_IS_EMPTY(self->sessions)){ + _tmedia_session_mgr_apply_params(self); + } + + return 0; +} + +int tmedia_session_mgr_get(tmedia_session_mgr_t* self, ...) +{ + va_list ap; + int ret = 0; + tmedia_params_L_t* params; + const tsk_list_item_t *item1, *item2; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + va_start(ap, self); + + if((params = tmedia_params_create_2(&ap))){ + tmedia_session_t* session; + tmedia_param_t* param; + tsk_list_foreach(item2, params){ + if((param = item2->data)){ + tsk_list_foreach(item1, self->sessions){ + if(!(session = (tmedia_session_t*)item1->data) || !session->plugin){ + continue; + } + if((session->type & param->media_type) == session->type && session->plugin->set){ + ret = session->plugin->get(session, param); + } + } + } + } + TSK_OBJECT_SAFE_FREE(params); + } + + va_end(ap); + + return ret; +} + +/**@ingroup tmedia_session_group +* Stops the session manager by stopping all underlying sessions. +* @param self The session manager to stop. +* @retval Zero if succced and non-zero error code otherwise. +* +* @sa @ref tmedia_session_mgr_start +*/ +int tmedia_session_mgr_stop(tmedia_session_mgr_t* self) +{ + int ret = 0; + tsk_list_item_t* item; + tmedia_session_t* session; + + TSK_DEBUG_INFO("tmedia_session_mgr_stop()"); + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + tsk_safeobj_lock(self); + + if(!self->started){ + goto bail; + } + + tsk_list_foreach(item, self->sessions){ + if(!(session = item->data) || !session->plugin || !session->plugin->stop){ + TSK_DEBUG_ERROR("Invalid session"); + ret = -2; + goto bail; + } + if((ret = session->plugin->stop(session))){ + TSK_DEBUG_ERROR("Failed to stop session"); + continue; + } + } + self->started = tsk_false; + +bail: + tsk_safeobj_unlock(self); + return ret; +} + +/**@ingroup tmedia_session_group +* Gets local offer. +*/ +const tsdp_message_t* tmedia_session_mgr_get_lo(tmedia_session_mgr_t* self) +{ + const tsk_list_item_t* item; + const tmedia_session_t* ms; + const tsdp_header_M_t* m; + const tsdp_message_t* ret = tsk_null; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_null; + } + + tsk_safeobj_lock(self); + + /* prepare the session manager if not already done (create all sessions) */ + if(TSK_LIST_IS_EMPTY(self->sessions)){ + if(_tmedia_session_mgr_load_sessions(self)){ + TSK_DEBUG_ERROR("Failed to prepare the session manager"); + goto bail; + } + } + + /* creates local sdp if not already done or update it's value (because of set_ro())*/ + if((self->ro_changed || self->state_changed || self->mediaType_changed) && self->sdp.lo){ + // delete current lo + TSK_OBJECT_SAFE_FREE(self->sdp.lo); + if(self->mediaType_changed){ + // reload session with new medias and keep the old one + _tmedia_session_mgr_load_sessions(self); + } + self->ro_changed = tsk_false; + self->ro_provisional = tsk_false; + self->state_changed = tsk_false; + self->mediaType_changed = tsk_false; + } + + if(self->sdp.lo){ + ret = self->sdp.lo; + goto bail; + } + else if((self->sdp.lo = tsdp_message_create_empty(self->public_addr ? self->public_addr : self->addr, self->ipv6, self->sdp.lo_ver++))){ + /* Set connection "c=" */ + tsdp_message_add_headers(self->sdp.lo, + TSDP_HEADER_C_VA_ARGS("IN", self->ipv6 ? "IP6" : "IP4", self->public_addr ? self->public_addr : self->addr),//FIXME + tsk_null); + }else{ + self->sdp.lo_ver--; + TSK_DEBUG_ERROR("Failed to create empty SDP message"); + goto bail; + } + + /* pass complete local sdp to the sessions to allow them to use session-level attributes */ + tmedia_session_mgr_set(self, + TMEDIA_SESSION_SET_POBJECT(self->type, "local-sdp-message", self->sdp.lo), + TMEDIA_SESSION_SET_NULL()); + + /* gets each "m=" line from the sessions and add them to the local sdp */ + tsk_list_foreach(item, self->sessions){ + if(!(ms = item->data) || !ms->plugin){ + TSK_DEBUG_ERROR("Invalid session"); + continue; + } + /* prepare the media session */ + if(!ms->prepared && (_tmedia_session_prepare(TMEDIA_SESSION(ms)))){ + TSK_DEBUG_ERROR("Failed to prepare session"); /* should never happen */ + continue; + } + + /* Add QoS lines to our local media */ + if((self->qos.type != tmedia_qos_stype_none) && !TMEDIA_SESSION(ms)->qos){ + TMEDIA_SESSION(ms)->qos = tmedia_qos_tline_create(self->qos.type, self->qos.strength); + } + + /* add "m=" line from the session to the local sdp */ + if((m = tmedia_session_get_lo(TMEDIA_SESSION(ms)))){ + tsdp_message_add_header(self->sdp.lo, TSDP_HEADER(m)); + } + else{ + TSK_DEBUG_ERROR("Failed to get m= line for [%s] media", ms->plugin->media); + } + } + + ret = self->sdp.lo; + +bail: + tsk_safeobj_unlock(self); + return ret; +} + + +/**@ingroup tmedia_session_group +* Sets remote offer. +*/ +int tmedia_session_mgr_set_ro(tmedia_session_mgr_t* self, const tsdp_message_t* sdp, tmedia_ro_type_t ro_type) +{ + const tmedia_session_t* ms; + const tsdp_header_M_t* M; + const tsdp_header_C_t* C; /* global "c=" line */ + const tsdp_header_O_t* O; + tsk_size_t index = 0; + tsk_size_t active_sessions_count = 0; + int ret = 0; + tsk_bool_t found; + tsk_bool_t stopped_to_reconf = tsk_false; + tsk_bool_t is_ro_network_info_changed = tsk_false; + tsk_bool_t is_ro_hold_resume_changed = tsk_false; + tsk_bool_t is_ro_loopback_address = tsk_false; + tsk_bool_t is_media_type_changed = tsk_false; + tsk_bool_t is_ro_media_lines_changed = tsk_false; + tsk_bool_t had_ro_sdp, had_ro_provisional, is_ro_provisional_final_matching = tsk_false; + tmedia_qos_stype_t qos_type = tmedia_qos_stype_none; + tmedia_type_t new_mediatype = tmedia_none; + + if(!self || !sdp){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + tsk_safeobj_lock(self); + + had_ro_sdp = (self->sdp.ro != tsk_null); + had_ro_provisional = (had_ro_sdp && self->ro_provisional); + + /* RFC 3264 subcaluse 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. If the version in the origin line + does not increment, the SDP MUST be identical to the SDP with that version number. The answerer MUST be prepared to receive + an offer that contains SDP with a version that has not changed; this is effectively a no-op. + */ + if((O = (const tsdp_header_O_t*)tsdp_message_get_header(sdp, tsdp_htype_O))){ + tsk_bool_t is_ro_provisional; + if(self->sdp.ro_ver == (int32_t)O->sess_version){ + TSK_DEBUG_INFO("Remote offer has not changed"); + ret = 0; + goto bail; + } + // Last provisional and new final sdp messages match only if: + // - session version diff is == 1 + // - previous sdp was provisional and new one is final + // - the new final sdp is inside an answer + is_ro_provisional = ((ro_type & tmedia_ro_type_provisional) == tmedia_ro_type_provisional); + is_ro_provisional_final_matching = ((had_ro_provisional && !is_ro_provisional) && ((self->sdp.ro_ver + 1) == O->sess_version) && ((ro_type & tmedia_ro_type_answer) == tmedia_ro_type_answer)); + self->sdp.ro_ver = (int32_t)O->sess_version; + } + else{ + TSK_DEBUG_ERROR("o= line is missing"); + ret = -2; + goto bail; + } + + /* SDP comparison */ + if(sdp && self->sdp.ro){ + const tsdp_header_M_t *M0, *M1; + const tsdp_header_C_t *C0, *C1; + index = 0; + while((M0 = (const tsdp_header_M_t*)tsdp_message_get_headerAt(self->sdp.ro, tsdp_htype_M, index))){ + M1 = (const tsdp_header_M_t*)tsdp_message_get_headerAt(sdp, tsdp_htype_M, index); + if(!M1 || !tsk_striequals(M1->media, M0->media)){ + // media lines must be at the same index + // (M1 == null) means media lines are not at the same index or new one have been added/removed + is_ro_media_lines_changed = tsk_true; + } + + // hold/resume + is_ro_hold_resume_changed |= !tsk_striequals(tsdp_header_M_get_holdresume_att(M0), tsdp_header_M_get_holdresume_att(M1)); + // media lines + if(!is_ro_media_lines_changed){ + is_ro_media_lines_changed + // (M1 == null) means media lines are not at the same index or new one have been added/removed + |= (!M1) + // same media (e.g. audio) + || !tsk_striequals(M1->media, M0->media) + // same protos (e.g. SRTP) + || !tsk_striequals(M1->proto, M0->proto); + } + // network ports + is_ro_network_info_changed |= ((M1 ? M1->port : 0) != (M0->port)); + + if(!is_ro_network_info_changed){ + C0 = (const tsdp_header_C_t*)tsdp_message_get_headerAt(self->sdp.ro, tsdp_htype_C, index); + C1 = (const tsdp_header_C_t*)tsdp_message_get_headerAt(sdp, tsdp_htype_C, index); + // Connection informations must be both "null" or "not-null" + if(!(is_ro_network_info_changed = !((C0 && C1) || (!C0 && !C1)))){ + if(C0){ + is_ro_network_info_changed = (!tsk_strequals(C1->addr, C0->addr) || !tsk_strequals(C1->nettype, C0->nettype) || !tsk_strequals(C1->addrtype, C0->addrtype)); + } + } + } + ++index; + } + // the index was used against current ro which means at this step there is no longer any media at "index" + // to be sure that new and old sdp have same number of media lines, we just check that there is no media in the new sdp at "index" + is_ro_media_lines_changed |= (tsdp_message_get_headerAt(sdp, tsdp_htype_M, index) != tsk_null); + } + + /* + * Make sure that the provisional response is an preview of the final as explained rfc6337 section 3.1.1. We only check the most important part (IP addr and ports). + * It's useless to check codecs or any other caps (SRTP, ICE, DTLS...) as our offer haven't changed + * If the preview is different than the final response than this is a bug on the remote party: + * As per rfc6337 section 3.1.1.: + * - [RFC3261] requires all SDP in the responses to the INVITE request to be identical. + * - After the UAS has sent the answer in a reliable provisional + response to the INVITE, the UAS should not include any SDPs in + subsequent responses to the INVITE. + * If the remote party is buggy, then the newly generated local SDP will be sent in the ACK request + */ + is_ro_provisional_final_matching &= !(is_ro_media_lines_changed || is_ro_network_info_changed); + + /* This is to hack fake forking from ZTE => ignore SDP with loopback address in order to not start/stop the camera several + * times which leads to more than ten seconds for session connection. + * Gets the global connection line: "c=" + * Loopback address is only invalid on + */ + if((C = (const tsdp_header_C_t*)tsdp_message_get_header(sdp, tsdp_htype_C)) && C->addr){ + is_ro_loopback_address = (tsk_striequals("IP4", C->addrtype) && tsk_striequals("127.0.0.1", C->addr)) + || (tsk_striequals("IP6", C->addrtype) && tsk_striequals("::1", C->addr)); + } + + /* Check if media type has changed or not + * For initial offer we don't need to check anything + */ + if(self->sdp.lo){ + new_mediatype = tmedia_type_from_sdp(sdp); + if((is_media_type_changed = (new_mediatype != self->type))){ + tmedia_session_mgr_set_media_type(self, new_mediatype); + TSK_DEBUG_INFO("media type has changed"); + } + } + + TSK_DEBUG_INFO( + "is_ro_provisional_final_matching=%d,\n" + "is_ro_media_lines_changed=%d,\n" + "is_ro_network_info_changed=%d,\n" + "is_ro_loopback_address=%d,\n" + "is_media_type_changed=%d\n", + is_ro_provisional_final_matching, + is_ro_media_lines_changed, + is_ro_network_info_changed, + is_ro_loopback_address, + is_media_type_changed + ); + + /* + * It's almost impossible to update the codecs, the connection information etc etc while the sessions are running + * For example, if the video producer is already started then, you probably cannot update its configuration + * without stoping it and restart again with the right config. Same for RTP Network config (ip addresses, NAT, ports, IP version, ...) + * "is_loopback_address" is used as a guard to avoid reconf for loopback address used for example by ZTE for fake forking. In all case + * loopback address won't work on embedded devices such as iOS and Android. + */ + if((self->started && !is_ro_loopback_address ) && (is_ro_network_info_changed || is_ro_media_lines_changed || is_media_type_changed)){ + TSK_DEBUG_INFO("stopped_to_reconf=true"); + if((ret = tmedia_session_mgr_stop(self))){ + TSK_DEBUG_ERROR("Failed to stop session manager"); + goto bail; + } + stopped_to_reconf = tsk_true; + } + + /* update remote offer */ + TSK_OBJECT_SAFE_FREE(self->sdp.ro); + self->sdp.ro = tsk_object_ref((void*)sdp); + + /* if the session is running this means no session update is required + this check must be done after the "ro" update + */ + if(self->started && !(is_ro_hold_resume_changed || is_ro_network_info_changed || is_ro_media_lines_changed)){ + goto end_of_sessions_update; + } + + /* prepare the session manager if not already done (create all sessions with their codecs) + * if network-initiated: think about tmedia_type_from_sdp() before creating the manager */ + if(_tmedia_session_mgr_load_sessions(self)){ + TSK_DEBUG_ERROR("Failed to prepare the session manager"); + ret = -3; + goto bail; + } + + /* get global connection line (common to all sessions) + * Each session should override this info if it has a different one in its "m=" line + * /!\ "remote-ip" is deprecated by "remote-sdp-message" and pending before complete remove + */ + if(C && C->addr){ + tmedia_session_mgr_set(self, + TMEDIA_SESSION_SET_STR(self->type, "remote-ip", C->addr), + TMEDIA_SESSION_SET_NULL()); + } + + /* pass complete remote sdp to the sessions to allow them to use session-level attributes + */ + tmedia_session_mgr_set(self, + TMEDIA_SESSION_SET_POBJECT(self->type, "remote-sdp-message", self->sdp.ro), + TMEDIA_SESSION_SET_NULL()); + + /* foreach "m=" line in the remote offer create/prepare a session (requires the session to be stopped)*/ + index = 0; + while((M = (const tsdp_header_M_t*)tsdp_message_get_headerAt(sdp, tsdp_htype_M, index++))){ + found = tsk_false; + /* Find session by media */ + if((ms = tsk_list_find_object_by_pred(self->sessions, __pred_find_session_by_media, M->media))){ + /* prepare the media session */ + if(!self->started){ + if(!ms->prepared && (_tmedia_session_prepare(TMEDIA_SESSION(ms)))){ + TSK_DEBUG_ERROR("Failed to prepare session"); /* should never happen */ + goto bail; + } + } + /* set remote ro at session-level */ + if((ret = _tmedia_session_set_ro(TMEDIA_SESSION(ms), M)) == 0){ + found = tsk_true; + ++active_sessions_count; + } + else{ + // will send 488 Not Acceptable + TSK_DEBUG_WARN("_tmedia_session_set_ro() failed"); + goto bail; + } + /* set QoS type (only if we are not the offerer) */ + if(/*!self->offerer ==> we suppose that the remote party respected our demand &&*/ qos_type == tmedia_qos_stype_none){ + tmedia_qos_tline_t* tline = tmedia_qos_tline_from_sdp(M); + if(tline){ + qos_type = tline->type; + TSK_OBJECT_SAFE_FREE(tline); + } + } + } + + if(!found && (self->sdp.lo == tsk_null)){ + /* Session not supported and we are not the initial offerer ==> add ghost session */ + /* + An offered stream MAY be rejected in the answer, for any reason. If + a stream is rejected, the offerer and answerer MUST NOT generate + media (or RTCP packets) for that stream. To reject an offered + stream, the port number in the corresponding stream in the answer + MUST be set to zero. Any media formats listed are ignored. AT LEAST + ONE MUST BE PRESENT, AS SPECIFIED BY SDP. + */ + tmedia_session_ghost_t* ghost; + if((ghost = (tmedia_session_ghost_t*)tmedia_session_create(tmedia_ghost))){ + tsk_strupdate(&ghost->media, M->media); /* copy media */ + tsk_strupdate(&ghost->proto, M->proto); /* copy proto */ + if(!TSK_LIST_IS_EMPTY(M->FMTs)){ + tsk_strupdate(&ghost->first_format, ((const tsdp_fmt_t*)TSK_LIST_FIRST_DATA(M->FMTs))->value); /* copy format */ + } + tsk_list_push_back_data(self->sessions, (void**)&ghost); + } + else{ + TSK_DEBUG_ERROR("Failed to create ghost session"); + continue; + } + } + } + +end_of_sessions_update: + + /* update QoS type */ + if(!self->offerer && (qos_type != tmedia_qos_stype_none)){ + self->qos.type = qos_type; + } + + /* signal that ro has changed (will be used to update lo) unless there was no ro_sdp */ + self->ro_changed = (had_ro_sdp && (is_ro_hold_resume_changed || is_ro_network_info_changed || is_ro_media_lines_changed)); + + /* update "provisional" info */ + self->ro_provisional = ((ro_type & tmedia_ro_type_provisional) == tmedia_ro_type_provisional); + + if(self->ro_changed){ + /* update local offer before restarting the session manager otherwise neg_codecs won't match if new codecs + have been added or removed */ + (tmedia_session_mgr_get_lo(self)); + } + /* manager was started and we stopped it in order to reconfigure it (codecs, network, ....) */ + if(stopped_to_reconf){ + if((ret = tmedia_session_mgr_start(self))){ + TSK_DEBUG_ERROR("Failed to re-start session manager"); + goto bail; + } + } + + // will send [488 Not Acceptable] / [BYE] if no active session + ret = (self->ro_changed && active_sessions_count <= 0) ? -0xFF : 0; + +bail: + tsk_safeobj_unlock(self); + return ret; +} + +const tsdp_message_t* tmedia_session_mgr_get_ro(tmedia_session_mgr_t* self) +{ + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_null; + } + return self->sdp.ro; +} + +/**@ingroup tmedia_session_group +* Holds the session as per 3GPP TS 34.610 +* @param self the session manager managing the session to hold. +* @param type the type of the sessions to hold (you can combine several medias. e.g. audio|video|msrp). +* @retval Zero if succeed and non zero error code otherwise. +* @sa @ref tmedia_session_mgr_resume +*/ +int tmedia_session_mgr_hold(tmedia_session_mgr_t* self, tmedia_type_t type) +{ + const tsk_list_item_t* item; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + tsk_list_foreach(item, self->sessions){ + tmedia_session_t* session = TMEDIA_SESSION(item->data); + if(((session->type & type) == session->type) && session->M.lo){ + if(tsdp_header_M_hold(session->M.lo, tsk_true) == 0){ + self->state_changed = tsk_true; + session->lo_held = tsk_true; + } + } + } + return 0; +} + +/**@ingroup tmedia_session_group +* Indicates whether the specified medias are held or not. +* @param self the session manager +* @param type the type of the medias to check (you can combine several medias. e.g. audio|video|msrp) +* @param local whether to check local or remote medias +*/ +tsk_bool_t tmedia_session_mgr_is_held(tmedia_session_mgr_t* self, tmedia_type_t type, tsk_bool_t local) +{ + const tsk_list_item_t* item; + tsk_bool_t have_these_sessions = tsk_false; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_false; + } + + tsk_list_foreach(item, self->sessions){ + tmedia_session_t* session = TMEDIA_SESSION(item->data); + if((session->type & type) == session->type){ + if(local && session->M.lo){ + have_these_sessions = tsk_true; + if(!tsdp_header_M_is_held(session->M.lo, tsk_true)){ + return tsk_false; + } + } + else if(!local && session->M.ro){ + have_these_sessions = tsk_true; + if(!tsdp_header_M_is_held(session->M.ro, tsk_false)){ + return tsk_false; + } + } + } + } + /* none is held */ + return have_these_sessions ? tsk_true : tsk_false; +} + +/**@ingroup tmedia_session_group +* Resumes the session as per 3GPP TS 34.610. Should be previously held +* by using @ref tmedia_session_mgr_hold. +* @param self the session manager managing the session to resume. +* @param type the type of the sessions to resume (you can combine several medias. e.g. audio|video|msrp). +* @retval Zero if succeed and non zero error code otherwise. +* @sa @ref tmedia_session_mgr_hold +*/ +int tmedia_session_mgr_resume(tmedia_session_mgr_t* self, tmedia_type_t type, tsk_bool_t local) +{ + const tsk_list_item_t* item; + int ret = 0; + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + tsk_list_foreach(item, self->sessions){ + tmedia_session_t* session = TMEDIA_SESSION(item->data); + if(((session->type & type) == session->type) && session->M.lo){ + if((ret = tsdp_header_M_resume(session->M.lo, local)) == 0){ + self->state_changed = tsk_true; + if(local){ + session->lo_held = tsk_false; + } + else{ + session->ro_held = tsk_false; + } + } + } + } + return ret; +} + +/**@ingroup tmedia_session_group +* Adds new medias to the manager. A media will only be added if it is missing +* or previously removed (slot with port equal to zero). +* @param self The session manager +* @param The types of the medias to add (ou can combine several medias. e.g. audio|video|msrp) +* @retval Zero if succeed and non zero error code otherwise. +*/ +int tmedia_session_mgr_add_media(tmedia_session_mgr_t* self, tmedia_type_t type) +{ + tsk_size_t i = 0; + tmedia_session_t* session; + const tmedia_session_plugin_def_t* plugin; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* for each registered plugin match with the supplied type */ + while((i < TMED_SESSION_MAX_PLUGINS) && (plugin = __tmedia_session_plugins[i++])){ + if((plugin->type & type) == plugin->type){ + /* check whether we already support this media */ + if((session = (tmedia_session_t*)tsk_list_find_object_by_pred(self->sessions, __pred_find_session_by_type, &plugin->type)) && session->plugin){ + if(session->prepared){ + TSK_DEBUG_WARN("[%s] already active", plugin->media); + } + else{ + /* exist but unprepared(port=0) */ + _tmedia_session_prepare(session); + if(self->started && session->plugin->start){ + session->plugin->start(session); + } + self->state_changed = tsk_true; + } + } + else{ + /* session not supported */ + self->state_changed = tsk_true; + if((session = tmedia_session_create(plugin->type))){ + if(self->started && session->plugin->start){ + session->plugin->start(session); + } + tsk_list_push_back_data(self->sessions, (void**)(&session)); + self->state_changed = tsk_true; + } + } + } + } + + return self->state_changed ? 0 : -2; +} + +/**@ingroup tmedia_session_group +* Removes medias from the manager. This action will stop the media and sets it's port value to zero (up to the session). +* @param self The session manager +* @param The types of the medias to remove (ou can combine several medias. e.g. audio|video|msrp) +* @retval Zero if succeed and non zero error code otherwise. +*/ +int tmedia_session_mgr_remove_media(tmedia_session_mgr_t* self, tmedia_type_t type) +{ + const tsk_list_item_t* item; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + tsk_list_foreach(item, self->sessions){ + tmedia_session_t* session = TMEDIA_SESSION(item->data); + if(((session->type & type) == session->type) && session->plugin->stop){ + if(!session->plugin->stop(session)){ + self->state_changed = tsk_true; + } + } + } + return 0; +} + +/**@ingroup tmedia_session_group +* Sets QoS type and strength +* @param self The session manager +* @param qos_type The QoS type +* @param qos_strength The QoS strength +* @retval Zero if succeed and non-zero error code otherwise +*/ +int tmedia_session_mgr_set_qos(tmedia_session_mgr_t* self, tmedia_qos_stype_t qos_type, tmedia_qos_strength_t qos_strength) +{ + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + self->qos.type = qos_type; + self->qos.strength = qos_strength; + return 0; +} + +/**@ingroup tmedia_session_group +* Indicates whether all preconditions are met +* @param self The session manager +* @retval @a tsk_true if all preconditions have been met and @a tsk_false otherwise +*/ +tsk_bool_t tmedia_session_mgr_canresume(tmedia_session_mgr_t* self) +{ + const tsk_list_item_t* item; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_true; + } + + tsk_list_foreach(item, self->sessions){ + tmedia_session_t* session = TMEDIA_SESSION(item->data); + if(session && session->qos && !tmedia_qos_tline_canresume(session->qos)){ + return tsk_false; + } + } + return tsk_true; +} + + +/**@ingroup tmedia_session_group +* Checks whether the manager holds at least one valid session (media port <> 0) +*/ +tsk_bool_t tmedia_session_mgr_has_active_session(tmedia_session_mgr_t* self) +{ + const tsk_list_item_t* item; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_false; + } + + tsk_list_foreach(item, self->sessions){ + tmedia_session_t* session = TMEDIA_SESSION(item->data); + if(session && session->M.lo && session->M.lo->port){ + return tsk_true; + } + } + return tsk_false; +} + +int tmedia_session_mgr_send_dtmf(tmedia_session_mgr_t* self, uint8_t event) +{ + tmedia_session_audio_t* session; + static const tmedia_type_t audio_type = tmedia_audio; + int ret = -3; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + session = (tmedia_session_audio_t*)tsk_list_find_object_by_pred(self->sessions, __pred_find_session_by_type, &audio_type); + if(session){ + session = tsk_object_ref(session); + ret = tmedia_session_audio_send_dtmf(TMEDIA_SESSION_AUDIO(session), event); + TSK_OBJECT_SAFE_FREE(session); + } + else{ + TSK_DEBUG_ERROR("No audio session associated to this manager"); + } + + return ret; +} + +int tmedia_session_mgr_set_t140_ondata_cbfn(tmedia_session_mgr_t* self, const void* context, tmedia_session_t140_ondata_cb_f func) +{ + tmedia_session_t* session; + int ret = -1; + if((session = tmedia_session_mgr_find(self, tmedia_t140))){ + ret = tmedia_session_t140_set_ondata_cbfn(session, context, func); + TSK_OBJECT_SAFE_FREE(session); + } + return ret; +} + +int tmedia_session_mgr_send_t140_data(tmedia_session_mgr_t* self, enum tmedia_t140_data_type_e data_type, const void* data_ptr, unsigned data_size) +{ + tmedia_session_t* session; + int ret = -1; + if((session = tmedia_session_mgr_find(self, tmedia_t140))){ + ret = tmedia_session_t140_send_data(session, data_type, data_ptr, data_size); + TSK_OBJECT_SAFE_FREE(session); + } + return ret; +} + +int tmedia_session_mgr_set_onrtcp_cbfn(tmedia_session_mgr_t* self, tmedia_type_t media_type, const void* context, tmedia_session_rtcp_onevent_cb_f fun) +{ + tmedia_session_t* session; + tsk_list_item_t *item; + + if(!self){ + TSK_DEBUG_ERROR("Invlid parameter"); + return -1; + } + + tsk_list_lock(self->sessions); + tsk_list_foreach(item, self->sessions){ + if(!(session = item->data) || !(session->type & media_type)){ + continue; + } + tmedia_session_set_onrtcp_cbfn(session, context, fun); + } + tsk_list_unlock(self->sessions); + + return 0; +} + +int tmedia_session_mgr_send_rtcp_event(tmedia_session_mgr_t* self, tmedia_type_t media_type, tmedia_rtcp_event_type_t event_type, uint32_t ssrc_media) +{ + tmedia_session_t* session; + tsk_list_item_t *item; + + if(!self){ + TSK_DEBUG_ERROR("Invlid parameter"); + return -1; + } + + tsk_list_lock(self->sessions); + tsk_list_foreach(item, self->sessions){ + if(!(session = item->data) || !(session->type & media_type)){ + continue; + } + tmedia_session_send_rtcp_event(session, event_type, ssrc_media); + } + tsk_list_unlock(self->sessions); + + return 0; +} + +int tmedia_session_mgr_send_file(tmedia_session_mgr_t* self, const char* path, ...) +{ + tmedia_session_msrp_t* session; + tmedia_type_t msrp_type = tmedia_msrp; + int ret = -3; + + if(!self || !path){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + session = (tmedia_session_msrp_t*)tsk_list_find_object_by_pred(self->sessions, __pred_find_session_by_type, &msrp_type); + if(session && session->send_file){ + va_list ap; + va_start(ap, path); + session = tsk_object_ref(session); + ret = session->send_file(TMEDIA_SESSION_MSRP(session), path, &ap); + TSK_OBJECT_SAFE_FREE(session); + va_end(ap); + } + else{ + TSK_DEBUG_ERROR("No MSRP session associated to this manager or session does not support file transfer"); + } + + return ret; +} + +int tmedia_session_mgr_send_message(tmedia_session_mgr_t* self, const void* data, tsk_size_t size, const tmedia_params_L_t *params) +{ + tmedia_session_msrp_t* session; + tmedia_type_t msrp_type = tmedia_msrp; + int ret = -3; + + if(!self || !size || !data){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + session = (tmedia_session_msrp_t*)tsk_list_find_object_by_pred(self->sessions, __pred_find_session_by_type, &msrp_type); + if(session && session->send_message){ + session = tsk_object_ref(session); + ret = session->send_message(TMEDIA_SESSION_MSRP(session), data, size, params); + TSK_OBJECT_SAFE_FREE(session); + } + else{ + TSK_DEBUG_ERROR("No MSRP session associated to this manager or session does not support file transfer"); + } + + return ret; +} + +int tmedia_session_mgr_set_msrp_cb(tmedia_session_mgr_t* self, const void* callback_data, tmedia_session_msrp_cb_f func) +{ + tmedia_session_msrp_t* session; + tmedia_type_t msrp_type = tmedia_msrp; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if((session = (tmedia_session_msrp_t*)tsk_list_find_object_by_pred(self->sessions, __pred_find_session_by_type, &msrp_type))){ + session->callback.data = callback_data; + session->callback.func = func; + return 0; + } + else{ + TSK_DEBUG_ERROR("No MSRP session associated to this manager or session does not support file transfer"); + return -2; + } +} + +int tmedia_session_mgr_set_onerror_cbfn(tmedia_session_mgr_t* self, const void* usrdata, tmedia_session_onerror_cb_f fun) +{ + tmedia_session_t* session; + tsk_list_item_t *item; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + self->onerror_cb.fun = fun; + self->onerror_cb.usrdata = usrdata; + + tsk_list_lock(self->sessions); + tsk_list_foreach(item, self->sessions){ + if(!(session = item->data)){ + continue; + } + tmedia_session_set_onerror_cbfn(session, usrdata, fun); + } + tsk_list_unlock(self->sessions); + + return 0; +} + +/** internal function used to load sessions */ +static int _tmedia_session_mgr_load_sessions(tmedia_session_mgr_t* self) +{ + tsk_size_t i = 0; + tmedia_session_t* session; + const tmedia_session_plugin_def_t* plugin; + +#define has_media(media_type) (tsk_list_find_object_by_pred(self->sessions, __pred_find_session_by_type, &(media_type))) + + if(TSK_LIST_IS_EMPTY(self->sessions) || self->mediaType_changed){ + /* for each registered plugin create a session instance */ + while((i < TMED_SESSION_MAX_PLUGINS) && (plugin = __tmedia_session_plugins[i++])){ + if((plugin->type & self->type) == plugin->type && !has_media(plugin->type)){// we don't have a session with this media type yet + if((session = tmedia_session_create(plugin->type))){ + /* do not call "tmedia_session_mgr_set()" here to avoid applying parms before the creation of all session */ + + /* set other default values */ + + // set callback functions + tmedia_session_set_onerror_cbfn(session, self->onerror_cb.usrdata, self->onerror_cb.fun); + + /* push session */ + tsk_list_push_back_data(self->sessions, (void**)(&session)); + } + } + else if(!(plugin->type & self->type) && has_media(plugin->type)){// we have media session from previous call (before update) + tsk_list_remove_item_by_pred(self->sessions, __pred_find_session_by_type, &(plugin->type)); + } + } + /* set default values and apply params*/ + tmedia_session_mgr_set(self, + TMEDIA_SESSION_SET_POBJECT(tmedia_audio, "ice-ctx", self->ice.ctx_audio), + TMEDIA_SESSION_SET_POBJECT(tmedia_video, "ice-ctx", self->ice.ctx_video), + + TMEDIA_SESSION_SET_STR(self->type, "local-ip", self->addr), + TMEDIA_SESSION_SET_STR(self->type, "local-ipver", self->ipv6 ? "ipv6" : "ipv4"), + TMEDIA_SESSION_SET_INT32(self->type, "bandwidth-level", self->bl), + TMEDIA_SESSION_SET_NULL()); + } +#undef has_media + return 0; +} + +/* internal function */ +static int _tmedia_session_mgr_clear_sessions(tmedia_session_mgr_t* self) +{ + if(self && self->sessions){ + tsk_list_clear_items(self->sessions); + } + return 0; +} + +/* internal function */ +static int _tmedia_session_mgr_apply_params(tmedia_session_mgr_t* self) +{ + tsk_list_item_t *it1, *it2; + tmedia_param_t* param; + tmedia_session_t* session; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* If no parameters ==> do nothing (not error) */ + if(TSK_LIST_IS_EMPTY(self->params)){ + return 0; + } + + tsk_list_lock(self->params); + + tsk_list_foreach(it1, self->params){ + if(!(param = it1->data)){ + continue; + } + + /* For us */ + if(param->plugin_type == tmedia_ppt_manager){ + continue; + } + + /* For the session (or consumer or producer or codec) */ + tsk_list_foreach(it2, self->sessions){ + if(!(session = it2->data) || !session->plugin){ + continue; + } + if(session->plugin->set && (session->type & param->media_type) == session->type){ + session->plugin->set(session, param); + } + } + } + + /* Clean up params */ + tsk_list_clear_items(self->params); + + tsk_list_unlock(self->params); + + return 0; +} + +//================================================================================================= +// Media Session Manager object definition +// +static tsk_object_t* tmedia_session_mgr_ctor(tsk_object_t * self, va_list * app) +{ + tmedia_session_mgr_t *mgr = self; + if(mgr){ + mgr->sessions = tsk_list_create(); + + mgr->sdp.lo_ver = TSDP_HEADER_O_SESS_VERSION_DEFAULT; + mgr->sdp.ro_ver = -1; + + mgr->qos.type = tmedia_qos_stype_none; + mgr->qos.strength = tmedia_qos_strength_optional; + mgr->bl = tmedia_defaults_get_bl(); + + tsk_safeobj_init(mgr); + } + return self; +} + +static tsk_object_t* tmedia_session_mgr_dtor(tsk_object_t * self) +{ + tmedia_session_mgr_t *mgr = self; + if(mgr){ + TSK_OBJECT_SAFE_FREE(mgr->sessions); + + TSK_OBJECT_SAFE_FREE(mgr->sdp.lo); + TSK_OBJECT_SAFE_FREE(mgr->sdp.ro); + + TSK_OBJECT_SAFE_FREE(mgr->params); + + TSK_OBJECT_SAFE_FREE(mgr->natt_ctx); + TSK_FREE(mgr->public_addr); + + TSK_OBJECT_SAFE_FREE(mgr->ice.ctx_audio); + TSK_OBJECT_SAFE_FREE(mgr->ice.ctx_video); + + TSK_FREE(mgr->addr); + + tsk_safeobj_deinit(mgr); + } + + return self; +} + +static const tsk_object_def_t tmedia_session_mgr_def_s = +{ + sizeof(tmedia_session_mgr_t), + tmedia_session_mgr_ctor, + tmedia_session_mgr_dtor, + tsk_null, +}; +const tsk_object_def_t *tmedia_session_mgr_def_t = &tmedia_session_mgr_def_s; + diff --git a/branches/2.0/doubango/tinyNET/src/ice/tnet_ice_candidate.c b/branches/2.0/doubango/tinyNET/src/ice/tnet_ice_candidate.c index 07defc5f..9d4092b4 100644 --- a/branches/2.0/doubango/tinyNET/src/ice/tnet_ice_candidate.c +++ b/branches/2.0/doubango/tinyNET/src/ice/tnet_ice_candidate.c @@ -46,7 +46,6 @@ static int _tnet_ice_candidate_tostring( static const char* _tnet_ice_candidate_get_foundation(tnet_ice_cand_type_t type); static tnet_stun_message_t * _tnet_ice_candidate_stun_create_bind_request(tnet_ice_candidate_t* self, const char* username, const char* password); static tsk_bool_t _tnet_ice_candidate_stun_transac_id_equals(const tnet_stun_transacid_t id1, const tnet_stun_transacid_t id2); -static int _tnet_ice_candidate_stun_address_tostring(const uint8_t in_ip[16], tnet_stun_addr_family_t family, char** out_ip); static const char* _tnet_ice_candidate_get_transport_str(tnet_socket_type_t transport_e); static tnet_socket_type_t _tnet_ice_candidate_get_transport_type(tsk_bool_t ipv6, const char* transport_str); static const char* _tnet_ice_candidate_get_candtype_str(tnet_ice_cand_type_t candtype_e); @@ -110,8 +109,7 @@ tnet_ice_candidate_t* tnet_ice_candidate_create(tnet_ice_cand_type_t type_e, tne TSK_DEBUG_ERROR("Failed to create candidate"); return tsk_null; } - - candidate->transport_e = socket->type; + candidate->type_e = type_e; candidate->socket = tsk_object_ref(socket); candidate->local_pref = 0xFFFF; @@ -129,6 +127,7 @@ tnet_ice_candidate_t* tnet_ice_candidate_create(tnet_ice_cand_type_t type_e, tne if(candidate->socket){ memcpy(candidate->connection_addr, candidate->socket->ip, sizeof(candidate->socket->ip)); candidate->port = candidate->socket->port; + candidate->transport_e = socket->type; } tnet_ice_candidate_set_credential(candidate, ufrag, pwd); @@ -420,12 +419,12 @@ int tnet_ice_candidate_process_stun_response(tnet_ice_candidate_t* self, const if((attribute = tnet_stun_message_get_attribute(response, stun_xor_mapped_address))){ const tnet_stun_attribute_xmapped_addr_t *xmaddr = (const tnet_stun_attribute_xmapped_addr_t *)attribute; - _tnet_ice_candidate_stun_address_tostring(xmaddr->xaddress, xmaddr->family, &self->stun.srflx_addr); + tnet_ice_utils_stun_address_tostring(xmaddr->xaddress, xmaddr->family, &self->stun.srflx_addr); self->stun.srflx_port = xmaddr->xport; } else if((attribute = tnet_stun_message_get_attribute(response, stun_mapped_address))){ const tnet_stun_attribute_mapped_addr_t *maddr = (const tnet_stun_attribute_mapped_addr_t *)attribute; - ret = _tnet_ice_candidate_stun_address_tostring(maddr->address, maddr->family, &self->stun.srflx_addr); + ret = tnet_ice_utils_stun_address_tostring(maddr->address, maddr->family, &self->stun.srflx_addr); self->stun.srflx_port = maddr->port; } } @@ -526,25 +525,6 @@ static tsk_bool_t _tnet_ice_candidate_stun_transac_id_equals(const tnet_stun_tra return tsk_true; } -static int _tnet_ice_candidate_stun_address_tostring(const uint8_t in_ip[16], tnet_stun_addr_family_t family, char** out_ip) -{ - if(family == stun_ipv6){ - tsk_sprintf(out_ip, "%x:%x:%x:%x:%x:%x:%x:%x", - TSK_TO_UINT16(&in_ip[0]), TSK_TO_UINT16(&in_ip[2]), TSK_TO_UINT16(&in_ip[4]), TSK_TO_UINT16(&in_ip[6]), - TSK_TO_UINT16(&in_ip[8]), TSK_TO_UINT16(&in_ip[10]), TSK_TO_UINT16(&in_ip[12]), TSK_TO_UINT16(&in_ip[14])); - } - else if(family == stun_ipv4){ - tsk_sprintf(out_ip, "%u.%u.%u.%u", in_ip[0], in_ip[1], in_ip[2], in_ip[3]); - - return 0; - } - else{ - TSK_DEBUG_ERROR("Unsupported address family: %u.", family); - } - - return -1; -} - static tnet_stun_message_t * _tnet_ice_candidate_stun_create_bind_request(tnet_ice_candidate_t* self, const char* username, const char* password) { tnet_stun_message_t *request = tsk_null; diff --git a/branches/2.0/doubango/tinyNET/src/ice/tnet_ice_ctx.c b/branches/2.0/doubango/tinyNET/src/ice/tnet_ice_ctx.c index 8d820e6b..8f700125 100644 --- a/branches/2.0/doubango/tinyNET/src/ice/tnet_ice_ctx.c +++ b/branches/2.0/doubango/tinyNET/src/ice/tnet_ice_ctx.c @@ -660,8 +660,17 @@ int tnet_ice_ctx_recv_stun_message(tnet_ice_ctx_t* self, const void* data, tsk_s if((message = tnet_stun_message_deserialize(data, size))){ if(message->type == stun_binding_request){ - // check controlling flag - if((pair = tnet_ice_pairs_find_by_fd_and_addr(self->candidates_pairs, local_fd, remote_addr))){ + pair = tnet_ice_pairs_find_by_fd_and_addr(self->candidates_pairs, local_fd, remote_addr); + if(!pair && !self->have_nominated_symetric){ // pair not found and we're still negotiating + // rfc 5245 - 7.1.3.2.1. Discovering Peer Reflexive Candidates + tnet_ice_pair_t* pair_peer = tnet_ice_pair_prflx_create(self->candidates_pairs, local_fd, remote_addr); + if(pair_peer){ + pair = pair_peer; // copy + tsk_list_push_back_data(self->candidates_pairs, (void**)&pair_peer); + TSK_OBJECT_SAFE_FREE(pair_peer); + } + } + if(pair){ short resp_code = 0; char* resp_phrase = tsk_null; // authenticate the request diff --git a/branches/2.0/doubango/tinyNET/src/ice/tnet_ice_pair.c b/branches/2.0/doubango/tinyNET/src/ice/tnet_ice_pair.c index db19f7d3..2a8261fc 100644 --- a/branches/2.0/doubango/tinyNET/src/ice/tnet_ice_pair.c +++ b/branches/2.0/doubango/tinyNET/src/ice/tnet_ice_pair.c @@ -96,6 +96,68 @@ tnet_ice_pair_t* tnet_ice_pair_create(const tnet_ice_candidate_t* candidate_offe return pair; } +// rfc 5245 - 7.1.3.2.1. Discovering Peer Reflexive Candidates +tnet_ice_pair_t* tnet_ice_pair_prflx_create(tnet_ice_pairs_L_t* pairs, uint16_t local_fd, const struct sockaddr_storage *remote_addr) +{ + int ret; + const tsk_list_item_t *item; + const tnet_ice_pair_t *pair_local = tsk_null, *pair; + tnet_ip_t remote_ip; + tnet_port_t remote_port; + + if(!pairs || !remote_addr){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_null; + } + + if((ret = tnet_get_sockip_n_port((const struct sockaddr*)remote_addr, &remote_ip, &remote_port))){ + TNET_PRINT_LAST_ERROR("tnet_get_sockip_n_port() failed"); + return tsk_null; + } + + tsk_list_foreach(item, pairs){ + if(!(pair = item->data) || !pair->candidate_offer || !pair->candidate_answer || !pair->candidate_offer->socket || pair->candidate_offer->socket->fd != local_fd){ + continue; + } + pair_local = pair; + break; + } + + if(!pair_local){ + TSK_DEBUG_ERROR("Cannot create prflx candidate with remote ip = %s and remote port = %u", remote_ip, remote_port); + return tsk_null; + } + else{ + tnet_ice_pair_t* pair_peer = tsk_null; + tnet_ice_candidate_t* cand_local = tnet_ice_candidate_create(tnet_ice_cand_type_prflx, pair_local->candidate_offer->socket, pair_local->is_ice_jingle, pair_local->candidate_offer->is_rtp, pair_local->candidate_offer->is_video, pair_local->candidate_offer->ufrag, pair_local->candidate_offer->pwd, pair_local->candidate_offer->foundation); + tnet_ice_candidate_t* cand_remote = tnet_ice_candidate_create(tnet_ice_cand_type_prflx, tsk_null, pair_local->is_ice_jingle, pair_local->candidate_answer->is_rtp, pair_local->candidate_answer->is_video, pair_local->candidate_answer->ufrag, pair_local->candidate_answer->pwd, pair_local->candidate_answer->foundation); + if(cand_local && cand_remote){ + + tsk_strupdate(&cand_remote->transport_str, pair->candidate_offer->transport_str); + cand_remote->comp_id = pair->candidate_offer->comp_id; + memcpy(cand_remote->connection_addr, remote_ip, sizeof(tnet_ip_t)); + cand_remote->port = remote_port; + + TSK_DEBUG_INFO("ICE Pair (Peer Reflexive Candidate): [%s %u %s %d] -> [%s %u %s %d]", + cand_local->foundation, + cand_local->comp_id, + cand_local->connection_addr, + cand_local->port, + + cand_remote->foundation, + cand_remote->comp_id, + cand_remote->connection_addr, + cand_remote->port); + pair_peer = tnet_ice_pair_create(cand_local, cand_remote, pair_local->is_controlling, pair_local->tie_breaker, pair_local->is_ice_jingle); + } + TSK_OBJECT_SAFE_FREE(cand_local); + TSK_OBJECT_SAFE_FREE(cand_remote); + return pair_peer; + } + + return tsk_null; +} + int tnet_ice_pair_send_conncheck(tnet_ice_pair_t *self) { char* username = tsk_null; @@ -498,11 +560,55 @@ const tnet_ice_pair_t* tnet_ice_pairs_find_by_response(tnet_ice_pairs_L_t* pairs if(pairs && response){ const tsk_list_item_t *item; const tnet_ice_pair_t *pair; + tnet_port_t mapped_port; + char* mapped_addr_str = tsk_null; tsk_list_foreach(item, pairs){ - if(!(pair = item->data)){ + if(!(pair = item->data) || !pair->candidate_answer || !pair->candidate_offer){ continue; } if(pair->last_request && tnet_stun_message_transac_id_equals(pair->last_request->transaction_id, response->transaction_id)){ + // check that mapped/xmapped address match destination + const tnet_stun_attribute_xmapped_addr_t *xmapped_addr; + const tnet_stun_attribute_mapped_addr_t* mapped_addr = tsk_null; + + if(!(xmapped_addr = (const tnet_stun_attribute_xmapped_addr_t *)tnet_stun_message_get_attribute(response, stun_xor_mapped_address))){ + mapped_addr = (const tnet_stun_attribute_mapped_addr_t *)tnet_stun_message_get_attribute(response, stun_mapped_address); + } + if(!xmapped_addr && !mapped_addr){ + return pair; // do nothing if the client doesn't return mapped address STUN attribute + } + /* rfc 5245 7.1.3.2.1. Discovering Peer Reflexive Candidates + + The agent checks the mapped address from the STUN response. If the + transport address does not match any of the local candidates that the + agent knows about, the mapped address represents a new candidate -- a + peer reflexive candidate. Like other candidates, it has a type, + base, priority, and foundation. They are computed as follows: + + o Its type is equal to peer reflexive. + + o Its base is set equal to the local candidate of the candidate pair + from which the STUN check was sent. + + o Its priority is set equal to the value of the PRIORITY attribute + in the Binding request. + + o Its foundation is selected as described in Section 4.1.1.3. + + This peer reflexive candidate is then added to the list of local + candidates for the media stream. Its username fragment and password + are the same as all other local candidates for that media stream. + */ + tnet_ice_utils_stun_address_tostring(xmapped_addr ? xmapped_addr->xaddress : mapped_addr->address, xmapped_addr ? xmapped_addr->family : mapped_addr->family, &mapped_addr_str); + mapped_port = xmapped_addr ? xmapped_addr->xport : mapped_addr->port; + if((mapped_port != pair->candidate_offer->port || !tsk_striequals(mapped_addr_str, pair->candidate_offer->connection_addr))){ + TSK_DEBUG_INFO("Mapped address different than local connection address...probably symetric NAT: %s#%s and %u#%u", + pair->candidate_offer->connection_addr, mapped_addr_str, + pair->candidate_offer->port, mapped_port); + // do we really need to add new local candidate? + // continue; + } + return pair; } } @@ -539,6 +645,8 @@ const tnet_ice_pair_t* tnet_ice_pairs_find_by_fd_and_addr(tnet_ice_pairs_L_t* pa return pair; } + TSK_DEBUG_INFO("No ICE candidate with remote ip = %s and port = %u could be found...probably symetric NAT", remote_ip, remote_port); + return tsk_null; } diff --git a/branches/2.0/doubango/tinyNET/src/ice/tnet_ice_pair.h b/branches/2.0/doubango/tinyNET/src/ice/tnet_ice_pair.h index 39bbbe6f..10b344a6 100644 --- a/branches/2.0/doubango/tinyNET/src/ice/tnet_ice_pair.h +++ b/branches/2.0/doubango/tinyNET/src/ice/tnet_ice_pair.h @@ -59,6 +59,7 @@ typedef struct tnet_ice_pair_s tnet_ice_pair_t; tnet_ice_pair_t* tnet_ice_pair_create(const struct tnet_ice_candidate_s* candidate_offer, const struct tnet_ice_candidate_s* candidate_answer, tsk_bool_t is_controlling, uint64_t tie_breaker, tsk_bool_t is_ice_jingle); +tnet_ice_pair_t* tnet_ice_pair_prflx_create(tnet_ice_pairs_L_t* pairs, uint16_t local_fd, const struct sockaddr_storage *remote_addr); int tnet_ice_pair_send_conncheck(tnet_ice_pair_t *self); int tnet_ice_pair_send_response(tnet_ice_pair_t *self, const struct tnet_stun_message_s* request, const short code, const char* phrase, const struct sockaddr_storage *remote_addr); int tnet_ice_pair_auth_conncheck(const tnet_ice_pair_t *self, const struct tnet_stun_message_s* request, const void* request_buff, tsk_size_t request_buff_size, short* resp_code, char** resp_phrase); diff --git a/branches/2.0/doubango/tinyNET/src/tls/tnet_tls.h b/branches/2.0/doubango/tinyNET/src/tls/tnet_tls.h index 9293743c..5e00ec80 100644 --- a/branches/2.0/doubango/tinyNET/src/tls/tnet_tls.h +++ b/branches/2.0/doubango/tinyNET/src/tls/tnet_tls.h @@ -46,6 +46,7 @@ TNET_BEGIN_DECLS typedef void tnet_tls_socket_handle_t; +struct ssl_ctx_st; int tnet_tls_socket_connect(tnet_tls_socket_handle_t* self); int tnet_tls_socket_accept(tnet_tls_socket_handle_t* self); diff --git a/branches/2.0/doubango/tinyNET/src/tnet_transport.c b/branches/2.0/doubango/tinyNET/src/tnet_transport.c index 423a1e54..9b78f780 100644 --- a/branches/2.0/doubango/tinyNET/src/tnet_transport.c +++ b/branches/2.0/doubango/tinyNET/src/tnet_transport.c @@ -160,6 +160,8 @@ tnet_transport_t* tnet_transport_create(const char* host, tnet_port_t port, tnet TSK_DEBUG_ERROR("Failed to initialize TLS and/or DTLS caps"); TSK_OBJECT_SAFE_FREE(transport); } + // set priority + tsk_runnable_set_priority(TSK_RUNNABLE(transport), TSK_THREAD_PRIORITY_TIME_CRITICAL); } return transport; @@ -189,6 +191,9 @@ tnet_transport_t* tnet_transport_create_2(tnet_socket_t *master, const char* des TSK_DEBUG_ERROR("Failed to initialize TLS and/or DTLS caps"); TSK_OBJECT_SAFE_FREE(transport); } + + // set priority + tsk_runnable_set_priority(TSK_RUNNABLE(transport), TSK_THREAD_PRIORITY_TIME_CRITICAL); } return transport; @@ -843,6 +848,8 @@ static void* TSK_STDCALL run(void* self) TSK_DEBUG_FATAL("Failed to create main thread [%d]", ret); return tsk_null; } + /* set thread priority */ + ret = tsk_thread_set_priority(transport->mainThreadId[0], TSK_THREAD_PRIORITY_TIME_CRITICAL); TSK_RUNNABLE_RUN_BEGIN(transport); diff --git a/branches/2.0/doubango/tinyNET/src/tnet_utils.c b/branches/2.0/doubango/tinyNET/src/tnet_utils.c index 711d66e5..248c1736 100644 --- a/branches/2.0/doubango/tinyNET/src/tnet_utils.c +++ b/branches/2.0/doubango/tinyNET/src/tnet_utils.c @@ -1450,20 +1450,24 @@ int tnet_sockfd_sendto(tnet_fd_t fd, const struct sockaddr *to, const void* buf, wsaBuffer.len = (size - sent); try_again: ret = WSASendTo(fd, &wsaBuffer, 1, &numberOfBytesSent, 0, to, tnet_get_sockaddr_size(to), 0, 0); // returns zero if succeed - if(ret == 0) ret = numberOfBytesSent; + if(ret == 0){ + ret = numberOfBytesSent; + } #else try_again: ret = sendto(fd, (((const uint8_t*)buf)+sent), (size-sent), 0, to, tnet_get_sockaddr_size(to)); // returns number of sent bytes if succeed #endif if(ret <= 0){ if(tnet_geterrno() == TNET_ERROR_WOULDBLOCK){ + TSK_DEBUG_INFO("SendUdp() - WouldBlock. Retrying..."); if(try_guard--){ - tsk_thread_sleep(7); + tsk_thread_sleep(10); goto try_again; } } else{ TNET_PRINT_LAST_ERROR("sendto() failed"); + } goto bail; } @@ -1522,14 +1526,13 @@ tsk_size_t tnet_sockfd_send(tnet_fd_t fd, const void* buf, tsk_size_t size, int while(sent < size){ if((ret = send(fd, (((const char*)buf)+sent), (size-sent), flags)) <= 0){ if(tnet_geterrno() == TNET_ERROR_WOULDBLOCK){ - // FIXME: HORRIBLE HACK if((ret = tnet_sockfd_waitUntilWritable(fd, TNET_CONNECT_TIMEOUT))){ break; } else continue; } else{ - TNET_PRINT_LAST_ERROR("send failed."); + TNET_PRINT_LAST_ERROR("send failed"); // Under Windows XP if WSAGetLastError()==WSAEINTR then try to disable both the ICS and the Firewall // More info about How to disable the ISC: http://support.microsoft.com/?scid=kb%3Ben-us%3B230112&x=6&y=11 goto bail; diff --git a/branches/2.0/doubango/tinyRTP/include/tinyrtp/rtcp/trtp_rtcp_session.h b/branches/2.0/doubango/tinyRTP/include/tinyrtp/rtcp/trtp_rtcp_session.h index 8f4e671b..3b4ee8ce 100644 --- a/branches/2.0/doubango/tinyRTP/include/tinyrtp/rtcp/trtp_rtcp_session.h +++ b/branches/2.0/doubango/tinyRTP/include/tinyrtp/rtcp/trtp_rtcp_session.h @@ -43,7 +43,7 @@ struct trtp_rtp_packet_s; typedef int (*trtp_rtcp_cb_f)(const void* callback_data, const struct trtp_rtcp_packet_s* packet); -struct trtp_rtcp_session_s* trtp_rtcp_session_create(uint32_t ssrc); +struct trtp_rtcp_session_s* trtp_rtcp_session_create(uint32_t ssrc, const char* cname); int trtp_rtcp_session_set_callback(struct trtp_rtcp_session_s* self, trtp_rtcp_cb_f callback, const void* callback_data); #if HAVE_SRTP int trtp_rtcp_session_set_srtp_sess(struct trtp_rtcp_session_s* self, const srtp_t* session); diff --git a/branches/2.0/doubango/tinyRTP/include/tinyrtp/trtp_manager.h b/branches/2.0/doubango/tinyRTP/include/tinyrtp/trtp_manager.h index 9e76cfd3..d349375f 100644 --- a/branches/2.0/doubango/tinyRTP/include/tinyrtp/trtp_manager.h +++ b/branches/2.0/doubango/tinyRTP/include/tinyrtp/trtp_manager.h @@ -100,6 +100,7 @@ typedef struct trtp_manager_s } rtp; struct{ + char* cname; char* remote_ip; tnet_port_t remote_port; struct sockaddr_storage remote_addr; diff --git a/branches/2.0/doubango/tinyRTP/src/rtcp/trtp_rtcp_report_fb.c b/branches/2.0/doubango/tinyRTP/src/rtcp/trtp_rtcp_report_fb.c index 024875e0..9a3de433 100644 --- a/branches/2.0/doubango/tinyRTP/src/rtcp/trtp_rtcp_report_fb.c +++ b/branches/2.0/doubango/tinyRTP/src/rtcp/trtp_rtcp_report_fb.c @@ -181,7 +181,7 @@ trtp_rtcp_report_rtpfb_t* trtp_rtcp_report_rtpfb_create_nack(uint32_t ssrc_sende rtpfb->nack.blp[0] = 0; for(i = 1; i <= 16 && i < count; ++i){ j = seq_nums[i] - rtpfb->nack.pid[0]; - rtpfb->nack.blp[0] |= (1 << (16 - j - 1)); + rtpfb->nack.blp[0] |= (1 << (j - 1)); } TRTP_RTCP_PACKET(rtpfb)->header->length_in_bytes += (rtpfb->nack.count << 2); diff --git a/branches/2.0/doubango/tinyRTP/src/rtcp/trtp_rtcp_session.c b/branches/2.0/doubango/tinyRTP/src/rtcp/trtp_rtcp_session.c index 4d35df10..552f1046 100644 --- a/branches/2.0/doubango/tinyRTP/src/rtcp/trtp_rtcp_session.c +++ b/branches/2.0/doubango/tinyRTP/src/rtcp/trtp_rtcp_session.c @@ -284,8 +284,7 @@ typedef struct trtp_rtcp_session_s // // - tsk_md5string_t cname; - tsk_bool_t is_cname_defined; + char* cname; uint32_t packets_count; uint32_t octets_count; // @@ -345,6 +344,7 @@ static tsk_object_t* trtp_rtcp_session_dtor(tsk_object_t * self) TSK_OBJECT_SAFE_FREE(session->sources); TSK_OBJECT_SAFE_FREE(session->source_local); TSK_OBJECT_SAFE_FREE(session->sdes); + TSK_FREE(session->cname); // release the handle for the global timer manager tsk_timer_mgr_global_unref(&session->timer.handle_global); @@ -362,7 +362,6 @@ static const tsk_object_def_t trtp_rtcp_session_def_s = const tsk_object_def_t *trtp_rtcp_session_def_t = &trtp_rtcp_session_def_s; -static void _trtp_rtcp_session_set_cname(trtp_rtcp_session_t* self, const void* random_data, tsk_size_t size); static tsk_bool_t _trtp_rtcp_session_have_source(trtp_rtcp_session_t* self, uint32_t ssrc); static trtp_rtcp_source_t* _trtp_rtcp_session_find_source(trtp_rtcp_session_t* self, uint32_t ssrc); static trtp_rtcp_source_t* _trtp_rtcp_session_find_or_add_source(trtp_rtcp_session_t* self, uint32_t ssrc, uint16_t seq_if_add, uint32_t ts_id_add); @@ -377,7 +376,7 @@ static void OnReceive(trtp_rtcp_session_t* session, const packet_ p, event_ e, t static void OnExpire(trtp_rtcp_session_t* session, event_ e); static void SendBYEPacket(trtp_rtcp_session_t* session, event_ e); -trtp_rtcp_session_t* trtp_rtcp_session_create(uint32_t ssrc) +trtp_rtcp_session_t* trtp_rtcp_session_create(uint32_t ssrc, const char* cname) { trtp_rtcp_session_t* session; @@ -398,6 +397,7 @@ trtp_rtcp_session_t* trtp_rtcp_session_create(uint32_t ssrc) session->senders = 1; session->members = 1; session->rtcp_bw = RTCP_BW;//FIXME: as parameter from the code, Also added possiblities to update this value + session->cname = tsk_strdup(cname); bail: return session; @@ -512,11 +512,6 @@ int trtp_rtcp_session_process_rtp_out(trtp_rtcp_session_t* self, const trtp_rtp_ tsk_safeobj_lock(self); - // initialize CNAME if not already done - if(!self->is_cname_defined){ - _trtp_rtcp_session_set_cname(self, packet_rtp->payload.data, packet_rtp->payload.size); - } - // create local source if not already done // first destroy it if the ssrc don't match if(self->source_local && self->source_local->ssrc != packet_rtp->header->ssrc){ @@ -800,17 +795,12 @@ static tsk_size_t _trtp_rtcp_session_send_pkt(trtp_rtcp_session_t* self, trtp_rt if(self->srtp.session) __num_bytes_pad = (SRTP_MAX_TRAILER_LEN + 0x4); #endif - if(!self->is_cname_defined){ // should not be true - uint64_t now = (tsk_time_now() ^ rand()); // not really random...but we hope it'll never called - _trtp_rtcp_session_set_cname(self, &now, sizeof(now)); - } - // SDES if(!self->sdes && (self->sdes = trtp_rtcp_report_sdes_create_null())){ trtp_rtcp_sdes_chunck_t* chunck = trtp_rtcp_sdes_chunck_create(self->source_local->ssrc); if(chunck){ static const char* _name = "test@doubango.org"; - trtp_rtcp_sdes_chunck_add_item(chunck, trtp_rtcp_sdes_item_type_cname, self->cname, TSK_MD5_STRING_SIZE); + trtp_rtcp_sdes_chunck_add_item(chunck, trtp_rtcp_sdes_item_type_cname, self->cname, tsk_strlen(self->cname)); trtp_rtcp_sdes_chunck_add_item(chunck, trtp_rtcp_sdes_item_type_name, _name, tsk_strlen(_name)); trtp_rtcp_report_sdes_add_chunck(self->sdes, chunck); TSK_OBJECT_SAFE_FREE(chunck); @@ -839,24 +829,6 @@ static tsk_size_t _trtp_rtcp_session_send_pkt(trtp_rtcp_session_t* self, trtp_rt return ret; } -// sets cname from rtp payload (sound or video) which is random xor'ed with some rand values -static void _trtp_rtcp_session_set_cname(trtp_rtcp_session_t* self, const void* random_data, tsk_size_t size) -{ - tsk_size_t i; - uint8_t _cname[16] = { 'd', 'o', 'u', 'b', 'a', 'n', 'g', 'o', 'd', 'o', 'u', 'b', 'a', 'n', 'g', 'o' }; - - if(random_data && size){ - memcpy(_cname, random_data, TSK_MIN(sizeof(_cname), size)); - } - - for(i = 0; i < sizeof(_cname); i+= 4){ - *((uint32_t*)&_cname[i]) ^= rand(); - } - - tsk_md5compute((char*)_cname, sizeof(_cname), &self->cname); - self->is_cname_defined = tsk_true; -} - static int _trtp_rtcp_session_timer_callback(const void* arg, tsk_timer_id_t timer_id) { trtp_rtcp_session_t* session = (trtp_rtcp_session_t*)arg; diff --git a/branches/2.0/doubango/tinyRTP/src/trtp_manager.c b/branches/2.0/doubango/tinyRTP/src/trtp_manager.c index fe8f7491..04e2b12f 100644 --- a/branches/2.0/doubango/tinyRTP/src/trtp_manager.c +++ b/branches/2.0/doubango/tinyRTP/src/trtp_manager.c @@ -414,7 +414,7 @@ static int _trtp_manager_recv_data(const trtp_manager_t* self, const uint8_t* da err_status_t status; if(self->srtp_ctx_neg_remote){ if((status = srtp_unprotect(self->srtp_ctx_neg_remote->rtp.session, (void*)data_ptr, (int*)&data_size)) != err_status_ok){ - TSK_DEBUG_ERROR("srtp_unprotect(RTP) failed with error code=%d", (int)status); + TSK_DEBUG_ERROR("srtp_unprotect(RTP) failed with error code=%d, seq_num=%u", (int)status, (data_size > 4 ? tnet_ntohs_2(&data_ptr[2]) : 0x0000)); return -1; } } @@ -1280,7 +1280,7 @@ int trtp_manager_start(trtp_manager_t* self) } /* create and start RTCP session */ if(!self->rtcp.session && ret == 0){ - self->rtcp.session = trtp_rtcp_session_create(self->rtp.ssrc.local); + self->rtcp.session = trtp_rtcp_session_create(self->rtp.ssrc.local, self->rtcp.cname); } if(self->rtcp.session){ ret = trtp_rtcp_session_set_callback(self->rtcp.session, self->rtcp.cb.fun, self->rtcp.cb.usrdata); @@ -1566,6 +1566,7 @@ static tsk_object_t* trtp_manager_ctor(tsk_object_t * self, va_list * app) manager->rtp.dscp = TRTP_DSCP_RTP_DEFAULT; /* rtcp */ + tsk_sprintf(&manager->rtcp.cname, "doubango@%llu", (tsk_time_now() + rand())); /* timer */ manager->timer_mgr_global = tsk_timer_mgr_global_ref(); @@ -1597,6 +1598,7 @@ static tsk_object_t* trtp_manager_dtor(tsk_object_t * self) TSK_OBJECT_SAFE_FREE(manager->rtcp.session); TSK_FREE(manager->rtcp.remote_ip); TSK_FREE(manager->rtcp.public_ip); + TSK_FREE(manager->rtcp.cname); TSK_OBJECT_SAFE_FREE(manager->rtcp.local_socket); /* SRTP */ diff --git a/branches/2.0/doubango/tinyRTP/src/trtp_srtp.c b/branches/2.0/doubango/tinyRTP/src/trtp_srtp.c index 85f236c2..1bd84ffe 100644 --- a/branches/2.0/doubango/tinyRTP/src/trtp_srtp.c +++ b/branches/2.0/doubango/tinyRTP/src/trtp_srtp.c @@ -68,6 +68,8 @@ int trtp_srtp_ctx_internal_init(struct trtp_srtp_ctx_internal_xs* ctx, int32_t t ctx->policy.key = (unsigned char*)ctx->key_bin; ctx->policy.ssrc.type = ssrc_any_outbound; ctx->policy.ssrc.value = ssrc; + ctx->policy.window_size = 1024; + ctx->policy.allow_repeat_tx = 0; if((srtp_err = srtp_create(&ctx->session, &ctx->policy)) != err_status_ok){ TSK_DEBUG_ERROR("srtp_create() failed"); return -3; @@ -236,6 +238,8 @@ int trtp_srtp_set_crypto(struct trtp_manager_s* rtp_mgr, const char* crypto_line tsk_base64_decode((const uint8_t*)srtp_ctx->rtp.key_str, tsk_strlen(srtp_ctx->rtp.key_str), (char**)&key_bin); srtp_ctx->rtp.policy.key = key_bin; srtp_ctx->rtp.policy.ssrc.type = idx == TRTP_SRTP_LINE_IDX_REMOTE ? ssrc_any_inbound : ssrc_any_outbound; + srtp_ctx->rtp.policy.window_size = 1024; + srtp_ctx->rtp.policy.allow_repeat_tx = 0; if((srtp_err = srtp_create(&srtp_ctx->rtp.session, &srtp_ctx->rtp.policy)) != err_status_ok){ TSK_DEBUG_ERROR("srtp_create() failed: %d", srtp_err); return -3; @@ -284,6 +288,8 @@ int trtp_srtp_set_key_and_salt(trtp_manager_t* rtp_mgr, trtp_srtp_crypto_type_t srtp_ctx->policy.key = (unsigned char *)srtp_ctx->key_bin; srtp_ctx->policy.ssrc.type = idx == TRTP_SRTP_LINE_IDX_REMOTE ? ssrc_any_inbound : ssrc_any_outbound; + srtp_ctx->policy.window_size = 1024; + srtp_ctx->policy.allow_repeat_tx = 0; if((srtp_err = srtp_create(&srtp_ctx->session, &srtp_ctx->policy)) != err_status_ok){ TSK_DEBUG_ERROR("srtp_create() failed: %d", srtp_err); return -3; diff --git a/branches/2.0/doubango/tinySAK/Makefile.am b/branches/2.0/doubango/tinySAK/Makefile.am index 2ba527ba..9e020219 100644 --- a/branches/2.0/doubango/tinySAK/Makefile.am +++ b/branches/2.0/doubango/tinySAK/Makefile.am @@ -1,5 +1,9 @@ lib_LTLIBRARIES = libtinySAK.la +if USE_RT +libtinySAK_la_LIBADD = ${LIBRT_LIBADD} +endif + libtinySAK_la_SOURCES = \ src/tsk.c\ src/tsk_base64.c\ diff --git a/branches/2.0/doubango/tinySAK/src/tsk_runnable.c b/branches/2.0/doubango/tinySAK/src/tsk_runnable.c index 45e289f3..982d7d92 100644 --- a/branches/2.0/doubango/tinySAK/src/tsk_runnable.c +++ b/branches/2.0/doubango/tinySAK/src/tsk_runnable.c @@ -31,6 +31,10 @@ #include "tsk_thread.h" #include "tsk_debug.h" +#if TSK_UNDER_WINDOWS +# include +#endif + /**@defgroup tsk_runnable_group Base class for runnable object. */ @@ -40,7 +44,22 @@ */ tsk_runnable_t* tsk_runnable_create() { - return (tsk_runnable_t*)tsk_object_new(tsk_runnable_def_t); + return tsk_runnable_create_2(TSK_THREAD_PRIORITY_MEDIUM); + +} + +/**@ingroup tsk_runnable_group +* Creates new Runnable object. +* @param priority Thread priority. Possible values: TSK_THREAD_PRIORITY_LOW, TSK_THREAD_PRIORITY_MEDIUM, TSK_THREAD_PRIORITY_HIGH or TSK_THREAD_PRIORITY_TIME_CRITICAL +* @retval @ref tsk_runnable_t. +*/ +tsk_runnable_t* tsk_runnable_create_2(int32_t priority) +{ + tsk_runnable_t* runnable; + if((runnable = (tsk_runnable_t*)tsk_object_new(tsk_runnable_def_t))){ + runnable->priority = priority; + } + return runnable; } /**@ingroup tsk_runnable_group @@ -117,6 +136,10 @@ int tsk_runnable_start(tsk_runnable_t *self, const tsk_object_def_t *objdef) TSK_DEBUG_ERROR("Failed to start new thread."); return ret; } + /* set priority now that the thread is created */ + if(tsk_runnable_set_priority(self, self->priority)){ + TSK_DEBUG_ERROR("Failed to set thread priority value to %d", self->priority); + } // Do not set "running" to true here // Problem: When you try to stop the thread before it start // Will be done by "TSK_RUNNABLE_RUN_BEGIN" which is called into the thread @@ -149,6 +172,21 @@ int tsk_runnable_set_important(tsk_runnable_t *self, tsk_bool_t important) } } +/**@ingroup tsk_runnable_group +*/ +int tsk_runnable_set_priority(tsk_runnable_t *self, int32_t priority) +{ + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + self->priority = priority; + if(self->h_thread[0]){ + return tsk_thread_set_priority(self->h_thread[0], priority); + } + return 0; +} + /**@ingroup tsk_runnable_group * Stops a runnable object. * @param self The runnable object to stop. @@ -217,6 +255,7 @@ static tsk_object_t* tsk_runnable_ctor(tsk_object_t * self, va_list * app) { tsk_runnable_t* runnable = (tsk_runnable_t*)self; if(runnable){ + } return self; } diff --git a/branches/2.0/doubango/tinySAK/src/tsk_runnable.h b/branches/2.0/doubango/tinySAK/src/tsk_runnable.h index fe04e7eb..347d7162 100644 --- a/branches/2.0/doubango/tinySAK/src/tsk_runnable.h +++ b/branches/2.0/doubango/tinySAK/src/tsk_runnable.h @@ -69,6 +69,8 @@ typedef struct tsk_runnable_s * default value: tsk_false */ tsk_bool_t important; + + int32_t priority; tsk_list_t *objects; } @@ -79,9 +81,11 @@ tsk_runnable_t; #define TSK_DECLARE_RUNNABLE tsk_runnable_t __runnable__ TINYSAK_API tsk_runnable_t* tsk_runnable_create(); +TINYSAK_API tsk_runnable_t* tsk_runnable_create_2(int32_t priority); TINYSAK_API int tsk_runnable_start(tsk_runnable_t *self, const tsk_object_def_t *objdef); TINYSAK_API int tsk_runnable_set_important(tsk_runnable_t *self, tsk_bool_t important); +TINYSAK_API int tsk_runnable_set_priority(tsk_runnable_t *self, int32_t priority); TINYSAK_API int tsk_runnable_enqueue(tsk_runnable_t *self, ...); TINYSAK_API int tsk_runnable_stop(tsk_runnable_t *self); diff --git a/branches/2.0/doubango/tinySAK/src/tsk_thread.c b/branches/2.0/doubango/tinySAK/src/tsk_thread.c index 4fcf84a5..ce395b19 100644 --- a/branches/2.0/doubango/tinySAK/src/tsk_thread.c +++ b/branches/2.0/doubango/tinySAK/src/tsk_thread.c @@ -92,7 +92,7 @@ int tsk_thread_set_priority(tsk_thread_handle_t* handle, int32_t priority) int ret; memset(&sp, 0, sizeof(struct sched_param)); sp.sched_priority = priority; - if ((ret = pthread_setschedparam(*((pthread_t*)handle), SCHED_RR, &sp))) { + if ((ret = pthread_setschedparam(*((pthread_t*)handle), SCHED_OTHER, &sp))) { TSK_DEBUG_ERROR("Failed to change priority to %d with error code=%d", priority, ret); return ret; } diff --git a/branches/2.0/doubango/tinySAK/src/tsk_thread.h b/branches/2.0/doubango/tinySAK/src/tsk_thread.h index 140ce0eb..ebab601d 100644 --- a/branches/2.0/doubango/tinySAK/src/tsk_thread.h +++ b/branches/2.0/doubango/tinySAK/src/tsk_thread.h @@ -35,9 +35,18 @@ typedef void tsk_thread_handle_t; #if TSK_UNDER_WINDOWS typedef unsigned long tsk_thread_id_t; +# define TSK_THREAD_PRIORITY_LOW THREAD_PRIORITY_LOWEST +# define TSK_THREAD_PRIORITY_MEDIUM THREAD_PRIORITY_NORMAL +# define TSK_THREAD_PRIORITY_HIGH THREAD_PRIORITY_HIGHEST +# define TSK_THREAD_PRIORITY_TIME_CRITICAL THREAD_PRIORITY_TIME_CRITICAL #else # include -typedef pthread_t tsk_thread_id_t; +# include + typedef pthread_t tsk_thread_id_t; +# define TSK_THREAD_PRIORITY_LOW sched_get_priority_min(SCHED_OTHER) +# define TSK_THREAD_PRIORITY_TIME_CRITICAL sched_get_priority_max(SCHED_OTHER) +# define TSK_THREAD_PRIORITY_MEDIUM ((TSK_THREAD_PRIORITY_TIME_CRITICAL - TSK_THREAD_PRIORITY_LOW) >> 1) +# define TSK_THREAD_PRIORITY_HIGH ((TSK_THREAD_PRIORITY_MEDIUM * 3) >> 1) #endif TSK_BEGIN_DECLS diff --git a/branches/2.0/doubango/tinySAK/src/tsk_time.c b/branches/2.0/doubango/tinySAK/src/tsk_time.c index 7514e29c..c0f1c646 100644 --- a/branches/2.0/doubango/tinySAK/src/tsk_time.c +++ b/branches/2.0/doubango/tinySAK/src/tsk_time.c @@ -124,6 +124,15 @@ int tsk_gettimeofday(struct timeval *tv, struct timezone *tz) return gettimeofday(tv, tz); } +/**@ingroup tsk_time_group +*/ +uint64_t tsk_gettimeofday_ms() +{ + struct timeval tv; + tsk_gettimeofday(&tv, tsk_null); + return (((uint64_t)tv.tv_sec)*(uint64_t)1000) + (((uint64_t)tv.tv_usec)/(uint64_t)1000); +} + /**@ingroup tsk_time_group * Gets the number of milliseconds in @a tv * @retval The number of milliseconds diff --git a/branches/2.0/doubango/tinySAK/src/tsk_time.h b/branches/2.0/doubango/tinySAK/src/tsk_time.h index b0c81078..512d59e6 100644 --- a/branches/2.0/doubango/tinySAK/src/tsk_time.h +++ b/branches/2.0/doubango/tinySAK/src/tsk_time.h @@ -46,6 +46,7 @@ struct timespec; #define TSK_TIME_MS_2_S(MS) ((MS)/1000) TINYSAK_API int tsk_gettimeofday(struct timeval *tv, struct timezone *tz); +TINYSAK_API uint64_t tsk_gettimeofday_ms(); TINYSAK_API uint64_t tsk_time_get_ms(const struct timeval *tv); TINYSAK_API uint64_t tsk_time_epoch(); TINYSAK_API uint64_t tsk_time_now();