From bb8a15d8b57187402b91df86aa228c8ef29f7eb7 Mon Sep 17 00:00:00 2001 From: "niklase@google.com" Date: Mon, 30 May 2011 11:43:19 +0000 Subject: [PATCH] git-svn-id: http://webrtc.googlecode.com/svn/trunk@9 4adac7df-926f-26a2-2b94-8c16560cd09d --- video_engine/OWNERS | 4 + video_engine/main/interface/vie_base.h | 153 + video_engine/main/interface/vie_capture.h | 234 ++ video_engine/main/interface/vie_codec.h | 185 + video_engine/main/interface/vie_encryption.h | 91 + video_engine/main/interface/vie_errors.h | 121 + .../main/interface/vie_external_codec.h | 54 + video_engine/main/interface/vie_file.h | 229 ++ .../main/interface/vie_image_process.h | 108 + video_engine/main/interface/vie_network.h | 214 ++ video_engine/main/interface/vie_render.h | 106 + video_engine/main/interface/vie_rtp_rtcp.h | 262 ++ .../main/source/video_engine_core.gyp | 133 + video_engine/main/source/vie_base_impl.cc | 838 +++++ video_engine/main/source/vie_base_impl.h | 101 + video_engine/main/source/vie_capture_impl.cc | 792 ++++ video_engine/main/source/vie_capture_impl.h | 110 + video_engine/main/source/vie_capturer.cc | 1092 ++++++ video_engine/main/source/vie_capturer.h | 202 ++ video_engine/main/source/vie_channel.cc | 3222 +++++++++++++++++ video_engine/main/source/vie_channel.h | 474 +++ .../main/source/vie_channel_manager.cc | 594 +++ .../main/source/vie_channel_manager.h | 102 + video_engine/main/source/vie_codec_impl.cc | 977 +++++ video_engine/main/source/vie_codec_impl.h | 110 + video_engine/main/source/vie_defines.h | 294 ++ video_engine/main/source/vie_encoder.cc | 1017 ++++++ video_engine/main/source/vie_encoder.h | 183 + .../main/source/vie_encryption_impl.cc | 543 +++ .../main/source/vie_encryption_impl.h | 75 + .../main/source/vie_external_codec_impl.cc | 203 ++ .../main/source/vie_external_codec_impl.h | 54 + video_engine/main/source/vie_file_image.cc | 107 + video_engine/main/source/vie_file_image.h | 34 + video_engine/main/source/vie_file_impl.cc | 1285 +++++++ video_engine/main/source/vie_file_impl.h | 175 + video_engine/main/source/vie_file_player.cc | 572 +++ video_engine/main/source/vie_file_player.h | 124 + video_engine/main/source/vie_file_recorder.cc | 281 ++ video_engine/main/source/vie_file_recorder.h | 64 + .../main/source/vie_frame_provider_base.cc | 310 ++ .../main/source/vie_frame_provider_base.h | 100 + .../main/source/vie_image_process_impl.cc | 396 ++ .../main/source/vie_image_process_impl.h | 66 + video_engine/main/source/vie_impl.cc | 301 ++ video_engine/main/source/vie_impl.h | 90 + video_engine/main/source/vie_input_manager.cc | 817 +++++ video_engine/main/source/vie_input_manager.h | 121 + video_engine/main/source/vie_manager_base.cc | 113 + video_engine/main/source/vie_manager_base.h | 63 + video_engine/main/source/vie_network_impl.cc | 1126 ++++++ video_engine/main/source/vie_network_impl.h | 137 + .../main/source/vie_performance_monitor.cc | 194 + .../main/source/vie_performance_monitor.h | 63 + video_engine/main/source/vie_receiver.cc | 536 +++ video_engine/main/source/vie_receiver.h | 116 + video_engine/main/source/vie_ref_count.cc | 58 + video_engine/main/source/vie_ref_count.h | 39 + video_engine/main/source/vie_render_impl.cc | 563 +++ video_engine/main/source/vie_render_impl.h | 77 + .../main/source/vie_render_manager.cc | 300 ++ video_engine/main/source/vie_render_manager.h | 86 + video_engine/main/source/vie_renderer.cc | 248 ++ video_engine/main/source/vie_renderer.h | 120 + video_engine/main/source/vie_rtp_rtcp_impl.cc | 1275 +++++++ video_engine/main/source/vie_rtp_rtcp_impl.h | 141 + video_engine/main/source/vie_sender.cc | 399 ++ video_engine/main/source/vie_sender.h | 84 + video_engine/main/source/vie_shared_data.cc | 91 + video_engine/main/source/vie_shared_data.h | 56 + video_engine/main/source/vie_sync_module.cc | 332 ++ video_engine/main/source/vie_sync_module.h | 84 + video_engine/main/test/AndroidTest/.classpath | 9 + video_engine/main/test/AndroidTest/.project | 33 + .../main/test/AndroidTest/AndroidManifest.xml | 25 + .../main/test/AndroidTest/default.properties | 13 + .../gen/org/webrtc/androidapp/R.java | 68 + ...brtc_videoengineapp_vie_android_java_api.h | 442 +++ .../AndroidTest/jni/vie_android_java_api.cc | 1751 +++++++++ .../test/AndroidTest/res/drawable/logo.png | Bin 0 -> 409 bytes .../test/AndroidTest/res/layout/aconfig.xml | 35 + .../main/test/AndroidTest/res/layout/both.xml | 43 + .../main/test/AndroidTest/res/layout/main.xml | 19 + .../main/test/AndroidTest/res/layout/send.xml | 11 + .../test/AndroidTest/res/layout/stats.xml | 37 + .../test/AndroidTest/res/layout/tabhost.xml | 25 + .../test/AndroidTest/res/layout/vconfig.xml | 29 + .../test/AndroidTest/res/values/arrays.xml | 25 + .../test/AndroidTest/res/values/strings.xml | 36 + .../videoengineapp/IViEAndroidCallback.java | 19 + .../webrtc/videoengineapp/ViEAndroidDemo.java | 775 ++++ .../videoengineapp/ViEAndroidJavaAPI.java | 150 + .../main/test/AutoTest/Android/.classpath | 9 + .../main/test/AutoTest/Android/.project | 33 + .../test/AutoTest/Android/AndroidManifest.xml | 23 + .../test/AutoTest/Android/default.properties | 11 + .../Android/gen/org/webrtc/vieautotest/R.java | 37 + .../jni/org_webrtc_vieautotest_vie_autotest.h | 33 + .../AutoTest/Android/jni/vie_autotest_jni.cc | 150 + .../AutoTest/Android/res/drawable/logo.png | Bin 0 -> 409 bytes .../test/AutoTest/Android/res/layout/main.xml | 64 + .../AutoTest/Android/res/values/strings.xml | 31 + .../org/webrtc/vieautotest/ViEAutotest.java | 159 + .../test/AutoTest/interface/tb_I420_codec.h | 132 + .../AutoTest/interface/tb_capture_device.h | 32 + .../interface/tb_external_transport.h | 95 + .../test/AutoTest/interface/tb_interfaces.h | 54 + .../AutoTest/interface/tb_video_channel.h | 42 + .../test/AutoTest/interface/vie_autotest.h | 143 + .../AutoTest/interface/vie_autotest_android.h | 26 + .../AutoTest/interface/vie_autotest_defines.h | 253 ++ .../AutoTest/interface/vie_autotest_linux.h | 44 + .../interface/vie_autotest_mac_carbon.h | 63 + .../interface/vie_autotest_mac_cocoa.h | 52 + .../AutoTest/interface/vie_autotest_main.h | 37 + .../vie_autotest_window_manager_interface.h | 32 + .../AutoTest/interface/vie_autotest_windows.h | 64 + .../AutoTest/media/captureDeviceImage.bmp | Bin 0 -> 304182 bytes .../AutoTest/media/captureDeviceImage.jpg | Bin 0 -> 10742 bytes .../test/AutoTest/media/renderStartImage.bmp | Bin 0 -> 304182 bytes .../test/AutoTest/media/renderStartImage.jpg | Bin 0 -> 10274 bytes .../AutoTest/media/renderTimeoutImage.bmp | Bin 0 -> 304182 bytes .../AutoTest/media/renderTimeoutImage.jpg | Bin 0 -> 10554 bytes .../test/AutoTest/source/tb_I420_codec.cc | 317 ++ .../test/AutoTest/source/tb_capture_device.cc | 100 + .../AutoTest/source/tb_external_transport.cc | 268 ++ .../test/AutoTest/source/tb_interfaces.cc | 136 + .../test/AutoTest/source/tb_video_channel.cc | 135 + .../main/test/AutoTest/source/vie_autotest.cc | 176 + .../AutoTest/source/vie_autotest_android.cc | 215 ++ .../test/AutoTest/source/vie_autotest_base.cc | 578 +++ .../AutoTest/source/vie_autotest_capture.cc | 808 +++++ .../AutoTest/source/vie_autotest_codec.cc | 1298 +++++++ .../source/vie_autotest_custom_call.cc | 950 +++++ .../source/vie_autotest_encryption.cc | 903 +++++ .../test/AutoTest/source/vie_autotest_file.cc | 858 +++++ .../source/vie_autotest_image_process.cc | 420 +++ .../AutoTest/source/vie_autotest_linux.cc | 151 + .../AutoTest/source/vie_autotest_loopback.cc | 509 +++ .../source/vie_autotest_mac_carbon.cc | 358 ++ .../AutoTest/source/vie_autotest_mac_cocoa.cc | 156 + .../test/AutoTest/source/vie_autotest_main.cc | 398 ++ .../AutoTest/source/vie_autotest_network.cc | 1058 ++++++ .../AutoTest/source/vie_autotest_render.cc | 500 +++ .../AutoTest/source/vie_autotest_rtp_rtcp.cc | 966 +++++ .../AutoTest/source/vie_autotest_windows.cc | 249 ++ .../main/test/AutoTest/vie_auto_test.gypi | 183 + .../main/test/SimpleCocoaGUI/GUI_Defines.h | 36 + .../SimpleCocoaGUI/SimpleCocoaGUI-Info.plist | 32 + .../SimpleCocoaGUIAppDelegate.h | 77 + .../SimpleCocoaGUIAppDelegate.mm | 1075 ++++++ .../SimpleCocoaGUI/SimpleCocoaGUI_Prefix.pch | 7 + video_engine/main/test/SimpleCocoaGUI/main.m | 12 + video_engine/main/test/WindowsTest/Capture.rc | 254 ++ .../test/WindowsTest/CaptureDevicePool.cpp | 98 + .../main/test/WindowsTest/CaptureDevicePool.h | 49 + .../main/test/WindowsTest/ChannelDlg.cpp | 1279 +++++++ .../main/test/WindowsTest/ChannelDlg.h | 272 ++ .../main/test/WindowsTest/ChannelPool.cpp | 40 + .../main/test/WindowsTest/ChannelPool.h | 36 + video_engine/main/test/WindowsTest/StdAfx.h | 37 + .../main/test/WindowsTest/VideoSize.h | 53 + .../main/test/WindowsTest/WindowsTest.cpp | 114 + .../main/test/WindowsTest/WindowsTest.h | 61 + .../test/WindowsTest/WindowsTestMainDlg.cpp | 142 + .../test/WindowsTest/WindowsTestMainDlg.h | 72 + .../test/WindowsTest/WindowsTestResouce.rc | 101 + .../test/WindowsTest/WindowsTestResource.h | 28 + .../test/WindowsTest/captureDeviceImage.jpg | Bin 0 -> 10742 bytes .../test/WindowsTest/renderStartImage.jpg | Bin 0 -> 10274 bytes .../test/WindowsTest/renderTimeoutImage.jpg | Bin 0 -> 10554 bytes .../main/test/WindowsTest/res/Capture.rc2 | 13 + video_engine/main/test/WindowsTest/resource.h | 69 + .../test/WindowsTest/tbExternalTransport.cpp | 317 ++ .../test/WindowsTest/tbExternalTransport.h | 106 + .../main/test/WindowsTest/videosize.cpp | 140 + .../main/test/WindowsTest/windowstest.gyp | 73 + 177 files changed, 45468 insertions(+) create mode 100644 video_engine/OWNERS create mode 100644 video_engine/main/interface/vie_base.h create mode 100644 video_engine/main/interface/vie_capture.h create mode 100644 video_engine/main/interface/vie_codec.h create mode 100644 video_engine/main/interface/vie_encryption.h create mode 100644 video_engine/main/interface/vie_errors.h create mode 100644 video_engine/main/interface/vie_external_codec.h create mode 100644 video_engine/main/interface/vie_file.h create mode 100644 video_engine/main/interface/vie_image_process.h create mode 100644 video_engine/main/interface/vie_network.h create mode 100644 video_engine/main/interface/vie_render.h create mode 100644 video_engine/main/interface/vie_rtp_rtcp.h create mode 100644 video_engine/main/source/video_engine_core.gyp create mode 100644 video_engine/main/source/vie_base_impl.cc create mode 100644 video_engine/main/source/vie_base_impl.h create mode 100644 video_engine/main/source/vie_capture_impl.cc create mode 100644 video_engine/main/source/vie_capture_impl.h create mode 100644 video_engine/main/source/vie_capturer.cc create mode 100644 video_engine/main/source/vie_capturer.h create mode 100644 video_engine/main/source/vie_channel.cc create mode 100644 video_engine/main/source/vie_channel.h create mode 100644 video_engine/main/source/vie_channel_manager.cc create mode 100644 video_engine/main/source/vie_channel_manager.h create mode 100644 video_engine/main/source/vie_codec_impl.cc create mode 100644 video_engine/main/source/vie_codec_impl.h create mode 100644 video_engine/main/source/vie_defines.h create mode 100644 video_engine/main/source/vie_encoder.cc create mode 100644 video_engine/main/source/vie_encoder.h create mode 100644 video_engine/main/source/vie_encryption_impl.cc create mode 100644 video_engine/main/source/vie_encryption_impl.h create mode 100644 video_engine/main/source/vie_external_codec_impl.cc create mode 100644 video_engine/main/source/vie_external_codec_impl.h create mode 100644 video_engine/main/source/vie_file_image.cc create mode 100644 video_engine/main/source/vie_file_image.h create mode 100644 video_engine/main/source/vie_file_impl.cc create mode 100644 video_engine/main/source/vie_file_impl.h create mode 100644 video_engine/main/source/vie_file_player.cc create mode 100644 video_engine/main/source/vie_file_player.h create mode 100644 video_engine/main/source/vie_file_recorder.cc create mode 100644 video_engine/main/source/vie_file_recorder.h create mode 100644 video_engine/main/source/vie_frame_provider_base.cc create mode 100644 video_engine/main/source/vie_frame_provider_base.h create mode 100644 video_engine/main/source/vie_image_process_impl.cc create mode 100644 video_engine/main/source/vie_image_process_impl.h create mode 100644 video_engine/main/source/vie_impl.cc create mode 100644 video_engine/main/source/vie_impl.h create mode 100644 video_engine/main/source/vie_input_manager.cc create mode 100644 video_engine/main/source/vie_input_manager.h create mode 100644 video_engine/main/source/vie_manager_base.cc create mode 100644 video_engine/main/source/vie_manager_base.h create mode 100644 video_engine/main/source/vie_network_impl.cc create mode 100644 video_engine/main/source/vie_network_impl.h create mode 100644 video_engine/main/source/vie_performance_monitor.cc create mode 100644 video_engine/main/source/vie_performance_monitor.h create mode 100644 video_engine/main/source/vie_receiver.cc create mode 100644 video_engine/main/source/vie_receiver.h create mode 100644 video_engine/main/source/vie_ref_count.cc create mode 100644 video_engine/main/source/vie_ref_count.h create mode 100644 video_engine/main/source/vie_render_impl.cc create mode 100644 video_engine/main/source/vie_render_impl.h create mode 100644 video_engine/main/source/vie_render_manager.cc create mode 100644 video_engine/main/source/vie_render_manager.h create mode 100644 video_engine/main/source/vie_renderer.cc create mode 100644 video_engine/main/source/vie_renderer.h create mode 100644 video_engine/main/source/vie_rtp_rtcp_impl.cc create mode 100644 video_engine/main/source/vie_rtp_rtcp_impl.h create mode 100644 video_engine/main/source/vie_sender.cc create mode 100644 video_engine/main/source/vie_sender.h create mode 100644 video_engine/main/source/vie_shared_data.cc create mode 100644 video_engine/main/source/vie_shared_data.h create mode 100644 video_engine/main/source/vie_sync_module.cc create mode 100644 video_engine/main/source/vie_sync_module.h create mode 100644 video_engine/main/test/AndroidTest/.classpath create mode 100644 video_engine/main/test/AndroidTest/.project create mode 100644 video_engine/main/test/AndroidTest/AndroidManifest.xml create mode 100644 video_engine/main/test/AndroidTest/default.properties create mode 100644 video_engine/main/test/AndroidTest/gen/org/webrtc/androidapp/R.java create mode 100644 video_engine/main/test/AndroidTest/jni/org_webrtc_videoengineapp_vie_android_java_api.h create mode 100644 video_engine/main/test/AndroidTest/jni/vie_android_java_api.cc create mode 100644 video_engine/main/test/AndroidTest/res/drawable/logo.png create mode 100644 video_engine/main/test/AndroidTest/res/layout/aconfig.xml create mode 100644 video_engine/main/test/AndroidTest/res/layout/both.xml create mode 100644 video_engine/main/test/AndroidTest/res/layout/main.xml create mode 100644 video_engine/main/test/AndroidTest/res/layout/send.xml create mode 100644 video_engine/main/test/AndroidTest/res/layout/stats.xml create mode 100644 video_engine/main/test/AndroidTest/res/layout/tabhost.xml create mode 100644 video_engine/main/test/AndroidTest/res/layout/vconfig.xml create mode 100644 video_engine/main/test/AndroidTest/res/values/arrays.xml create mode 100644 video_engine/main/test/AndroidTest/res/values/strings.xml create mode 100644 video_engine/main/test/AndroidTest/src/org/webrtc/videoengineapp/IViEAndroidCallback.java create mode 100644 video_engine/main/test/AndroidTest/src/org/webrtc/videoengineapp/ViEAndroidDemo.java create mode 100644 video_engine/main/test/AndroidTest/src/org/webrtc/videoengineapp/ViEAndroidJavaAPI.java create mode 100644 video_engine/main/test/AutoTest/Android/.classpath create mode 100644 video_engine/main/test/AutoTest/Android/.project create mode 100644 video_engine/main/test/AutoTest/Android/AndroidManifest.xml create mode 100644 video_engine/main/test/AutoTest/Android/default.properties create mode 100644 video_engine/main/test/AutoTest/Android/gen/org/webrtc/vieautotest/R.java create mode 100644 video_engine/main/test/AutoTest/Android/jni/org_webrtc_vieautotest_vie_autotest.h create mode 100644 video_engine/main/test/AutoTest/Android/jni/vie_autotest_jni.cc create mode 100644 video_engine/main/test/AutoTest/Android/res/drawable/logo.png create mode 100644 video_engine/main/test/AutoTest/Android/res/layout/main.xml create mode 100644 video_engine/main/test/AutoTest/Android/res/values/strings.xml create mode 100644 video_engine/main/test/AutoTest/Android/src/org/webrtc/vieautotest/ViEAutotest.java create mode 100644 video_engine/main/test/AutoTest/interface/tb_I420_codec.h create mode 100644 video_engine/main/test/AutoTest/interface/tb_capture_device.h create mode 100644 video_engine/main/test/AutoTest/interface/tb_external_transport.h create mode 100644 video_engine/main/test/AutoTest/interface/tb_interfaces.h create mode 100644 video_engine/main/test/AutoTest/interface/tb_video_channel.h create mode 100644 video_engine/main/test/AutoTest/interface/vie_autotest.h create mode 100644 video_engine/main/test/AutoTest/interface/vie_autotest_android.h create mode 100644 video_engine/main/test/AutoTest/interface/vie_autotest_defines.h create mode 100644 video_engine/main/test/AutoTest/interface/vie_autotest_linux.h create mode 100644 video_engine/main/test/AutoTest/interface/vie_autotest_mac_carbon.h create mode 100644 video_engine/main/test/AutoTest/interface/vie_autotest_mac_cocoa.h create mode 100644 video_engine/main/test/AutoTest/interface/vie_autotest_main.h create mode 100644 video_engine/main/test/AutoTest/interface/vie_autotest_window_manager_interface.h create mode 100644 video_engine/main/test/AutoTest/interface/vie_autotest_windows.h create mode 100644 video_engine/main/test/AutoTest/media/captureDeviceImage.bmp create mode 100644 video_engine/main/test/AutoTest/media/captureDeviceImage.jpg create mode 100644 video_engine/main/test/AutoTest/media/renderStartImage.bmp create mode 100644 video_engine/main/test/AutoTest/media/renderStartImage.jpg create mode 100644 video_engine/main/test/AutoTest/media/renderTimeoutImage.bmp create mode 100644 video_engine/main/test/AutoTest/media/renderTimeoutImage.jpg create mode 100644 video_engine/main/test/AutoTest/source/tb_I420_codec.cc create mode 100644 video_engine/main/test/AutoTest/source/tb_capture_device.cc create mode 100644 video_engine/main/test/AutoTest/source/tb_external_transport.cc create mode 100644 video_engine/main/test/AutoTest/source/tb_interfaces.cc create mode 100644 video_engine/main/test/AutoTest/source/tb_video_channel.cc create mode 100644 video_engine/main/test/AutoTest/source/vie_autotest.cc create mode 100644 video_engine/main/test/AutoTest/source/vie_autotest_android.cc create mode 100644 video_engine/main/test/AutoTest/source/vie_autotest_base.cc create mode 100644 video_engine/main/test/AutoTest/source/vie_autotest_capture.cc create mode 100644 video_engine/main/test/AutoTest/source/vie_autotest_codec.cc create mode 100644 video_engine/main/test/AutoTest/source/vie_autotest_custom_call.cc create mode 100644 video_engine/main/test/AutoTest/source/vie_autotest_encryption.cc create mode 100644 video_engine/main/test/AutoTest/source/vie_autotest_file.cc create mode 100644 video_engine/main/test/AutoTest/source/vie_autotest_image_process.cc create mode 100644 video_engine/main/test/AutoTest/source/vie_autotest_linux.cc create mode 100644 video_engine/main/test/AutoTest/source/vie_autotest_loopback.cc create mode 100644 video_engine/main/test/AutoTest/source/vie_autotest_mac_carbon.cc create mode 100644 video_engine/main/test/AutoTest/source/vie_autotest_mac_cocoa.cc create mode 100644 video_engine/main/test/AutoTest/source/vie_autotest_main.cc create mode 100644 video_engine/main/test/AutoTest/source/vie_autotest_network.cc create mode 100644 video_engine/main/test/AutoTest/source/vie_autotest_render.cc create mode 100644 video_engine/main/test/AutoTest/source/vie_autotest_rtp_rtcp.cc create mode 100644 video_engine/main/test/AutoTest/source/vie_autotest_windows.cc create mode 100644 video_engine/main/test/AutoTest/vie_auto_test.gypi create mode 100644 video_engine/main/test/SimpleCocoaGUI/GUI_Defines.h create mode 100644 video_engine/main/test/SimpleCocoaGUI/SimpleCocoaGUI-Info.plist create mode 100644 video_engine/main/test/SimpleCocoaGUI/SimpleCocoaGUIAppDelegate.h create mode 100644 video_engine/main/test/SimpleCocoaGUI/SimpleCocoaGUIAppDelegate.mm create mode 100644 video_engine/main/test/SimpleCocoaGUI/SimpleCocoaGUI_Prefix.pch create mode 100644 video_engine/main/test/SimpleCocoaGUI/main.m create mode 100644 video_engine/main/test/WindowsTest/Capture.rc create mode 100644 video_engine/main/test/WindowsTest/CaptureDevicePool.cpp create mode 100644 video_engine/main/test/WindowsTest/CaptureDevicePool.h create mode 100644 video_engine/main/test/WindowsTest/ChannelDlg.cpp create mode 100644 video_engine/main/test/WindowsTest/ChannelDlg.h create mode 100644 video_engine/main/test/WindowsTest/ChannelPool.cpp create mode 100644 video_engine/main/test/WindowsTest/ChannelPool.h create mode 100644 video_engine/main/test/WindowsTest/StdAfx.h create mode 100644 video_engine/main/test/WindowsTest/VideoSize.h create mode 100644 video_engine/main/test/WindowsTest/WindowsTest.cpp create mode 100644 video_engine/main/test/WindowsTest/WindowsTest.h create mode 100644 video_engine/main/test/WindowsTest/WindowsTestMainDlg.cpp create mode 100644 video_engine/main/test/WindowsTest/WindowsTestMainDlg.h create mode 100644 video_engine/main/test/WindowsTest/WindowsTestResouce.rc create mode 100644 video_engine/main/test/WindowsTest/WindowsTestResource.h create mode 100644 video_engine/main/test/WindowsTest/captureDeviceImage.jpg create mode 100644 video_engine/main/test/WindowsTest/renderStartImage.jpg create mode 100644 video_engine/main/test/WindowsTest/renderTimeoutImage.jpg create mode 100644 video_engine/main/test/WindowsTest/res/Capture.rc2 create mode 100644 video_engine/main/test/WindowsTest/resource.h create mode 100644 video_engine/main/test/WindowsTest/tbExternalTransport.cpp create mode 100644 video_engine/main/test/WindowsTest/tbExternalTransport.h create mode 100644 video_engine/main/test/WindowsTest/videosize.cpp create mode 100644 video_engine/main/test/WindowsTest/windowstest.gyp diff --git a/video_engine/OWNERS b/video_engine/OWNERS new file mode 100644 index 0000000000..435723d107 --- /dev/null +++ b/video_engine/OWNERS @@ -0,0 +1,4 @@ +mflodman@google.com +perkj@google.com +ronghuawu@google.com +mallinath@google.com diff --git a/video_engine/main/interface/vie_base.h b/video_engine/main/interface/vie_base.h new file mode 100644 index 0000000000..2e6731af9c --- /dev/null +++ b/video_engine/main/interface/vie_base.h @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// This sub-API supports the following functionalities: +// +// - Creating and deleting VideoEngine instances. +// - Creating and deleting channels. +// - Connect a video channel with a corresponding voice channel for audio/video synchronization. +// - Start and stop sending and receiving. + + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_BASE_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_BASE_H_ + +#include "common_types.h" + + +// Forward declarations +namespace webrtc +{ + +class VoiceEngine; + +// ---------------------------------------------------------------------------- +// VideoEngine Callbacks +// ---------------------------------------------------------------------------- + +class WEBRTC_DLLEXPORT ViEBaseObserver +{ +public: + // This method will be called periodically if the average system CPU usage + // exceeds 75%. + virtual void PerformanceAlarm(const unsigned int cpuLoad) = 0; + +protected: + virtual ~ViEBaseObserver() {}; +}; + +// ---------------------------------------------------------------------------- +// VideoEngine +// ---------------------------------------------------------------------------- + +class WEBRTC_DLLEXPORT VideoEngine +{ +public: + // Creates a VideoEngine object, which can then be used to acquire sub‐APIs. + static VideoEngine* Create(); + + // Deletes a VideoEngine instance. + static bool Delete(VideoEngine*& videoEngine); + + // Specifies the amount and type of trace information, which will be created + // by the VideoEngine. + static int SetTraceFilter(const unsigned int filter); + + // Sets the name of the trace file and enables non‐encrypted trace messages. + static int SetTraceFile(const char* fileNameUTF8, + const bool addFileCounter = false); + + // Installs the TraceCallback implementation to ensure that the VideoEngine + // user receives callbacks for generated trace messages. + static int SetTraceCallback(TraceCallback* callback); + + // Android specific + // Provides VideoEngine with pointers to objects supplied by the Java + // applications JNI interface. + static int SetAndroidObjects(void* javaVM, void* javaContext); + +protected: + VideoEngine() {}; + virtual ~VideoEngine() {}; +}; + +// ---------------------------------------------------------------------------- +// VideoBase +// ---------------------------------------------------------------------------- + +class WEBRTC_DLLEXPORT ViEBase +{ +public: + // Factory for the ViEBase sub‐API and increases an internal reference + // counter if successful. Returns NULL if the API is not supported or if + // construction fails. + static ViEBase* GetInterface(VideoEngine* videoEngine); + + // Releases the ViEBase sub-API and decreases an internal reference counter. + // Returns the new reference count. This value should be zero + // for all sub-API:s before the VideoEngine object can be safely deleted. + virtual int Release() = 0; + + // Initiates all common parts of the VideoEngine. + virtual int Init() = 0; + + // Connects a VideoEngine instance to a VoiceEngine instance for audio video + // synchronization. + virtual int SetVoiceEngine(VoiceEngine* ptrVoiceEngine) = 0; + + // Creates a new channel, either with a new encoder instance or by sharing + // encoder instance with an already created channel. + virtual int CreateChannel(int& videoChannel) = 0; + virtual int CreateChannel(int& videoChannel, int originalChannel) = 0; + + // Deletes an existing channel and releases the utilized resources. + virtual int DeleteChannel(const int videoChannel) = 0; + + // Specifies the VoiceEngine and VideoEngine channel pair to use for + // audio/video synchronization. + virtual int ConnectAudioChannel(const int videoChannel, + const int audioChannel) = 0; + + // Disconnects a previously paired VideoEngine and VoiceEngine channel pair. + virtual int DisconnectAudioChannel(const int videoChannel) = 0; + + // Starts sending packets to an already specified IP address and port number + // for a specified channel. + virtual int StartSend(const int videoChannel) = 0; + + // Stops packets from being sent for a specified channel. + virtual int StopSend(const int videoChannel) = 0; + + // Prepares VideoEngine for receiving packets on the specified channel. + virtual int StartReceive(const int videoChannel) = 0; + + // Stops receiving incoming RTP and RTCP packets on the specified channel. + virtual int StopReceive(const int videoChannel) = 0; + + // Registers an instance of a user implementation of the ViEBase + // observer. + virtual int RegisterObserver(ViEBaseObserver& observer) = 0; + + // Removes an already registered instance of ViEBaseObserver. + virtual int DeregisterObserver() = 0; + + // Retrieves the version information for VideoEngine and its components. + virtual int GetVersion(char version[1024]) = 0; + + // Returns the last VideoEngine error code. + virtual int LastError() = 0; + +protected: + ViEBase() {}; + virtual ~ViEBase(){}; +}; + +} // namespace webrtc +#endif // #define WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_BASE_H_ diff --git a/video_engine/main/interface/vie_capture.h b/video_engine/main/interface/vie_capture.h new file mode 100644 index 0000000000..7bd71d32cd --- /dev/null +++ b/video_engine/main/interface/vie_capture.h @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// This sub-API supports the following functionalities: +// +// - Allocating capture devices. +// - Connect a capture device with one or more channels. +// - Start and stop capture devices. +// - Getting capture device capabilities. + + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_CAPTURE_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_CAPTURE_H_ + +#include "common_types.h" + +namespace webrtc +{ + +class VideoEngine; +class VideoCaptureModule; + +// This structure describes one set of the supported capabilities for a capture +// device. +struct CaptureCapability +{ + unsigned int width; + unsigned int height; + unsigned int maxFPS; + RawVideoType rawType; + VideoCodecType codecType; + unsigned int expectedCaptureDelay; + bool interlaced; + CaptureCapability() + { + width = 0; + height = 0; + maxFPS = 0; + rawType = kVideoI420; + codecType = kVideoCodecUnknown; + expectedCaptureDelay = 0; + interlaced = false; + } +}; + +// This enumerator tells the current brightness alarm mode. +enum Brightness +{ + Normal = 0, + Bright = 1, + Dark = 2 +}; + +// This enumerator describes the capture alarm mode. +enum CaptureAlarm +{ + AlarmRaised = 0, + AlarmCleared = 1 +}; + +enum RotateCapturedFrame +{ + RotateCapturedFrame_0 = 0, + RotateCapturedFrame_90 = 90, + RotateCapturedFrame_180 = 180, + RotateCapturedFrame_270 = 270 +}; + +// This class declares an abstract interface to be used when using an external +// capture device. The user implemented derived class is registered using +// AllocateExternalCaptureDevice and is released using ReleaseCaptureDevice. +class WEBRTC_DLLEXPORT ViEExternalCapture +{ +public: + ViEExternalCapture() {} + virtual ~ViEExternalCapture() {} + + // This method is called by the user to deliver a new captured frame to + // VideoEngine. + virtual int IncomingFrame(unsigned char* videoFrame, + unsigned int videoFrameLength, + unsigned short width, unsigned short height, + RawVideoType videoType, + unsigned long long captureTime = 0) = 0; +}; + +// ---------------------------------------------------------------------------- +// ViECaptureObserver +// ---------------------------------------------------------------------------- + +// This class declares an abstract interface for a user defined observer. It is +// up to the VideoEngine user to implement a derived class which implements the +// observer class. The observer is registered using RegisterObserver() and +// deregistered using DeregisterObserver(). +class WEBRTC_DLLEXPORT ViECaptureObserver +{ +public: + // This method is called if a bright or dark captured image is detected. + virtual void BrightnessAlarm(const int captureId, + const Brightness brightness) = 0; + + // This method is called periodically telling the capture device frame rate. + virtual void CapturedFrameRate(const int captureId, + const unsigned char frameRate) = 0; + + // This method is called if the capture device stops delivering images to + // VideoEngine. + virtual void NoPictureAlarm(const int captureId, + const CaptureAlarm alarm) = 0; + +protected: + virtual ~ViECaptureObserver() + { + } +}; + +// ---------------------------------------------------------------------------- +// ViECapture +// ---------------------------------------------------------------------------- + +class WEBRTC_DLLEXPORT ViECapture +{ +public: + // Factory for the ViECapture sub‐API and increases an internal reference + // counter if successful. Returns NULL if the API is not supported or if + // construction fails. + static ViECapture* GetInterface(VideoEngine* videoEngine); + + // Releases the ViECapture sub-API and decreases an internal reference + // counter. + // Returns the new reference count. This value should be zero + // for all sub-API:s before the VideoEngine object can be safely deleted. + virtual int Release() = 0; + + + // Gets the number of available capture devices. + virtual int NumberOfCaptureDevices() = 0; + + // Gets the name and unique id of a capture device. + virtual int GetCaptureDevice(unsigned int listNumber, char* deviceNameUTF8, + const unsigned int deviceNameUTF8Length, + char* uniqueIdUTF8, + const unsigned int uniqueIdUTF8Length) = 0; + + // Allocates a capture device to be used in VideoEngine. + virtual int AllocateCaptureDevice(const char* uniqueIdUTF8, + const unsigned int uniqueIdUTF8Length, + int& captureId) = 0; + + // Registers an external capture device to be used in VideoEngine + virtual int AllocateExternalCaptureDevice( + int& captureId, ViEExternalCapture *&externalCapture) = 0; + + // Use capture device using external capture module. + virtual int AllocateCaptureDevice(VideoCaptureModule& captureModule, + int& captureId) = 0; + + // Releases a capture device and makes it available for other applications. + virtual int ReleaseCaptureDevice(const int captureId) = 0; + + // This function connects a capture device with a channel. Multiple channels + // can be connected to the same capture device. + virtual int ConnectCaptureDevice(const int captureId, + const int videoChannel) = 0; + + // Disconnects a capture device as input for a specified channel. + virtual int DisconnectCaptureDevice(const int videoChannel) = 0; + + // Makes a capture device start capturing video frames. + virtual int StartCapture(const int captureId, + const CaptureCapability captureCapability = + CaptureCapability()) = 0; + + // Stops a started capture device from capturing video frames. + virtual int StopCapture(const int captureId) = 0; + + // Rotates captured frames before encoding and sending. + // Used on mobile devices with rotates cameras. + virtual int SetRotateCapturedFrames(const int captureId, + const RotateCapturedFrame rotation) = 0; + + // This function sets the expected delay from when a video frame is captured + // to when that frame is delivered to VideoEngine. + virtual int SetCaptureDelay(const int captureId, + const unsigned int captureDelayMs) = 0; + + // Returns the number of sets of capture capabilities the capture device + // supports. + virtual int NumberOfCapabilities(const char* uniqueIdUTF8, + const unsigned int uniqueIdUTF8Length) = 0; + + // Gets a set of capture capabilities for a specified capture device. + virtual int GetCaptureCapability(const char* uniqueIdUTF8, + const unsigned int uniqueIdUTF8Length, + const unsigned int capabilityNumber, + CaptureCapability& capability) = 0; + + // Displays the capture device property dialog box for the specified capture + // device. Windows only. + virtual int ShowCaptureSettingsDialogBox( + const char* uniqueIdUTF8, const unsigned int uniqueIdUTF8Length, + const char* dialogTitle, void* parentWindow = NULL, + const unsigned int x = 200, const unsigned int y = 200) = 0; + + // Gets the clockwise angle the frames from the camera must be rotated in + // order to display the frames correctly if the display is rotated in its + // natural orientation. + virtual int GetOrientation(const char* uniqueIdUTF8, + RotateCapturedFrame &orientation) = 0; + + // Enables brightness alarm detection and the brightness alarm callback. + virtual int EnableBrightnessAlarm(const int captureId, + const bool enable) = 0; + + // Registers an instance of a user implementation of the ViECaptureObserver. + virtual int RegisterObserver(const int captureId, + ViECaptureObserver& observer) = 0; + + // Removes an already registered instance of ViECaptureObserver. + virtual int DeregisterObserver(const int captureId) = 0; + +protected: + ViECapture() {}; + virtual ~ViECapture() {}; +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_CAPTURE_H_ diff --git a/video_engine/main/interface/vie_codec.h b/video_engine/main/interface/vie_codec.h new file mode 100644 index 0000000000..0b2d2aee1f --- /dev/null +++ b/video_engine/main/interface/vie_codec.h @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// This sub-API supports the following functionalities: +// - Setting send and receive codecs. +// - Codec specific settings. +// - Key frame signaling. +// - Stream management settings. + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_CODEC_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_CODEC_H_ + +#include "common_types.h" + +namespace webrtc +{ + +class VideoEngine; +struct VideoCodec; + +// ---------------------------------------------------------------------------- +// ViEEncoderObserver +// ---------------------------------------------------------------------------- + +// This class declares an abstract interface for a user defined observer. It is +// up to the VideoEngine user to implement a derived class which implements the +// observer class. The observer is registered using RegisterEncoderObserver() +// and deregistered using DeregisterEncoderObserver(). +class WEBRTC_DLLEXPORT ViEEncoderObserver +{ +public: + // This method is called once per second with the current encoded frame rate + // and bit rate. + virtual void OutgoingRate(const int videoChannel, + const unsigned int framerate, + const unsigned int bitrate) = 0; +protected: + virtual ~ViEEncoderObserver() {}; +}; + +// ---------------------------------------------------------------------------- +// ViEEncoderObserver +// ---------------------------------------------------------------------------- + +// This class declares an abstract interface for a user defined observer. It is +// up to the VideoEngine user to implement a derived class which implements the +// observer class. The observer is registered using RegisterDecoderObserver() +// and deregistered using DeregisterDecoderObserver(). +class WEBRTC_DLLEXPORT ViEDecoderObserver +{ +public: + // This method is called when a new incoming stream is detected, normally + // triggered by a new incoming SSRC or payload type. + virtual void IncomingCodecChanged(const int videoChannel, + const VideoCodec& videoCodec) = 0; + + // This method is called once per second containing the frame rate and bit + // rate for the incoming stream + virtual void IncomingRate(const int videoChannel, + const unsigned int framerate, + const unsigned int bitrate) = 0; + + // This method is called when the decoder needs a new key frame from encoder + // on the sender. + virtual void RequestNewKeyFrame(const int videoChannel) = 0; + +protected: + virtual ~ViEDecoderObserver() {}; +}; + +// ---------------------------------------------------------------------------- +// ViECodec +// ---------------------------------------------------------------------------- + +class WEBRTC_DLLEXPORT ViECodec +{ +public: + // Factory for the ViECodec sub‐API and increases an internal reference + // counter if successful. Returns NULL if the API is not supported or if + // construction fails. + static ViECodec* GetInterface(VideoEngine* videoEngine); + + // Releases the ViECodec sub-API and decreases an internal reference + // counter. + // Returns the new reference count. This value should be zero + // for all sub-API:s before the VideoEngine object can be safely deleted. + virtual int Release() = 0; + + // Gets the number of available codecs for the VideoEngine build. + virtual int NumberOfCodecs() const = 0; + + // Gets a VideoCodec struct for a codec containing the default configuration + // for that codec type. + virtual int GetCodec(const unsigned char listNumber, + VideoCodec& videoCodec) const = 0; + + // Sets the send codec to use for a specified channel. + virtual int SetSendCodec(const int videoChannel, + const VideoCodec& videoCodec) = 0; + + // Gets the current send codec settings. + virtual int GetSendCodec(const int videoChannel, + VideoCodec& videoCodec) const = 0; + + // Prepares VideoEngine to receive a certain codec type and setting for a + // specified payload type. + virtual int SetReceiveCodec(const int videoChannel, + const VideoCodec& videoCodec) = 0; + + // Gets the current receive codec. + virtual int GetReceiveCodec(const int videoChannel, + VideoCodec& videoCodec) const = 0; + + // This function is used to get codec configuration parameters to be + // signaled from the encoder to the decoder in the call setup. + virtual int GetCodecConfigParameters( + const int videoChannel, + unsigned char configParameters[kConfigParameterSize], + unsigned char& configParametersSize) const = 0; + + // Enables advanced scaling of the captured video stream if the stream + // differs from the send codec settings. + virtual int SetImageScaleStatus(const int videoChannel, + const bool enable) = 0; + + // Gets the number of sent key frames and number of sent delta frames. + virtual int GetSendCodecStastistics(const int videoChannel, + unsigned int& keyFrames, + unsigned int& deltaFrames) const = 0; + + // Gets the number of decoded key frames and number of decoded delta frames. + virtual int GetReceiveCodecStastistics(const int videoChannel, + unsigned int& keyFrames, + unsigned int& deltaFrames) const = 0; + + // Enables key frame request callback in ViEDecoderObserver. + virtual int SetKeyFrameRequestCallbackStatus(const int videoChannel, + const bool enable) = 0; + + // Enables key frame requests for detected lost packets. + virtual int SetSignalKeyPacketLossStatus( + const int videoChannel, const bool enable, + const bool onlyKeyFrames = false) = 0; + + // Registers an instance of a user implementation of the ViEEncoderObserver. + virtual int RegisterEncoderObserver(const int videoChannel, + ViEEncoderObserver& observer) = 0; + + // Removes an already registered instance of ViEEncoderObserver. + virtual int DeregisterEncoderObserver(const int videoChannel) = 0; + + // Registers an instance of a user implementation of the ViEDecoderObserver. + virtual int RegisterDecoderObserver(const int videoChannel, + ViEDecoderObserver& observer) = 0; + + // Removes an already registered instance of ViEDecoderObserver. + virtual int DeregisterDecoderObserver(const int videoChannel) = 0; + + // This function forces the next encoded frame to be a key frame. This is + // normally used when the remote endpoint only supports out‐band key frame + // request. + virtual int SendKeyFrame(const int videoChannel) = 0; + + // This function makes the decoder wait for a key frame before starting to + // decode the incoming video stream. + virtual int WaitForFirstKeyFrame(const int videoChannel, + const bool wait) = 0; + + // This function makes VideoEngine decode all incoming H.263 key frames as + // delta frames and all incoming delta frames as key frames. + virtual int SetInverseH263Logic(int videoChannel, bool enable) = 0; + +protected: + ViECodec() {}; + virtual ~ViECodec() {}; +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_CODEC_H_ diff --git a/video_engine/main/interface/vie_encryption.h b/video_engine/main/interface/vie_encryption.h new file mode 100644 index 0000000000..2be59e3b2c --- /dev/null +++ b/video_engine/main/interface/vie_encryption.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// This sub-API supports the following functionalities: +// - Secure RTP (SRTP). +// - External encryption and decryption. + + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_ENCRYPTION_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_ENCRYPTION_H_ + +#include "common_types.h" + +namespace webrtc +{ +class VideoEngine; + +// ---------------------------------------------------------------------------- +// ViEEncryption +// ---------------------------------------------------------------------------- + +class WEBRTC_DLLEXPORT ViEEncryption +{ +public: + enum + { + kMaxSrtpKeyLength = 30 + }; + + // Factory for the ViEEncryption sub‐API and increases an internal reference + // counter if successful. Returns NULL if the API is not supported or if + // construction fails. + static ViEEncryption* GetInterface(VideoEngine* videoEngine); + + // Releases the ViEEncryption sub-API and decreases an internal reference + // counter. + // Returns the new reference count. This value should be zero + // for all sub-API:s before the VideoEngine object can be safely deleted. + virtual int Release() = 0; + + // This function enables SRTP on send packets for a specific channel. + virtual int EnableSRTPSend(const int videoChannel, + const CipherTypes cipherType, + const unsigned int cipherKeyLength, + const AuthenticationTypes authType, + const unsigned int authKeyLength, + const unsigned int authTagLength, + const SecurityLevels level, + const unsigned char key[kMaxSrtpKeyLength], + const bool useForRTCP = false) = 0; + + // This function disables SRTP for the specified channel. + virtual int DisableSRTPSend(const int videoChannel) = 0; + + // This function enables SRTP on the received packets for a specific + // channel. + virtual int EnableSRTPReceive(const int videoChannel, + const CipherTypes cipherType, + const unsigned int cipherKeyLength, + const AuthenticationTypes authType, + const unsigned int authKeyLength, + const unsigned int authTagLength, + const SecurityLevels level, + const unsigned char key[kMaxSrtpKeyLength], + const bool useForRTCP = false) = 0; + + // This function disables SRTP on received packets for a specific channel. + virtual int DisableSRTPReceive(const int videoChannel) = 0; + + // This function registers a encryption derived instance and enables + // external encryption for the specified channel. + virtual int RegisterExternalEncryption(const int videoChannel, + Encryption& encryption) = 0; + + // This function deregisters a registered encryption derived instance + // and disables external encryption. + virtual int DeregisterExternalEncryption(const int videoChannel) = 0; + +protected: + ViEEncryption() {}; + virtual ~ViEEncryption() {}; +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_ENCRYPTION_H_ diff --git a/video_engine/main/interface/vie_errors.h b/video_engine/main/interface/vie_errors.h new file mode 100644 index 0000000000..78ae5520b9 --- /dev/null +++ b/video_engine/main/interface/vie_errors.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_ERRORS_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_ERRORS_H_ + + +enum ViEErrors { + + //ViEBase + kViENotInitialized = 12000, // Init has not been called successfully. + kViEBaseVoEFailure, // SetVoiceEngine. ViE failed to use VE instance. Check VE instance pointer.ConnectAudioChannel failed to set voice channel. Have SetVoiceEngine been called? Is the voice channel correct. + kViEBaseChannelCreationFailed, // CreateChannel. + kViEBaseInvalidChannelId, // The channel does not exist. + kViEAPIDoesNotExist, // Release called on Interface that has not been created. + kViEBaseInvalidArgument, + kViEBaseAlreadySending, // StartSend called on channel that is already sending. + kViEBaseNotSending, // StopSend called on channel that is not sending. + kViEBaseAlreadyReceiving, // StartReceive called on channel that is already receiving. + kViEBaseObserverAlreadyRegistered, // RegisterObserver- an observer has already been set. + kViEBaseObserverNotRegistered, // DeregisterObserver - no observer has been registered. + kViEBaseUnknownError, // An unknown error has occurred. Check the log file. + + //ViECodec + kViECodecInvalidArgument = 12100, // Wrong input parameter to function. + kViECodecObserverAlreadyRegistered, // RegisterEncoderObserver, RegisterDecoderObserver. + kViECodecObserverNotRegistered, // DeregisterEncoderObserver, DeregisterDecoderObserver. + kViECodecInvalidCodec, // SetSendCodec,SetReceiveCodec- The codec structure is invalid. + kViECodecInvalidChannelId, // The channel does not exist. + kViECodecInUse, // SetSendCodec- Can't change codec size or type when multiple channels use the same encoder. + kViECodecUnknownError, // An unknown error has occurred. Check the log file. + + //ViERender + kViERenderInvalidRenderId = 12200, // No renderer with the ID exist. In AddRenderer - The render ID is invalid. No capture device, channel or file is allocated with that id. + kViERenderAlreadyExists, // AddRenderer: the renderer already exist. + kViERenderInvalidFrameFormat, // AddRender (external renderer). The user has requested a frame format that we don't support. + kViERenderUnknownError, // An unknown error has occurred. Check the log file. + + //ViECapture + kViECaptureDeviceAlreadyConnected = 12300, // ConnectCaptureDevice - A capture device has already been connected to this video channel. + kViECaptureDeviceDoesnNotExist, // No capture device exist with the provided capture id or unique name. + kViECaptureDeviceInvalidChannelId, // ConnectCaptureDevice, DisconnectCaptureDevice- No Channel exist with the provided channel id. + kViECaptureDeviceNotConnected, // DisconnectCaptureDevice- No capture device is connected to the channel. + kViECaptureDeviceNotStarted, // Stop- The capture device is not started. + kViECaptureDeviceAlreadyStarted, // Start- The capture device is already started. + kViECaptureDeviceAlreadyAllocated, // AllocateCaptureDevice The device is already allocated. + kViECaptureDeviceMaxNoDevicesAllocated, // AllocateCaptureDevice Max number of devices already allocated. + kViECaptureObserverAlreadyRegistered, // RegisterObserver- An observer is already registered. Need to deregister first. + kViECaptureDeviceObserverNotRegistered, // DeregisterObserver- No observer is registered. + kViECaptureDeviceUnknownError, // An unknown error has occurred. Check the log file. + kViECaptureDeviceMacQtkitNotSupported, // QTKit handles the capture devices automatically. Thus querying capture capabilities is not supported. + + //ViEFile + kViEFileInvalidChannelId = 12400, // No Channel exist with the provided channel id. + kViEFileInvalidArgument, // Incorrect input argument + kViEFileAlreadyRecording, // StartRecordOutgoingVideo - already recording channel + kViEFileVoENotSet, // StartRecordOutgoingVideo. Failed to access voice engine. Has SetVoiceEngine been called? + kViEFileNotRecording, // StopRecordOutgoingVideo + kViEFileMaxNoOfFilesOpened, // StartPlayFile + kViEFileNotPlaying, // StopPlayFile. The file with the provided id is not playing. + kViEFileObserverAlreadyRegistered, // RegisterObserver + kViEFileObserverNotRegistered, // DeregisterObserver + kViEFileInputAlreadyConnected, // SendFileOnChannel- the video channel already have a connected input. + kViEFileNotConnected, // StopSendFileOnChannel- No file is being sent on the channel. + kViEFileVoEFailure, // SendFileOnChannel,StartPlayAudioLocally - failed to play audio stream + kViEFileInvalidRenderId, // SetRenderTimeoutImage and SetRenderStartImage: Renderer with the provided render id does not exist. + kViEFileInvalidFile, // Can't open the file with provided filename. Is the path and file format correct? + kViEFileInvalidCapture, // Can't use ViEPicture. Is the object correct? + kViEFileSetRenderTimeoutError, // SetRenderTimeoutImage- Please see log file. + kViEFileInvalidCaptureId, // SetCaptureDeviceImage capture id does not exist. + kViEFileSetCaptureImageError, // SetCaptureDeviceImage error. Please see log file. + kViEFileSetStartImageError, // SetRenderStartImage error. Please see log file. + kViEFileUnknownError, // An unknown error has occurred. Check the log file. + + //ViENetwork + kViENetworkInvalidChannelId = 12500, // No Channel exist with the provided channel id. + kViENetworkAlreadyReceiving, // SetLocalReceiver: Can not change ports while receiving. + kViENetworkLocalReceiverNotSet, // GetLocalReceiver: SetLocalReceiver not called. + kViENetworkAlreadySending, // SetSendDestination + kViENetworkDestinationNotSet, // GetSendDestination + kViENetworkInvalidArgument, // GetLocalIP- Check function arguments. + kViENetworkSendCodecNotSet, // SetSendGQoS- Need to set the send codec first. + kViENetworkServiceTypeNotSupported, // SetSendGQoS + kViENetworkNotSupported, // SetSendGQoS Not supported on this OS. + kViENetworkObserverAlreadyRegistered, // RegisterObserver + kViENetworkObserverNotRegistered, // SetPeriodicDeadOrAliveStatus - Need to call RegisterObserver first, DeregisterObserver if no observer is registered. + kViENetworkUnknownError, // An unknown error has occurred. Check the log file. + + //ViERTP_RTCP + kViERtpRtcpInvalidChannelId = 12600, // No Channel exist with the provided channel id. + kViERtpRtcpAlreadySending, // The channel is already sending. Need to stop send before calling this API. + kViERtpRtcpNotSending, // The channel needs to be sending in order for this function to work. + kViERtpRtcpRtcpDisabled, // Functions failed because RTCP is disabled. + kViERtpRtcpObserverAlreadyRegistered, // An observer is already registered. Need to deregister the old first. + kViERtpRtcpObserverNotRegistered, // No observer registered. + kViERtpRtcpUnknownError, // An unknown error has occurred. Check the log file. + + //ViEEncryption + kViEEncryptionInvalidChannelId = 12700, // Channel id does not exist. + kViEEncryptionInvalidSrtpParameter, // EnableSRTPSend, EnableSRTPReceive- Check the SRTP parameters. + kViEEncryptionSrtpNotSupported, // This build does not support SRTP. + kViEEncryptionUnknownError, // An unknown error has occurred. Check the log file. + + //ViEImageProcess + kViEImageProcessInvalidChannelId = 12800, // No Channel exist with the provided channel id. + kViEImageProcessInvalidCaptureId, // No capture device exist with the provided capture id. + kViEImageProcessFilterExists, // RegisterCaptureEffectFilter,RegisterSendEffectFilter,RegisterRenderEffectFilter - Effect filter already registered. + kViEImageProcessFilterDoesNotExist, // DeRegisterCaptureEffectFilter,DeRegisterSendEffectFilter,DeRegisterRenderEffectFilter - Effect filter not registered. + kViEImageProcessAlreadyEnabled, // EnableDeflickering,EnableDenoising,EnableColorEnhancement- Function already enabled. + kViEImageProcessAlreadyDisabled, // EnableDeflickering,EnableDenoising,EnableColorEnhancement- Function already disabled. + kViEImageProcessUnknownError // An unknown error has occurred. Check the log file. +}; + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_ERRORS_H_ diff --git a/video_engine/main/interface/vie_external_codec.h b/video_engine/main/interface/vie_external_codec.h new file mode 100644 index 0000000000..28e2767552 --- /dev/null +++ b/video_engine/main/interface/vie_external_codec.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_EXTERNAL_CODEC_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_EXTERNAL_CODEC_H_ + +#include "common_types.h" + +namespace webrtc +{ +class VideoEngine; +class VideoEncoder; +class VideoDecoder; + +// ---------------------------------------------------------------------------- +// ViEExternalCodec +// ---------------------------------------------------------------------------- + +class WEBRTC_DLLEXPORT ViEExternalCodec +{ +public: + static ViEExternalCodec* GetInterface(VideoEngine* videoEngine); + + virtual int Release() = 0; + + virtual int RegisterExternalSendCodec(const int videoChannel, + const unsigned char plType, + VideoEncoder* encoder) = 0; + + virtual int DeRegisterExternalSendCodec(const int videoChannel, + const unsigned char plType) = 0; + + virtual int RegisterExternalReceiveCodec(const int videoChannel, + const unsigned int plType, + VideoDecoder* decoder, + bool decoderRender = false, + int renderDelay = 0) = 0; + + virtual int DeRegisterExternalReceiveCodec(const int videoChannel, + const unsigned char plType) = 0; + +protected: + ViEExternalCodec() {}; + virtual ~ViEExternalCodec() {}; +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_EXTERNAL_CODEC_H_ diff --git a/video_engine/main/interface/vie_file.h b/video_engine/main/interface/vie_file.h new file mode 100644 index 0000000000..141254b559 --- /dev/null +++ b/video_engine/main/interface/vie_file.h @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// This sub-API supports the following functionalities: +// - File recording and playing. +// - Snapshots. +// - Background images. + + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_FILE_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_FILE_H_ + +#include "common_types.h" + +namespace webrtc +{ +class VideoEngine; +struct VideoCodec; + +// This structure contains picture data and describes the picture type. +struct ViEPicture +{ + unsigned char* data; + unsigned int size; + unsigned int width; + unsigned int height; + RawVideoType type; + + ViEPicture() + { + data = NULL; + size = 0; + width = 0; + height = 0; + type = kVideoI420; + } + + //call FreePicture to free data + ~ViEPicture() + { + data = NULL; + size = 0; + width = 0; + height = 0; + type = kVideoUnknown; + } +}; + +// This enumerator tells which audio source to use for media files. +enum AudioSource +{ + NO_AUDIO, + MICROPHONE, + PLAYOUT, + VOICECALL +}; + +// This class declares an abstract interface for a user defined observer. It is +// up to the VideoEngine user to implement a derived class which implements the +// observer class. The observer is registered using RegisterObserver() and +// deregistered using DeregisterObserver(). +class WEBRTC_DLLEXPORT ViEFileObserver +{ +public: + // This method is called when the end is reached of a played file. + virtual void PlayFileEnded(const WebRtc_Word32 fileId) = 0; + +protected: + virtual ~ViEFileObserver() {}; +}; + +// ---------------------------------------------------------------------------- +// ViEFile +// ---------------------------------------------------------------------------- + +class WEBRTC_DLLEXPORT ViEFile +{ +public: + // Factory for the ViEFile sub‐API and increases an internal reference + // counter if successful. Returns NULL if the API is not supported or if + // construction fails. + static ViEFile* GetInterface(VideoEngine* videoEngine); + + // Releases the ViEFile sub-API and decreases an internal reference counter. + // Returns the new reference count. This value should be zero + // for all sub-API:s before the VideoEngine object can be safely deleted. + virtual int Release() = 0; + + // Starts playing a video file. + virtual int StartPlayFile(const char* fileNameUTF8, int& fileId, + const bool loop = false, + const FileFormats fileFormat = + kFileFormatAviFile) = 0; + + // Stops a file from being played. + virtual int StopPlayFile(const int fileId) = 0; + + // Registers an instance of a user implementation of the ViEFileObserver. + virtual int RegisterObserver(int fileId, ViEFileObserver& observer) = 0; + + // Removes an already registered instance of ViEFileObserver. + virtual int DeregisterObserver(int fileId, ViEFileObserver& observer) = 0; + + // This function tells which channel, if any, the file should be sent on. + virtual int SendFileOnChannel(const int fileId, const int videoChannel) = 0; + + // Stops a file from being sent on a a channel. + virtual int StopSendFileOnChannel(const int videoChannel) = 0; + + // Starts playing the file audio as microphone input for the specified voice + // channel. + virtual int StartPlayFileAsMicrophone(const int fileId, + const int audioChannel, + bool mixMicrophone = false, + float volumeScaling = 1) = 0; + + // The function stop the audio from being played on a VoiceEngine channel. + virtual int StopPlayFileAsMicrophone(const int fileId, + const int audioChannel) = 0; + + // The function plays and mixes the file audio with the local speaker signal + // for playout. + virtual int StartPlayAudioLocally(const int fileId, const int audioChannel, + float volumeScaling = 1) = 0; + + // Stops the audio from a file from being played locally. + virtual int StopPlayAudioLocally(const int fileId, + const int audioChannel) = 0; + + // This function starts recording the video transmitted to another endpoint. + virtual int StartRecordOutgoingVideo(const int videoChannel, + const char* fileNameUTF8, + AudioSource audioSource, + const CodecInst& audioCodec, + const VideoCodec& videoCodec, + const FileFormats fileFormat = + kFileFormatAviFile) =0; + + // This function starts recording the incoming video stream on a channel. + virtual int StartRecordIncomingVideo(const int videoChannel, + const char* fileNameUTF8, + AudioSource audioSource, + const CodecInst& audioCodec, + const VideoCodec& videoCodec, + const FileFormats fileFormat = + kFileFormatAviFile) = 0; + + // Stops the file recording of the outgoing stream. + virtual int StopRecordOutgoingVideo(const int videoChannel) = 0; + + // Stops the file recording of the incoming stream. + virtual int StopRecordIncomingVideo(const int videoChannel) = 0; + + // Gets the audio codec, video codec and file format of a recorded file. + virtual int GetFileInformation(const char* fileName, + VideoCodec& videoCodec, + CodecInst& audioCodec, + const FileFormats fileFormat = + kFileFormatAviFile) = 0; + + // The function takes a snapshot of the last rendered image for a video + // channel. + virtual int GetRenderSnapshot(const int videoChannel, + const char* fileNameUTF8) = 0; + + // The function takes a snapshot of the last rendered image for a video + // channel + virtual int GetRenderSnapshot(const int videoChannel, + ViEPicture& picture) = 0; + + // The function takes a snapshot of the last captured image by a specified + // capture device. + virtual int GetCaptureDeviceSnapshot(const int captureId, + const char* fileNameUTF8) = 0; + + // The function takes a snapshot of the last captured image by a specified + // capture device. + virtual int GetCaptureDeviceSnapshot(const int captureId, + ViEPicture& picture) = 0; + + // This function sets a jpg image to show before the first frame is captured + // by the capture device. This frame will be encoded and transmitted to a + // possible receiver + virtual int SetCaptureDeviceImage(const int captureId, + const char* fileNameUTF8) = 0; + + // This function sets an image to show before the first frame is captured by + // the capture device. This frame will be encoded and transmitted to a + // possible receiver + virtual int SetCaptureDeviceImage(const int captureId, + const ViEPicture& picture) = 0; + + virtual int FreePicture(ViEPicture& picture) = 0; + + // This function sets a jpg image to render before the first received video + // frame is decoded for a specified channel. + virtual int SetRenderStartImage(const int videoChannel, + const char* fileNameUTF8) = 0; + + // This function sets an image to render before the first received video + // frame is decoded for a specified channel. + virtual int SetRenderStartImage(const int videoChannel, + const ViEPicture& picture) = 0; + + // This function sets a jpg image to render if no frame is decoded for a + // specified time interval. + virtual int SetRenderTimeoutImage(const int videoChannel, + const char* fileNameUTF8, + const unsigned int timeoutMs = 1000) = 0; + + // This function sets an image to render if no frame is decoded for a + // specified time interval. + virtual int SetRenderTimeoutImage(const int videoChannel, + const ViEPicture& picture, + const unsigned int timeoutMs) = 0; + +protected: + ViEFile() {}; + virtual ~ViEFile() {}; +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_FILE_H_ diff --git a/video_engine/main/interface/vie_image_process.h b/video_engine/main/interface/vie_image_process.h new file mode 100644 index 0000000000..c1b79deca4 --- /dev/null +++ b/video_engine/main/interface/vie_image_process.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// This sub-API supports the following functionalities: +// - Effect filters +// - Deflickering +// - Denoising +// - Color enhancement + + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_IMAGE_PROCESS_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_IMAGE_PROCESS_H_ + +#include "common_types.h" + +namespace webrtc +{ +class VideoEngine; + +// ---------------------------------------------------------------------------- +// ViEEffectFilter +// ---------------------------------------------------------------------------- + +// This class declares an abstract interface for a user defined effect filter. +// The effect filter is registered using RegisterCaptureEffectFilter(), +// RegisterSendEffectFilter() or RegisterRenderEffectFilter() and deregistered +// with the corresponding deregister function. +class WEBRTC_DLLEXPORT ViEEffectFilter +{ +public: + // This method is called with an I420 video frame allowing the user to + // modify the video frame. + virtual int Transform(int size, unsigned char* frameBuffer, + unsigned int timeStamp90KHz, unsigned int width, + unsigned int height) = 0; +protected: + ViEEffectFilter() {} + virtual ~ViEEffectFilter(){} +}; + +// ---------------------------------------------------------------------------- +// ViEImageProcess +// ---------------------------------------------------------------------------- + +class WEBRTC_DLLEXPORT ViEImageProcess +{ +public: + // Factory for the ViEImageProcess sub‐API and increases an internal + // reference counter if successful. Returns NULL if the API is not supported + // or if construction fails. + static ViEImageProcess* GetInterface(VideoEngine* videoEngine); + + // Releases the ViEImageProcess sub-API and decreases an internal reference + // counter. Returns the new reference count. This value should be zero + // for all sub-API:s before the VideoEngine object can be safely deleted. + virtual int Release() = 0; + + // This function registers a EffectFilter to use for a specified capture + // device. + virtual int RegisterCaptureEffectFilter(const int captureId, + ViEEffectFilter& captureFilter) = 0; + + // This function deregisters a EffectFilter for a specified capture device. + virtual int DeregisterCaptureEffectFilter(const int captureId) = 0; + + // This function registers an EffectFilter to use for a specified channel. + virtual int RegisterSendEffectFilter(const int videoChannel, + ViEEffectFilter& sendFilter) = 0; + + // This function deregisters a send effect filter for a specified channel. + virtual int DeregisterSendEffectFilter(const int videoChannel) = 0; + + // This function registers a EffectFilter to use for the rendered video + // stream on an incoming channel. + virtual int RegisterRenderEffectFilter(const int videoChannel, + ViEEffectFilter& renderFilter) = 0; + + // This function deregisters a render effect filter for a specified channel. + virtual int DeregisterRenderEffectFilter(const int videoChannel) = 0; + + // All cameras run the risk of getting in almost perfect sync with + // florescent lamps, which will result in a very annoying flickering of the + // image. Most cameras have some type of filter to protect against this but + // not all of them succeed. Enabling this function will remove the flicker. + virtual int EnableDeflickering(const int captureId, const bool enable) = 0; + + // Some cameras produce very noisy captured images, especially in low‐light + // conditions. This functionality will reduce the camera noise. + virtual int EnableDenoising(const int captureId, const bool enable) = 0; + + // This function enhances the colors on the decoded video stream, enabled by + // default. + virtual int EnableColorEnhancement(const int videoChannel, + const bool enable) = 0; + +protected: + ViEImageProcess() {}; + virtual ~ViEImageProcess() {}; +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_IMAGE_PROCESS_H_ diff --git a/video_engine/main/interface/vie_network.h b/video_engine/main/interface/vie_network.h new file mode 100644 index 0000000000..aa97053e5c --- /dev/null +++ b/video_engine/main/interface/vie_network.h @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_NETWORK_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_NETWORK_H_ + +// This sub-API supports the following functionalities: +// - Configuring send and receive addresses. +// - External transport support. +// - Port and address filters. +// - Windows GQoS functions and ToS functions. +// - Packet timeout notification. +// - Dead‐or‐Alive connection observations. + + +#include "common_types.h" + +namespace webrtc +{ +class VideoEngine; +class Transport; + +// ---------------------------------------------------------------------------- +// ViENetworkObserver +// ---------------------------------------------------------------------------- + +// This enumerator describes VideoEngine packet timeout states. +enum ViEPacketTimeout +{ + NoPacket = 0, + PacketReceived = 1 +}; + +// This class declares an abstract interface for a user defined observer. It is +// up to the VideoEngine user to implement a derived class which implements the +// observer class. The observer is registered using RegisterObserver() and +// deregistered using DeregisterObserver(). +class WEBRTC_DLLEXPORT ViENetworkObserver +{ +public: + // This method will be called periodically delivering a dead‐or‐alive + // decision for a specified channel. + virtual void OnPeriodicDeadOrAlive(const int videoChannel, + const bool alive) = 0; + + // This method is called once if a packet timeout occurred. + virtual void PacketTimeout(const int videoChannel, + const ViEPacketTimeout timeout) = 0; +protected: + virtual ~ViENetworkObserver() {}; +}; + +// ---------------------------------------------------------------------------- +// ViENetwork +// ---------------------------------------------------------------------------- + +class WEBRTC_DLLEXPORT ViENetwork +{ +public: + // Default values + enum + { + KDefaultSampleTimeSeconds = 2 + }; + + // Factory for the ViENetwork sub‐API and increases an internal reference + // counter if successful. Returns NULL if the API is not supported or if + // construction fails. + static ViENetwork* GetInterface(VideoEngine* videoEngine); + + // Releases the ViENetwork sub-API and decreases an internal reference + // counter.Returns the new reference count. This value should be zero + // for all sub-API:s before the VideoEngine object can be safely deleted. + virtual int Release() = 0; + + // Specifies the ports to receive RTP packets on. It is also possible to set + // port for RTCP and local IP address. + virtual int SetLocalReceiver(const int videoChannel, + const unsigned short rtpPort, + const unsigned short rtcpPort = 0, + const char* ipAddress = NULL) = 0; + + // Gets the local receiver ports and address for a specified channel. + virtual int GetLocalReceiver(const int videoChannel, + unsigned short& rtpPort, + unsigned short& rtcpPort, char* ipAddress) = 0; + + // Specifies the destination port and IP address for a specified channel. + virtual int SetSendDestination(const int videoChannel, + const char* ipAddress, + const unsigned short rtpPort, + const unsigned short rtcpPort = 0, + const unsigned short sourceRtpPort = 0, + const unsigned short sourceRtcpPort = 0) = 0; + + // Get the destination port and address for a specified channel. + virtual int GetSendDestination(const int videoChannel, char* ipAddress, + unsigned short& rtpPort, + unsigned short& rtcpPort, + unsigned short& sourceRtpPort, + unsigned short& sourceRtcpPort) = 0; + + // This function registers a user implementation of Transport to use for + // sending RTP and RTCP packets on this channel. + virtual int RegisterSendTransport(const int videoChannel, + Transport& transport) = 0; + + // This function deregisters a used Transport for a specified channel. + virtual int DeregisterSendTransport(const int videoChannel) = 0; + + // When using external transport for a channel, received RTP packets should + // be passed to VideoEngine using this function. The input should contain + // the RTP header and payload. + virtual int ReceivedRTPPacket(const int videoChannel, const void* data, + const int length) = 0; + + // When using external transport for a channel, received RTCP packets should + // be passed to VideoEngine using this function. + virtual int ReceivedRTCPPacket(const int videoChannel, const void* data, + const int length) = 0; + + // Gets the source ports and IP address of the incoming stream for a + // specified channel. + virtual int GetSourceInfo(const int videoChannel, unsigned short& rtpPort, + unsigned short& rtcpPort, char* ipAddress, + unsigned int ipAddressLength) = 0; + + // Gets the local IP address, in string format. + virtual int GetLocalIP(char ipAddress[64], bool ipv6 = false) = 0; + + // Enables IPv6, instead of IPv4, for a specified channel. + virtual int EnableIPv6(int videoChannel) = 0; + + // The function returns true if IPv6 is enabled, false otherwise. + virtual bool IsIPv6Enabled(int videoChannel) = 0; + + // Enables a port and IP address filtering for incoming packets on a + // specific channel. + virtual int SetSourceFilter(const int videoChannel, + const unsigned short rtpPort, + const unsigned short rtcpPort = 0, + const char* ipAddress = NULL) = 0; + + // Gets current port and IP address filter for a specified channel. + virtual int GetSourceFilter(const int videoChannel, unsigned short& rtpPort, + unsigned short& rtcpPort, char* ipAddress) = 0; + + // This function sets the six‐bit Differentiated Services Code Point (DSCP) + // in the IP header of the outgoing stream for a specific channel. + // Windows and Linux only. + virtual int SetSendToS(const int videoChannel, const int DSCP, + const bool useSetSockOpt = false) = 0; + + // Retrieves the six‐bit Differentiated Services Code Point (DSCP) in the IP + // header of the outgoing stream for a specific channel. + virtual int GetSendToS(const int videoChannel, int& DSCP, + bool& useSetSockOpt) = 0; + + // This function sets the Generic Quality of Service (GQoS) service level. + // The Windows operating system then maps to a Differentiated Services Code + // Point (DSCP) and to an 802.1p setting. Windows only. + virtual int SetSendGQoS(const int videoChannel, const bool enable, + const int serviceType, + const int overrideDSCP = 0) = 0; + + // This function retrieves the currently set GQoS service level for a + // specific channel. + virtual int GetSendGQoS(const int videoChannel, bool& enabled, + int& serviceType, int& overrideDSCP) = 0; + + // This function sets the Maximum Transition Unit (MTU) for a channel. The + // RTP packet will be packetized based on this MTU to optimize performance + // over the network. + virtual int SetMTU(int videoChannel, unsigned int mtu) = 0; + + // This function enables or disables warning reports if packets have not + // been received for a specified time interval. + virtual int SetPacketTimeoutNotification(const int videoChannel, + bool enable, + int timeoutSeconds) = 0; + + // Registers an instance of a user implementation of the ViENetwork + // observer. + virtual int RegisterObserver(const int videoChannel, + ViENetworkObserver& observer) = 0; + + // Removes a registered instance of ViENetworkObserver. + virtual int DeregisterObserver(const int videoChannel) = 0; + + // This function enables or disables the periodic dead‐or‐alive callback + // functionality for a specified channel. + virtual int SetPeriodicDeadOrAliveStatus( + const int videoChannel, const bool enable, + const unsigned int sampleTimeSeconds = KDefaultSampleTimeSeconds) = 0; + + // This function handles sending a raw UDP data packet over an existing RTP + // or RTCP socket. + virtual int SendUDPPacket(const int videoChannel, const void* data, + const unsigned int length, int& transmittedBytes, + bool useRtcpSocket = false) = 0; + +protected: + ViENetwork() {}; + virtual ~ViENetwork() {}; +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_NETWORK_H_ diff --git a/video_engine/main/interface/vie_render.h b/video_engine/main/interface/vie_render.h new file mode 100644 index 0000000000..ef2631dafa --- /dev/null +++ b/video_engine/main/interface/vie_render.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// This sub-API supports the following functionalities: +// - Specify render destinations for incoming video streams, capture devices +// and files. +// - Configuring render streams. + + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_RENDER_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_RENDER_H_ + +#include "common_types.h" + +namespace webrtc +{ + +class VideoRender; +class VideoEngine; + +// ---------------------------------------------------------------------------- +// ExternalRenderer +// ---------------------------------------------------------------------------- + +// This class declares an abstract interface to be used for external renderers. +// The user implemented derived class is registered using AddRenderer(). +class WEBRTC_DLLEXPORT ExternalRenderer +{ +public: + // This method will be called when the stream to be rendered changes in + // resolution or number of streams mixed in the image. + virtual int FrameSizeChange(unsigned int width, unsigned int height, + unsigned int numberOfStreams) = 0; + + // This method is called when a new frame should be rendered. + virtual int DeliverFrame(unsigned char* buffer, int bufferSize) = 0; + +protected: + virtual ~ExternalRenderer() {} +}; + +// ---------------------------------------------------------------------------- +// ViERender +// ---------------------------------------------------------------------------- + +class WEBRTC_DLLEXPORT ViERender +{ +public: + // Factory for the ViERender sub‐API and increases an internal reference + // counter if successful. Returns NULL if the API is not supported or if + // construction fails. + static ViERender* GetInterface(VideoEngine* videoEngine); + + // Releases the ViERender sub-API and decreases an internal reference + // counter. Returns the new reference count. This value should be zero + // for all sub-API:s before the VideoEngine object can be safely deleted. + virtual int Release() = 0; + + // Registers render module + virtual int RegisterVideoRenderModule(VideoRender& renderModule) = 0; + + // Deegisters render module + virtual int DeRegisterVideoRenderModule(VideoRender& renderModule) = 0; + + // Sets the render destination for a given render ID. + virtual int AddRenderer(const int renderId, void* window, + const unsigned int zOrder, const float left, + const float top, const float right, + const float bottom) = 0; + + // Removes the renderer for a stream + virtual int RemoveRenderer(const int renderId) = 0; + + // Starts rendering a render stream. + virtual int StartRender(const int renderId) = 0; + + // Stops rendering a render stream. + virtual int StopRender(const int renderId) = 0; + + // Configures an already added render stream. + virtual int ConfigureRender(int renderId, const unsigned int zOrder, + const float left, const float top, + const float right, const float bottom) = 0; + + // This function mirrors the rendered stream left and right or up and down. + virtual int MirrorRenderStream(const int renderId, const bool enable, + const bool mirrorXAxis, + const bool mirrorYAxis) = 0; + + // External render + virtual int AddRenderer(const int renderId, RawVideoType videoInputFormat, + ExternalRenderer* renderer) = 0; + +protected: + ViERender() {}; + virtual ~ViERender() {}; +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_RENDER_H_ diff --git a/video_engine/main/interface/vie_rtp_rtcp.h b/video_engine/main/interface/vie_rtp_rtcp.h new file mode 100644 index 0000000000..70bf3b4809 --- /dev/null +++ b/video_engine/main/interface/vie_rtp_rtcp.h @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// This sub-API supports the following functionalities: +// - Callbacks for RTP and RTCP events such as modified SSRC or CSRC. +// - SSRC handling. +// - Transmission of RTCP reports. +// - Obtaining RTCP data from incoming RTCP sender reports. +// - RTP and RTCP statistics (jitter, packet loss, RTT etc.). +// - Forward Error Correction (FEC). +// - RTP Keep‐alive for maintaining the NAT mappings associated to RTP flows. +// - Writing RTP and RTCP packets to binary files for off‐line analysis of the +// call quality. +// - Inserting extra RTP packets into active audio stream. + + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_RTP_RTCP_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_RTP_RTCP_H_ + +#include "common_types.h" + +namespace webrtc +{ +class VideoEngine; + +// This enumerator sets the RTCP mode. +enum ViERTCPMode +{ + kRtcpNone = 0, + kRtcpCompound_RFC4585 = 1, + kRtcpNonCompound_RFC5506 = 2 +}; + +// This enumerator describes the key frame request mode. +enum ViEKeyFrameRequestMethod +{ + kViEKeyFrameRequestNone = 0, + kViEKeyFrameRequestPliRtcp = 1, + kViEKeyFrameRequestFirRtp = 2, + kViEKeyFrameRequestFirRtcp = 3 +}; + +// ---------------------------------------------------------------------------- +// ViERTPObserver +// ---------------------------------------------------------------------------- + +// This class declares an abstract interface for a user defined observer. It is +// up to the VideoEngine user to implement a derived class which implements the +// observer class. The observer is registered using RegisterRTPObserver() and +// deregistered using DeregisterRTPObserver(). +class WEBRTC_DLLEXPORT ViERTPObserver +{ +public: + // This method is called if SSRC of the incoming stream is changed. + virtual void IncomingSSRCChanged(const int videoChannel, + const unsigned int SSRC) = 0; + + // This method is called if a field in CSRC changes or if the number of + // CSRCs changes. + virtual void IncomingCSRCChanged(const int videoChannel, + const unsigned int CSRC, + const bool added) = 0; +protected: + virtual ~ViERTPObserver() {} +}; + +// ---------------------------------------------------------------------------- +// ViERTCPObserver +// ---------------------------------------------------------------------------- + +// This class declares an abstract interface for a user defined observer. It is +// up to the VideoEngine user to implement a derived class which implements the +// observer class. The observer is registered using RegisterRTCPObserver() and +// deregistered using DeregisterRTCPObserver(). + +class WEBRTC_DLLEXPORT ViERTCPObserver +{ +public: + // This method is called if a application-defined RTCP packet has been + // received. + virtual void OnApplicationDataReceived( + const int videoChannel, const unsigned char subType, + const unsigned int name, const char* data, + const unsigned short dataLengthInBytes) = 0; +protected: + virtual ~ViERTCPObserver() {} +}; + +// +class WEBRTC_DLLEXPORT ViERTP_RTCP +{ +public: + // Default values + enum + { + KDefaultDeltaTransmitTimeSeconds = 15 + }; + enum + { + KMaxRTCPCNameLength = 256 + }; + + // Factory for the ViERTP_RTCP sub‐API and increases an internal reference + // counter if successful. Returns NULL if the API is not supported or if + // construction fails. + static ViERTP_RTCP* GetInterface(VideoEngine* videoEngine); + + // Releases the ViERTP_RTCP sub-API and decreases an internal reference + // counter. Returns the new reference count. This value should be zero + // for all sub-API:s before the VideoEngine object can be safely deleted. + virtual int Release() = 0; + + // This function enables you to specify the RTP synchronization source + // identifier (SSRC) explicitly. + virtual int SetLocalSSRC(const int videoChannel, + const unsigned int SSRC) = 0; + + // This function gets the SSRC for the outgoing RTP stream for the specified + // channel. + virtual int GetLocalSSRC(const int videoChannel, + unsigned int& SSRC) const = 0; + + // This function gets the SSRC for the incoming RTP stream for the specified + // channel. + virtual int GetRemoteSSRC(const int videoChannel, + unsigned int& SSRC) const = 0; + + // This function returns the CSRCs of the incoming RTP packets. + virtual int GetRemoteCSRCs(const int videoChannel, + unsigned int CSRCs[kRtpCsrcSize]) const = 0; + + // This function enables manual initialization of the sequence number. The + // start sequence number is normally a random number. + virtual int SetStartSequenceNumber(const int videoChannel, + unsigned short sequenceNumber) = 0; + + // This function sets the RTCP status for the specified channel. + // Default mode is kRtcpCompound_RFC4585. + virtual int SetRTCPStatus(const int videoChannel, + const ViERTCPMode rtcpMode) = 0; + + // This function gets the RTCP status for the specified channel. + virtual int GetRTCPStatus(const int videoChannel, + ViERTCPMode& rtcpMode) = 0; + + // This function sets the RTCP canonical name (CNAME) for the RTCP reports + // on a specific channel. + virtual int SetRTCPCName(const int videoChannel, + const char rtcpCName[KMaxRTCPCNameLength]) = 0; + + // This function gets the RTCP canonical name (CNAME) for the RTCP reports + // sent the specified channel. + virtual int GetRTCPCName(const int videoChannel, + char rtcpCName[KMaxRTCPCNameLength]) = 0; + + // This function gets the RTCP canonical name (CNAME) for the RTCP reports + // received on the specified channel. + virtual int GetRemoteRTCPCName( + const int videoChannel, char rtcpCName[KMaxRTCPCNameLength]) const = 0; + + // This function sends an RTCP APP packet on a specific channel. + virtual int SendApplicationDefinedRTCPPacket( + const int videoChannel, const unsigned char subType, + unsigned int name, const char* data, + unsigned short dataLengthInBytes) = 0; + + // This function enables Negative Acknowledgement (NACK) using RTCP, + // implemented based on RFC 4585. NACK retransmits RTP packets if lost on + // the network. This creates a lossless transport at the expense of delay. + // If using NACK, NACK should be enabled on both endpoints in a call. + virtual int SetNACKStatus(const int videoChannel, const bool enable) = 0; + + // This function enables Forward Error Correction (FEC) using RTCP, + // implemented based on RFC 5109, to improve packet loss robustness. Extra + // FEC packets are sent together with the usual media packets, hence + // part of the bitrate will be used for FEC packets. + virtual int SetFECStatus(const int videoChannel, const bool enable, + const unsigned char payloadTypeRED, + const unsigned char payloadTypeFEC) = 0; + + // This function enables RTCP key frame requests. + virtual int SetKeyFrameRequestMethod( + const int videoChannel, const ViEKeyFrameRequestMethod method) = 0; + + // This function enables signaling of temporary bitrate constraints using + // RTCP, implemented based on RFC4585. + virtual int SetTMMBRStatus(const int videoChannel, const bool enable) = 0; + + // The function gets statistics from the received RTCP report. + virtual int GetReceivedRTCPStatistics( + const int videoChannel, unsigned short& fractionLost, + unsigned int& cumulativeLost, unsigned int& extendedMax, + unsigned int& jitter, int& rttMs) const = 0; + + // The function gets statistics from the RTCP report sent to the receiver. + virtual int GetSentRTCPStatistics(const int videoChannel, + unsigned short& fractionLost, + unsigned int& cumulativeLost, + unsigned int& extendedMax, + unsigned int& jitter, + int& rttMs) const = 0; + + // The function gets statistics from the sent and received RTP streams. + virtual int GetRTPStatistics(const int videoChannel, + unsigned int& bytesSent, + unsigned int& packetsSent, + unsigned int& bytesReceived, + unsigned int& packetsReceived) const = 0; + + // This function enables or disables an RTP keep-alive mechanism which can + // be used to maintain an existing Network Address Translator (NAT) mapping + // while regular RTP is no longer transmitted. + virtual int SetRTPKeepAliveStatus( + const int videoChannel, bool enable, const char unknownPayloadType, + const unsigned int deltaTransmitTimeSeconds = + KDefaultDeltaTransmitTimeSeconds) = 0; + + // This function gets the RTP keep-alive status. + virtual int GetRTPKeepAliveStatus( + const int videoChannel, bool& enabled, char& unkownPayloadType, + unsigned int& deltaTransmitTimeSeconds) = 0; + + // This function enables capturing of RTP packets to a binary file on a + // specific channel and for a given direction. The file can later be + // replayed using e.g. RTP Tools rtpplay since the binary file format is + // compatible with the rtpdump format. + virtual int StartRTPDump(const int videoChannel, + const char fileNameUTF8[1024], + RTPDirections direction) = 0; + + // This function disables capturing of RTP packets to a binary file on a + // specific channel and for a given direction. + virtual int StopRTPDump(const int videoChannel, + RTPDirections direction) = 0; + + // Registers an instance of a user implementation of the ViERTPObserver. + virtual int RegisterRTPObserver(const int videoChannel, + ViERTPObserver& observer) = 0; + + // Removes a registered instance of ViERTPObserver. + virtual int DeregisterRTPObserver(const int videoChannel) = 0; + + // Registers an instance of a user implementation of the ViERTCPObserver. + virtual int RegisterRTCPObserver(const int videoChannel, + ViERTCPObserver& observer) = 0; + + // Removes a registered instance of ViERTCPObserver. + virtual int DeregisterRTCPObserver(const int videoChannel) = 0; + +protected: + ViERTP_RTCP() {}; + virtual ~ViERTP_RTCP() {}; +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_INTERFACE_VIE_RTP_RTCP_H_ diff --git a/video_engine/main/source/video_engine_core.gyp b/video_engine/main/source/video_engine_core.gyp new file mode 100644 index 0000000000..0f73a2eee1 --- /dev/null +++ b/video_engine/main/source/video_engine_core.gyp @@ -0,0 +1,133 @@ +# Copyright (c) 2009 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'includes': [ + '../../../common_settings.gypi', # Common settings + ], + 'targets': [ + { + 'target_name': 'video_engine_core', + 'type': '<(library)', + 'dependencies': [ + + ## common_video + '../../../common_video/vplib/main/source/vplib.gyp:webrtc_vplib', + '../../../common_video/jpeg/main/source/jpeg.gyp:webrtc_jpeg', + + ## ModulesShared + '../../../modules/media_file/source/media_file.gyp:media_file', + '../../../modules/rtp_rtcp/source/rtp_rtcp.gyp:rtp_rtcp', + '../../../modules/udp_transport/source/udp_transport.gyp:udp_transport', + '../../../modules/utility/source/utility.gyp:webrtc_utility', + + ## ModulesVideo + '../../../modules/video_coding/main/source/video_coding.gyp:webrtc_video_coding', + '../../../modules/video_processing/main/source/video_processing.gyp:video_processing', + '../../../modules/video_render/main/source/video_render.gyp:video_render_module', + + ## VoiceEngine + '../../../voice_engine/main/source/voice_engine_core.gyp:voice_engine_core', + + ## system_wrappers_2005 + '../../../system_wrappers/source/system_wrappers.gyp:system_wrappers', + ], + 'include_dirs': [ + '../interface', + '../../../modules/video_capture/main/interface', + '../../../modules/video_render/main/interface', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '../interface', + ], + }, + 'sources': [ + # interface + '../interface/vie_base.h', + '../interface/vie_capture.h', + '../interface/vie_codec.h', + '../interface/vie_encryption.h', + '../interface/vie_errors.h', + '../interface/vie_external_codec.h', + '../interface/vie_file.h', + '../interface/vie_image_process.h', + '../interface/vie_network.h', + '../interface/vie_render.h', + '../interface/vie_rtp_rtcp.h', + + # headers + 'vie_base_impl.h', + 'vie_capture_impl.h', + 'vie_codec_impl.h', + 'vie_defines.h', + 'vie_encryption_impl.h', + 'vie_external_codec_impl.h', + 'vie_file_impl.h', + 'vie_image_process_impl.h', + 'vie_impl.h', + 'vie_network_impl.h', + 'vie_ref_count.h', + 'vie_render_impl.h', + 'vie_rtp_rtcp_impl.h', + 'vie_shared_data.h', + 'vie_capturer.h', + 'vie_channel.h', + 'vie_channel_manager.h', + 'vie_encoder.h', + 'vie_file_image.h', + 'vie_file_player.h', + 'vie_file_recorder.h', + 'vie_frame_provider_base.h', + 'vie_input_manager.h', + 'vie_manager_base.h', + 'vie_performance_monitor.h', + 'vie_receiver.h', + 'vie_renderer.h', + 'vie_render_manager.h', + 'vie_sender.h', + 'vie_sync_module.h', + + # ViE + 'vie_base_impl.cc', + 'vie_capture_impl.cc', + 'vie_codec_impl.cc', + 'vie_encryption_impl.cc', + 'vie_external_codec_impl.cc', + 'vie_file_impl.cc', + 'vie_image_process_impl.cc', + 'vie_impl.cc', + 'vie_network_impl.cc', + 'vie_ref_count.cc', + 'vie_render_impl.cc', + 'vie_rtp_rtcp_impl.cc', + 'vie_shared_data.cc', + + # ViE + 'vie_capturer.cc', + 'vie_channel.cc', + 'vie_channel_manager.cc', + 'vie_encoder.cc', + 'vie_file_image.cc', + 'vie_file_player.cc', + 'vie_file_recorder.cc', + 'vie_frame_provider_base.cc', + 'vie_input_manager.cc', + 'vie_manager_base.cc', + 'vie_performance_monitor.cc', + 'vie_receiver.cc', + 'vie_renderer.cc', + 'vie_render_manager.cc', + 'vie_sender.cc', + 'vie_sync_module.cc', + ], # source + }, + ], +} + +# Local Variables: +# tab-width:2 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=2 shiftwidth=2: diff --git a/video_engine/main/source/vie_base_impl.cc b/video_engine/main/source/vie_base_impl.cc new file mode 100644 index 0000000000..7d7edf829e --- /dev/null +++ b/video_engine/main/source/vie_base_impl.cc @@ -0,0 +1,838 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_base_impl.cc + */ + +#include "vie_base_impl.h" + +// Defines +#include "engine_configurations.h" +#include "vie_defines.h" + +#include "critical_section_wrapper.h" +#include "trace.h" +#include "vie_errors.h" +#include "vie_impl.h" +#include "vie_shared_data.h" +#include "vie_channel.h" +#include "vie_channel_manager.h" +#include "vie_encoder.h" +#include "vie_input_manager.h" +#include "vie_performance_monitor.h" +#include "rtp_rtcp.h" +#include "video_render.h" +#include "video_coding.h" +#include "video_processing.h" +#include "stdio.h" + +namespace webrtc +{ + +// ---------------------------------------------------------------------------- +// GetInterface +// ---------------------------------------------------------------------------- + +ViEBase* ViEBase::GetInterface(VideoEngine* videoEngine) +{ + if (videoEngine == NULL) + { + return NULL; + } + VideoEngineImpl* vieImpl = reinterpret_cast (videoEngine); + ViEBaseImpl* vieBaseImpl = vieImpl; + (*vieBaseImpl)++; // Increase ref count + + return vieBaseImpl; +} + +// ---------------------------------------------------------------------------- +// Release +// +// Releases the interface, i.e. reduces the reference counter. The number of +// remaining references is returned, -1 if released too many times. +// ---------------------------------------------------------------------------- +int ViEBaseImpl::Release() +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, _instanceId, + "ViEBase::Release()"); + (*this)--; // Decrease ref count + + WebRtc_Word32 refCount = GetCount(); + if (refCount < 0) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, _instanceId, + "ViEBase release too many times"); + SetLastError(kViEAPIDoesNotExist); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, _instanceId, + "ViEBase reference count: %d", refCount); + return refCount; +} + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + +ViEBaseImpl::ViEBaseImpl() : + _viePerformanceMonitor(0) +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, _instanceId, + "ViEBaseImpl::ViEBaseImpl() Ctor"); +} + +// ---------------------------------------------------------------------------- +// Destructor +// ---------------------------------------------------------------------------- + +ViEBaseImpl::~ViEBaseImpl() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, _instanceId, + "ViEBaseImpl::ViEBaseImpl() Dtor"); + + _viePerformanceMonitor.Terminate(); +} + +// ---------------------------------------------------------------------------- +// Init +// +// Must be called before any other API is called. +// This API should also reset the state of the enigne to the original state. +// ---------------------------------------------------------------------------- + +int ViEBaseImpl::Init() +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, _instanceId, "Init"); + if (IsInitialized()) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, _instanceId, + "Init called twice"); + return 0; + } + + SetInitialized(); + + _viePerformanceMonitor.Init(); + + return 0; +} + +// ---------------------------------------------------------------------------- +// SetVoiceEngine +// +// Connects ViE to a VoE instance. +// ---------------------------------------------------------------------------- + +int ViEBaseImpl::SetVoiceEngine(VoiceEngine* ptrVoiceEngine) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s", + __FUNCTION__); + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + if (_channelManager.SetVoiceEngine(ptrVoiceEngine) != 0) + { + SetLastError(kViEBaseVoEFailure); + return -1; + } + return 0; + +} + +// ============================================================================ +// Channel functions +// ============================================================================ + +// ---------------------------------------------------------------------------- +// CreateChannel +// +// Creates a new ViE channel +// ---------------------------------------------------------------------------- + +int ViEBaseImpl::CreateChannel(int& videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s", + __FUNCTION__); + + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + + if (_channelManager.CreateChannel(videoChannel) == -1) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: Could not create channel", __FUNCTION__); + videoChannel = -1; + SetLastError(kViEBaseChannelCreationFailed); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: channel created: %d", __FUNCTION__, videoChannel); + return 0; +} + +// ---------------------------------------------------------------------------- +// CreateChannel +// +// Creates a new channel using the same capture device and encoder as +// the original channel. +// ---------------------------------------------------------------------------- + +int ViEBaseImpl::CreateChannel(int& videoChannel, int originalChannel) +{ + + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + + ViEChannelManagerScoped cs(_channelManager); + + if (!cs.Channel(originalChannel)) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - originalChannel does not exist.", __FUNCTION__, + _instanceId); + SetLastError(kViEBaseInvalidChannelId); + return -1; + } + + if (_channelManager.CreateChannel(videoChannel, + originalChannel) == -1) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: Could not create channel", __FUNCTION__); + videoChannel = -1; + SetLastError(kViEBaseChannelCreationFailed); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: channel created: %d", __FUNCTION__, videoChannel); + return 0; +} + +// ---------------------------------------------------------------------------- +// DeleteChannel +// +// Deleted a ViE channel +// ---------------------------------------------------------------------------- + +int ViEBaseImpl::DeleteChannel(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s(%d)", + __FUNCTION__, videoChannel); + + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + + { + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (vieChannel == NULL) + { + // No such channel + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViEBaseInvalidChannelId); + return -1; + } + + // Deregister the ViEEncoder if no other channel is using it. + ViEEncoder* ptrViEEncoder = cs.Encoder(videoChannel); + if (cs.ChannelUsingViEEncoder(videoChannel) == false) + { + // No other channels using this ViEEncoder. + // Disconnect the channel encoder from possible input. + // capture or file. + ViEInputManagerScoped is(_inputManager); + ViEFrameProviderBase* provider = is.FrameProvider(ptrViEEncoder); + if (provider) + { + provider->DeregisterFrameCallback(ptrViEEncoder); + } + } + } + if (_channelManager.DeleteChannel(videoChannel) == -1) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: Could not delete channel %d", __FUNCTION__, + videoChannel); + SetLastError(kViEBaseUnknownError); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: channel deleted: %d", __FUNCTION__, videoChannel); + return 0; +} + +// ---------------------------------------------------------------------------- +// ConnectAudioChannel +// +// Connects a ViE channel with a VoE channel +// ---------------------------------------------------------------------------- + +int ViEBaseImpl::ConnectAudioChannel(const int videoChannel, + const int audioChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s(%d)", + __FUNCTION__, videoChannel); + + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + + ViEChannelManagerScoped cs(_channelManager); + if (cs.Channel(videoChannel) == NULL) + { + // No such channel + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: channel %d doesn't exist", __FUNCTION__, videoChannel); + SetLastError(kViEBaseInvalidChannelId); + return -1; + } + + if (_channelManager.ConnectVoiceChannel(videoChannel, audioChannel) != 0) + { + SetLastError(kViEBaseVoEFailure); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// DisconnectAudioChannel +// +// Disconnects a previously connected ViE and VoE channel pair +// ---------------------------------------------------------------------------- + +int ViEBaseImpl::DisconnectAudioChannel(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s(%d)", + __FUNCTION__, videoChannel); + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + ViEChannelManagerScoped cs(_channelManager); + if (cs.Channel(videoChannel) == NULL) + { + // No such channel + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: channel %d doesn't exist", __FUNCTION__, videoChannel); + SetLastError(kViEBaseInvalidChannelId); + return -1; + } + + if (_channelManager.DisconnectVoiceChannel(videoChannel) != 0) + { + SetLastError(kViEBaseVoEFailure); + return -1; + } + return 0; +} + +// ============================================================================ +// Start and stop +// ============================================================================ + +// ---------------------------------------------------------------------------- +// StartSend +// +// Starts sending on videoChannel and also starts the encoder. +// ---------------------------------------------------------------------------- +int ViEBaseImpl::StartSend(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId, + videoChannel), + "%s(channel: %d)", __FUNCTION__, videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d does not exist", __FUNCTION__, videoChannel); + SetLastError(kViEBaseInvalidChannelId); + return -1; + } + ViEEncoder* ptrViEEncoder = cs.Encoder(videoChannel); + if (ptrViEEncoder == NULL) + { + assert(false); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Could not find encoder for channel %d", __FUNCTION__, + videoChannel); + return -1; + } + + // Make sure we start with a key frame... + ptrViEEncoder->Pause(); + WebRtc_Word32 error = ptrViEChannel->StartSend(); + if (error != 0) + { + // Restart the encoder, if it was stopped + ptrViEEncoder->Restart(); + + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Could not start sending on channel %d", __FUNCTION__, + videoChannel); + if (error == kViEBaseAlreadySending) + { + SetLastError(kViEBaseAlreadySending); + } + SetLastError(kViEBaseUnknownError); + return -1; + } + + // Trigger the key frame and restart + ptrViEEncoder->SendKeyFrame(); + ptrViEEncoder->Restart(); + return 0; +} + +// ---------------------------------------------------------------------------- +// StopSend +// +// Stops sending on the channel. This will also stop the encoder for the +// channel, if not shared with still active channels. +// ---------------------------------------------------------------------------- +int ViEBaseImpl::StopSend(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d does not exist", __FUNCTION__, videoChannel); + SetLastError(kViEBaseInvalidChannelId); + return -1; + } + + WebRtc_Word32 error = ptrViEChannel->StopSend(); + if (error != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Could not stop sending on channel %d", __FUNCTION__, + videoChannel); + if (error == kViEBaseNotSending) + { + SetLastError(kViEBaseNotSending); + } + else + { + SetLastError(kViEBaseUnknownError); + } + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// StartReceive +// +// Stops receiving on the channel. This will also start the decoder. +// ---------------------------------------------------------------------------- +int ViEBaseImpl::StartReceive(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d does not exist", __FUNCTION__, videoChannel); + SetLastError(kViEBaseInvalidChannelId); + return -1; + } + if (ptrViEChannel->Receiving()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d already receive.", __FUNCTION__, + videoChannel); + SetLastError(kViEBaseAlreadyReceiving); + return -1; + } + if (ptrViEChannel->StartReceive() != 0) + { + SetLastError(kViEBaseUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// StopReceive +// +// Stops receiving on the channel. No decoding will be done. +// ---------------------------------------------------------------------------- +int ViEBaseImpl::StopReceive(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d does not exist", __FUNCTION__, videoChannel); + SetLastError(kViEBaseInvalidChannelId); + return -1; + } + if (ptrViEChannel->StopReceive() != 0) + { + SetLastError(kViEBaseUnknownError); + return -1; + } + return 0; +} + +// ============================================================================ +// Channel functions +// ============================================================================ + +// ---------------------------------------------------------------------------- +// RegisterObserver +// +// Registers a customer implemented ViE observer +// ---------------------------------------------------------------------------- + +int ViEBaseImpl::RegisterObserver(ViEBaseObserver& observer) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s", + __FUNCTION__); + if (_viePerformanceMonitor.ViEBaseObserverRegistered()) + { + SetLastError(kViEBaseObserverAlreadyRegistered); + return -1; + } + + return _viePerformanceMonitor.RegisterViEBaseObserver(&observer); +} + +// ---------------------------------------------------------------------------- +// DeregisterObserver +// +// Deregisters an observer +// ---------------------------------------------------------------------------- + +int ViEBaseImpl::DeregisterObserver() +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s", + __FUNCTION__); + + if (!_viePerformanceMonitor.ViEBaseObserverRegistered()) + { + SetLastError(kViEBaseObserverNotRegistered); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, _instanceId, + "%s No observer registered.", __FUNCTION__); + return -1; + } + return _viePerformanceMonitor.RegisterViEBaseObserver(NULL); +} + +// ============================================================================ +// Info functions +// ============================================================================ + +// ---------------------------------------------------------------------------- +// GetVersion +// +// Writes version information in 'version' +// ---------------------------------------------------------------------------- + +int ViEBaseImpl::GetVersion(char version[1024]) +{ + + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "GetVersion(version=?)"); + assert(kViEVersionMaxMessageSize == 1024); + + if (version == NULL) + { + SetLastError(kViEBaseInvalidArgument); + return (-1); + } + + char versionBuf[kViEVersionMaxMessageSize]; + char* versionPtr = versionBuf; + + WebRtc_Word32 len = 0; // does not include terminating NULL + WebRtc_Word32 accLen = 0; + + len = AddViEVersion(versionPtr); + if (len == -1) + { + SetLastError(kViEBaseUnknownError); + return -1; + } + versionPtr += len; + accLen += len; + assert(accLen < kViEVersionMaxMessageSize); + + len = AddBuildInfo(versionPtr); + if (len == -1) + { + SetLastError(kViEBaseUnknownError); + return -1; + } + versionPtr += len; + accLen += len; + assert(accLen < kViEVersionMaxMessageSize); + +#ifdef WEBRTC_EXTERNAL_TRANSPORT + len = AddExternalTransportBuild(versionPtr); + if (len == -1) + { + SetLastError(kViEBaseUnknownError); + return -1; + } + versionPtr += len; + accLen += len; + assert(accLen < kViEVersionMaxMessageSize); +#endif + + len = AddVCMVersion(versionPtr); + if (len == -1) + { + SetLastError(kViEBaseUnknownError); + return -1; + } + versionPtr += len; + accLen += len; + assert(accLen < kViEVersionMaxMessageSize); + +#ifndef WEBRTC_EXTERNAL_TRANSPORT + len = AddSocketModuleVersion(versionPtr); + if (len == -1) + { + SetLastError(kViEBaseUnknownError); + return -1; + } + versionPtr += len; + accLen += len; + assert(accLen < kViEVersionMaxMessageSize); +#endif + +#ifdef WEBRTC_SRTP + len = AddSRTPModuleVersion(versionPtr); + if (len == -1) + { + SetLastError(kViEBaseUnknownError); + return -1; + } + versionPtr += len; + accLen += len; + assert(accLen < kViEVersionMaxMessageSize); +#endif + + len = AddRtpRtcpModuleVersion(versionPtr); + if (len == -1) + { + SetLastError(kViEBaseUnknownError); + return -1; + } + versionPtr += len; + accLen += len; + assert(accLen < kViEVersionMaxMessageSize); + + len = AddVideoCaptureVersion(versionPtr); + if (len == -1) + { + SetLastError(kViEBaseUnknownError); + return -1; + } + versionPtr += len; + accLen += len; + assert(accLen < kViEVersionMaxMessageSize); + + len = AddRenderVersion(versionPtr); + if (len == -1) + { + SetLastError(kViEBaseUnknownError); + return -1; + } + versionPtr += len; + accLen += len; + assert(accLen < kViEVersionMaxMessageSize); + + len = AddVideoProcessingVersion(versionPtr); + if (len == -1) + { + SetLastError(kViEBaseUnknownError); + return -1; + } + versionPtr += len; + accLen += len; + assert(accLen < kViEVersionMaxMessageSize); + + memcpy(version, versionBuf, accLen); + version[accLen] = '\0'; + + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, ViEId(_instanceId), + "GetVersion() => %s", version); + return 0; +} + +WebRtc_Word32 ViEBaseImpl::AddBuildInfo(char* str) const +{ + return sprintf(str, "Build: %s\n", BUILDINFO); +} + +WebRtc_Word32 ViEBaseImpl::AddViEVersion(char* str) const +{ + return sprintf(str, "VideoEngine 3.1.0\n"); +} + +#ifdef WEBRTC_EXTERNAL_TRANSPORT +WebRtc_Word32 ViEBaseImpl::AddExternalTransportBuild(char* str) const +{ + return sprintf(str, "External transport build\n"); +} +#endif + +WebRtc_Word32 ViEBaseImpl::AddModuleVersion(webrtc::Module* module, + char* str) const +{ + WebRtc_Word8 version[kViEMaxModuleVersionSize]; + WebRtc_UWord32 remainingBufferInBytes(kViEMaxModuleVersionSize); + WebRtc_UWord32 position(0); + if (module && module->Version(version, remainingBufferInBytes, position) + == 0) + { + return sprintf(str, "%s\n", version); + } + return -1; +} + +WebRtc_Word32 ViEBaseImpl::AddVCMVersion(char* str) const +{ + webrtc::VideoCodingModule* vcmPtr = + webrtc::VideoCodingModule::Create(_instanceId); + int len = AddModuleVersion(vcmPtr, str); + webrtc::VideoCodingModule::Destroy(vcmPtr); + return len; +} + +WebRtc_Word32 ViEBaseImpl::AddVideoCaptureVersion(char* str) const +{ + return 0; +} + +WebRtc_Word32 ViEBaseImpl::AddVideoProcessingVersion(char* str) const +{ + webrtc::VideoProcessingModule* videoPtr = + webrtc::VideoProcessingModule::Create(_instanceId); + int len = AddModuleVersion(videoPtr, str); + webrtc::VideoProcessingModule::Destroy(videoPtr); + return len; +} +WebRtc_Word32 ViEBaseImpl::AddRenderVersion(char* str) const +{ + + return 0; +} + +#ifndef WEBRTC_EXTERNAL_TRANSPORT +WebRtc_Word32 ViEBaseImpl::AddSocketModuleVersion(char* str) const +{ + WebRtc_UWord8 numSockThreads(1); + UdpTransport* socketPtr = + UdpTransport::Create( + _instanceId, numSockThreads); + int len = AddModuleVersion(socketPtr, str); + UdpTransport::Destroy(socketPtr); + return len; +} +#endif + +#ifdef WEBRTC_SRTP +WebRtc_Word32 ViEBaseImpl::AddSRTPModuleVersion(char* str) const +{ + SrtpModule* srtpPtr = SrtpModule::CreateSrtpModule(-1); + int len = AddModuleVersion(srtpPtr, str); + SrtpModule::DestroySrtpModule(srtpPtr); + return len; +} +#endif + +WebRtc_Word32 ViEBaseImpl::AddRtpRtcpModuleVersion(char* str) const +{ + RtpRtcp* rtpRtcpPtr = + RtpRtcp::CreateRtpRtcp(-1, true); + int len = AddModuleVersion(rtpRtcpPtr, str); + RtpRtcp::DestroyRtpRtcp(rtpRtcpPtr); + return len; +} + +// ---------------------------------------------------------------------------- +// LastError +// +// Returns the last set error in this ViE instance +// ---------------------------------------------------------------------------- + +int ViEBaseImpl::LastError() +{ + return LastErrorInternal(); +} + +} // namespace webrtc diff --git a/video_engine/main/source/vie_base_impl.h b/video_engine/main/source/vie_base_impl.h new file mode 100644 index 0000000000..1e840dd860 --- /dev/null +++ b/video_engine/main/source/vie_base_impl.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_base_impl.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_BASE_IMPL_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_BASE_IMPL_H_ + +#include "vie_defines.h" + +#include "vie_ref_count.h" +#include "vie_shared_data.h" +#include "vie_base.h" + +// Forward declarations +namespace webrtc +{ + +class VoiceEngine; +class Module; +class ViEPerformanceMonitor; + +class ViEBaseImpl: public virtual ViESharedData, + public ViEBase, + public ViERefCount +{ +public: + virtual int Release(); + + virtual int Init(); + + virtual int SetVoiceEngine(VoiceEngine* ptrVoiceEngine); + + // Channel functions + virtual int CreateChannel(int& videoChannel); + + virtual int CreateChannel(int& videoChannel, int originalChannel); + + virtual int DeleteChannel(const int videoChannel); + + virtual int ConnectAudioChannel(const int videoChannel, + const int audioChannel); + + virtual int DisconnectAudioChannel(const int videoChannel); + + // Start and stop + virtual int StartSend(const int videoChannel); + + virtual int StopSend(const int videoChannel); + + virtual int StartReceive(const int videoChannel); + + virtual int StopReceive(const int videoChannel); + + // Callbacks + virtual int RegisterObserver(ViEBaseObserver& observer); + + virtual int DeregisterObserver(); + + // Info functions + virtual int GetVersion(char version[1024]); + + virtual int LastError(); + +protected: + ViEBaseImpl(); + virtual ~ViEBaseImpl(); + ViEPerformanceMonitor _viePerformanceMonitor; +private: + + // Version functions + WebRtc_Word32 AddViEVersion(char* str) const; + WebRtc_Word32 AddBuildInfo(char* str) const; +#ifdef WEBRTC_EXTERNAL_TRANSPORT + WebRtc_Word32 AddExternalTransportBuild(char* str) const; +#else + WebRtc_Word32 AddSocketModuleVersion(char* str) const; +#endif + WebRtc_Word32 AddModuleVersion(webrtc::Module* module, char* str) const; + WebRtc_Word32 AddVCMVersion(char* str) const; + WebRtc_Word32 AddVideoCaptureVersion(char* str) const; + WebRtc_Word32 AddVideoProcessingVersion(char* str) const; + WebRtc_Word32 AddRenderVersion(char* str) const; +#ifdef WEBRTC_SRTP + WebRtc_Word32 AddSRTPModuleVersion(char* str) const; +#endif + WebRtc_Word32 AddRtpRtcpModuleVersion(char* str) const; +}; + +} // namespace webrtc + +#endif // #define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_BASE_IMPL_H_ diff --git a/video_engine/main/source/vie_capture_impl.cc b/video_engine/main/source/vie_capture_impl.cc new file mode 100644 index 0000000000..c1664979c1 --- /dev/null +++ b/video_engine/main/source/vie_capture_impl.cc @@ -0,0 +1,792 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_capture_impl.cc + */ + +#include "vie_capture_impl.h" + +// Defines +#include "vie_defines.h" + +#include "trace.h" +#include "vie_capturer.h" +#include "vie_channel.h" +#include "vie_channel_manager.h" +#include "vie_encoder.h" +#include "vie_impl.h" +#include "vie_input_manager.h" +#include "vie_errors.h" + +namespace webrtc +{ + +// ---------------------------------------------------------------------------- +// GetInterface +// ---------------------------------------------------------------------------- + +ViECapture* ViECapture::GetInterface(VideoEngine* videoEngine) +{ +#ifdef WEBRTC_VIDEO_ENGINE_CAPTURE_API + if (videoEngine == NULL) + { + return NULL; + } + VideoEngineImpl* vieImpl = reinterpret_cast (videoEngine); + ViECaptureImpl* vieCaptureImpl = vieImpl; + (*vieCaptureImpl)++; // Increase ref count + + return vieCaptureImpl; +#else + return NULL; +#endif +} + +// ---------------------------------------------------------------------------- +// Release +// +// Releases the interface, i.e. reduces the reference counter. The number of +// remaining references is returned, -1 if released too many times. +// ---------------------------------------------------------------------------- + +int ViECaptureImpl::Release() +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, _instanceId, + "ViECapture::Release()"); + (*this)--; // Decrease ref count + + WebRtc_Word32 refCount = GetCount(); + if (refCount < 0) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, _instanceId, + "ViECapture release too many times"); + SetLastError(kViEAPIDoesNotExist); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, _instanceId, + "ViECapture reference count: %d", refCount); + return refCount; +} + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + +ViECaptureImpl::ViECaptureImpl() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, _instanceId, + "ViECaptureImpl::ViECaptureImpl() Ctor"); +} + +// ---------------------------------------------------------------------------- +// Destructor +// ---------------------------------------------------------------------------- + +ViECaptureImpl::~ViECaptureImpl() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, _instanceId, + "ViECaptureImpl::~ViECaptureImpl() Dtor"); +} + +// ============================================================================ +// Available devices +// ============================================================================ + +// ---------------------------------------------------------------------------- +// NumberOfCaptureDevices +// +// Returns the number of available devices +// ---------------------------------------------------------------------------- +int ViECaptureImpl::NumberOfCaptureDevices() +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s", + __FUNCTION__); + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + return _inputManager.NumberOfCaptureDevices(); +} + +// ---------------------------------------------------------------------------- +// GetCaptureDevice +// +// Gets capture device listNumber, both name and unique id if available +// ---------------------------------------------------------------------------- +int ViECaptureImpl::GetCaptureDevice(unsigned int listNumber, + char* deviceNameUTF8, + unsigned int deviceNameUTF8Length, + char* uniqueIdUTF8, + unsigned int uniqueIdUTF8Length) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(listNumber: %d)", __FUNCTION__, listNumber); + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + return _inputManager.GetDeviceName(listNumber, + (WebRtc_UWord8*) deviceNameUTF8, + deviceNameUTF8Length, + (WebRtc_UWord8*) uniqueIdUTF8, + uniqueIdUTF8Length); +} + +// ============================================================================ +// Allocate capture device +// ============================================================================ + + +// ---------------------------------------------------------------------------- +// AllocateCaptureDevice +// +// Allocates the capture device +// ---------------------------------------------------------------------------- +int ViECaptureImpl::AllocateCaptureDevice( + const char* uniqueIdUTF8, + const unsigned int uniqueIdUTF8Length, + int& captureId) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(uniqueIdUTF8: %s)", __FUNCTION__, uniqueIdUTF8); + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + const WebRtc_Word32 + result = + _inputManager.CreateCaptureDevice( + (const WebRtc_UWord8*) uniqueIdUTF8, + (const WebRtc_UWord32) uniqueIdUTF8Length, captureId); + if (result != 0) + { + SetLastError(result); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// AllocateExternalCaptureDevice +// +// Register a customer implemented capture device. callback should be called +// for all new captured images once the the capture device is started +// ---------------------------------------------------------------------------- +int ViECaptureImpl::AllocateExternalCaptureDevice( + int& captureId, ViEExternalCapture*& externalCapture) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s", + __FUNCTION__); + + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + const WebRtc_Word32 result = + _inputManager.CreateExternalCaptureDevice(externalCapture, captureId); + + if (result != 0) + { + SetLastError(result); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// AllocateCaptureDevice +// +// Allocates the capture device, the capture module to attach +// must be associated with the unique ID. +// ---------------------------------------------------------------------------- +int ViECaptureImpl::AllocateCaptureDevice(VideoCaptureModule& captureModule, + int& captureId) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s", + __FUNCTION__); + + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + const WebRtc_Word32 result = + _inputManager.CreateCaptureDevice(captureModule, captureId); + if (result != 0) + { + SetLastError(result); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// ReleaseCaptureDevice +// +// Releases an allocated capture device +// ---------------------------------------------------------------------------- +int ViECaptureImpl::ReleaseCaptureDevice(const int captureId) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(captureId: %d)", __FUNCTION__, captureId); + + { + ViEInputManagerScoped is(_inputManager); + ViECapturer* ptrViECapture = is.Capture(captureId); + if (ptrViECapture == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: Capture device %d doesn't exist", __FUNCTION__, + captureId); + SetLastError(kViECaptureDeviceDoesnNotExist); + return -1; + } + + } + + // Destroy the capture device + return _inputManager.DestroyCaptureDevice(captureId); +} + +// ============================================================================ +// Pair capture device and channel +// ============================================================================ + +// ---------------------------------------------------------------------------- +// ConnectCaptureDevice +// +// Connects a capture device with a channel, i.e. the capture video from this +// device will be sent to that channel. Serveral channels can be connectet to +// the same capture device. +// ---------------------------------------------------------------------------- +int ViECaptureImpl::ConnectCaptureDevice(const int captureId, + const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(captureId: %d, videoChannel: %d)", __FUNCTION__, captureId, + videoChannel); + + ViEInputManagerScoped is(_inputManager); + ViECapturer* ptrViECapture = is.Capture(captureId); + if (ptrViECapture == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Capture device %d doesn't exist", __FUNCTION__, + captureId); + SetLastError(kViECaptureDeviceDoesnNotExist); + return -1; + } + + ViEChannelManagerScoped cs(_channelManager); + ViEEncoder* ptrViEEncoder = cs.Encoder(videoChannel); + if (ptrViEEncoder == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViECaptureDeviceInvalidChannelId); + return -1; + } + // Check if the encoder already has a connected frame provider + if (is.FrameProvider(ptrViEEncoder) != NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d already connected to a capture device.", + __FUNCTION__, videoChannel); + SetLastError(kViECaptureDeviceAlreadyConnected); + return -1; + } + VideoCodec codec; + bool useHardwareEncoder = false; + if (ptrViEEncoder->GetEncoder(codec) == 0) + { // try to provide the encoder with preencoded frames if possible + if (ptrViECapture->PreEncodeToViEEncoder(codec, *ptrViEEncoder, + videoChannel) == 0) + { + useHardwareEncoder = true; + } + } + // If we don't use the camera as hardware encoder we register the vieEncoder + // for callbacks + if (!useHardwareEncoder + && ptrViECapture->RegisterFrameCallback(videoChannel, ptrViEEncoder) + != 0) + { + SetLastError(kViECaptureDeviceUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// DisconnectCaptureDevice +// +// Disconnects a capture device from a connected channel. +// ---------------------------------------------------------------------------- +int ViECaptureImpl::DisconnectCaptureDevice(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(videoChannel: %d)", + __FUNCTION__, videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEEncoder* ptrViEEncoder = cs.Encoder(videoChannel); + if (ptrViEEncoder == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViECaptureDeviceInvalidChannelId); + return -1; + } + + ViEInputManagerScoped is(_inputManager); + ViEFrameProviderBase* frameProvider = is.FrameProvider(ptrViEEncoder); + if (!frameProvider) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: No capture device connected to channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViECaptureDeviceNotConnected); + return -1; + } + if (frameProvider->Id() < kViECaptureIdBase + || frameProvider->Id() > kViECaptureIdMax) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: No capture device connected to channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViECaptureDeviceNotConnected); + return -1; + } + + if (frameProvider->DeregisterFrameCallback(ptrViEEncoder) != 0) + { + SetLastError(kViECaptureDeviceUnknownError); + return -1; + } + + return 0; + +} + +// ============================================================================ +// Start/stop +// ============================================================================ + +// ---------------------------------------------------------------------------- +// StartCapture +// +// Starts an allocated capture device, i.e. will start output captured frame +// ---------------------------------------------------------------------------- +int ViECaptureImpl::StartCapture(const int captureId, + const CaptureCapability captureCapability) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(captureId: %d)", __FUNCTION__, captureId); + + ViEInputManagerScoped is(_inputManager); + ViECapturer* ptrViECapture = is.Capture(captureId); + if (ptrViECapture == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, captureId), + "%s: Capture device %d doesn't exist", __FUNCTION__, + captureId); + SetLastError(kViECaptureDeviceDoesnNotExist); + return -1; + } + if (ptrViECapture->Started()) + { + SetLastError(kViECaptureDeviceAlreadyStarted); + return -1; + } + if (ptrViECapture->Start(captureCapability) != 0) + { + SetLastError(kViECaptureDeviceUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// StopCapture +// +// Stops a started capture device +// ---------------------------------------------------------------------------- +int ViECaptureImpl::StopCapture(const int captureId) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(captureId: %d)", __FUNCTION__, captureId); + + ViEInputManagerScoped is(_inputManager); + ViECapturer* ptrViECapture = is.Capture(captureId); + if (ptrViECapture == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, captureId), + "%s: Capture device %d doesn't exist", __FUNCTION__, + captureId); + SetLastError(kViECaptureDeviceDoesnNotExist); + return -1; + } + if (!ptrViECapture->Started()) + { + SetLastError(kViECaptureDeviceNotStarted); + return -1; + } + if (ptrViECapture->Stop() != 0) + { + SetLastError(kViECaptureDeviceUnknownError); + return -1; + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// RotateCapturedFrames +// +// Rotates a frame as soon as it's delivered from the capture device. +// This will apply to mobile devices with accelerometers or other rotation +// detection abilities. +// ---------------------------------------------------------------------------- +int ViECaptureImpl::SetRotateCapturedFrames(const int captureId, + const RotateCapturedFrame rotation) +{ + int iRotation = -1; + switch (rotation) + { + case RotateCapturedFrame_0: + iRotation = 0; + break; + case RotateCapturedFrame_90: + iRotation = 90; + break; + case RotateCapturedFrame_180: + iRotation = 180; + break; + case RotateCapturedFrame_270: + iRotation = 270; + break; + + } + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(rotation: %d)", __FUNCTION__, iRotation); + + ViEInputManagerScoped is(_inputManager); + ViECapturer* ptrViECapture = is.Capture(captureId); + if (ptrViECapture == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, captureId), + "%s: Capture device %d doesn't exist", __FUNCTION__, + captureId); + SetLastError(kViECaptureDeviceDoesnNotExist); + return -1; + } + if (ptrViECapture->SetRotateCapturedFrames(rotation) != 0) + { + SetLastError(kViECaptureDeviceUnknownError); + return -1; + } + + return 0; + +} + +// ---------------------------------------------------------------------------- +// SetCaptureDelay +// +// Defines the capture delay for an external capture device. +// This call will also override a the capture delay value for a capture +// device. +// ---------------------------------------------------------------------------- +int ViECaptureImpl::SetCaptureDelay(const int captureId, + const unsigned int captureDelayMs) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(captureId: %d, captureDelayMs %u)", __FUNCTION__, + captureId, captureDelayMs); + + ViEInputManagerScoped is(_inputManager); + ViECapturer* ptrViECapture = is.Capture(captureId); + if (ptrViECapture == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, captureId), + "%s: Capture device %d doesn't exist", __FUNCTION__, + captureId); + SetLastError(kViECaptureDeviceDoesnNotExist); + return -1; + } + + if (ptrViECapture->SetCaptureDelay(captureDelayMs) != 0) + { + SetLastError(kViECaptureDeviceUnknownError); + return -1; + } + return 0; + +} + +// ============================================================================ +// Capture capabilities +// ============================================================================ + +// ---------------------------------------------------------------------------- +// NumberOfCapabilities +// +// Returns the number of capabilities fot the specified device +// ---------------------------------------------------------------------------- +int ViECaptureImpl::NumberOfCapabilities(const char* uniqueIdUTF8, + const unsigned int uniqueIdUTF8Length) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(captureDeviceName: %s)", __FUNCTION__, uniqueIdUTF8); + +#if defined(WEBRTC_MAC_INTEL) + // TODO: Move to capture module! + // QTKit framework handles all capabilites and capture settings + // automatically (mandatory). + // Thus this function cannot be supported on the Mac platform. + SetLastError(kViECaptureDeviceMacQtkitNotSupported); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s This API is not supported on Mac OS", __FUNCTION__, + _instanceId); + return -1; +#endif + + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + + return (int) _inputManager.NumberOfCaptureCapabilities( + (WebRtc_UWord8*) uniqueIdUTF8); +} + +// ---------------------------------------------------------------------------- +// GetCaptureCapability +// +// Gets a capture capability for the specified capture device +// ---------------------------------------------------------------------------- +int ViECaptureImpl::GetCaptureCapability(const char* uniqueIdUTF8, + const unsigned int uniqueIdUTF8Length, + const unsigned int capabilityNumber, + CaptureCapability& capability) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(captureDeviceName: %s)", __FUNCTION__, uniqueIdUTF8); + +#if defined(WEBRTC_MAC_INTEL) + // TODO: Move to capture module! + // QTKit framework handles all capabilites and capture settings + // automatically (mandatory). + // Thus this function cannot be supported on the Mac platform. + SetLastError(kViECaptureDeviceMacQtkitNotSupported); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s This API is not supported on Mac OS", __FUNCTION__, + _instanceId); + return -1; +#endif + + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + if (_inputManager.GetCaptureCapability((WebRtc_UWord8*) uniqueIdUTF8, + capabilityNumber, capability) != 0) + { + SetLastError(kViECaptureDeviceUnknownError); + return -1; + } + return 0; +} + +int ViECaptureImpl::ShowCaptureSettingsDialogBox( + const char* uniqueIdUTF8, const unsigned int uniqueIdUTF8Length, + const char* dialogTitle, void* parentWindow /*= NULL*/, + const unsigned int x/*=200*/, const unsigned int y/*=200*/) +{ +#if defined(WEBRTC_MAC_INTEL) + // TODO: Move to capture module + // QTKit framework handles all capabilites and capture settings + // automatically (mandatory). + // Thus this function cannot be supported on the Mac platform. + SetLastError(kViECaptureDeviceMacQtkitNotSupported); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s This API is not supported on Mac OS", __FUNCTION__, + _instanceId); + return -1; +#endif + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s captureId (captureDeviceName: %s)", __FUNCTION__, + uniqueIdUTF8); + + return _inputManager.DisplayCaptureSettingsDialogBox( + (WebRtc_UWord8*) uniqueIdUTF8, (WebRtc_UWord8*) dialogTitle, + parentWindow, x, y); +} + +int ViECaptureImpl::GetOrientation(const char* uniqueIdUTF8, + RotateCapturedFrame &orientation) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s (captureDeviceName: %s)", __FUNCTION__, uniqueIdUTF8); + + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + if (_inputManager.GetOrientation((WebRtc_UWord8*) uniqueIdUTF8, + orientation) != 0) + { + SetLastError(kViECaptureDeviceUnknownError); + return -1; + } + return 0; +} + +// ============================================================================ +// Callbacks +// ============================================================================ + +// ---------------------------------------------------------------------------- +// EnableBrightnessAlarm +// +// Enables brightness alarm callback for a specified capture device +// ---------------------------------------------------------------------------- +int ViECaptureImpl::EnableBrightnessAlarm(const int captureId, + const bool enable) +{ + ViEInputManagerScoped is(_inputManager); + ViECapturer* ptrViECapture = is.Capture(captureId); + if (ptrViECapture == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, captureId), + "%s: Capture device %d doesn't exist", __FUNCTION__, + captureId); + SetLastError(kViECaptureDeviceDoesnNotExist); + return -1; + } + if (ptrViECapture->EnableBrightnessAlarm(enable) != 0) + { + SetLastError(kViECaptureDeviceUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// RegisterObserver +// +// Register the customer implemented observer for capture callbacks +// ---------------------------------------------------------------------------- +int ViECaptureImpl::RegisterObserver(const int captureId, + ViECaptureObserver& observer) +{ + ViEInputManagerScoped is(_inputManager); + ViECapturer* ptrViECapture = is.Capture(captureId); + if (ptrViECapture == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, captureId), + "%s: Capture device %d doesn't exist", __FUNCTION__, + captureId); + SetLastError(kViECaptureDeviceDoesnNotExist); + return -1; + } + if (ptrViECapture->IsObserverRegistered()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, captureId), + "%s: Observer already registered", __FUNCTION__); + SetLastError(kViECaptureObserverAlreadyRegistered); + return -1; + } + + if (ptrViECapture->RegisterObserver(observer) != 0) + { + SetLastError(kViECaptureDeviceUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// DeregisterObserver +// +// Removes the previously registered observer +// ---------------------------------------------------------------------------- +int ViECaptureImpl::DeregisterObserver(const int captureId) +{ + ViEInputManagerScoped is(_inputManager); + ViECapturer* ptrViECapture = is.Capture(captureId); + if (ptrViECapture == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, captureId), + "%s: Capture device %d doesn't exist", __FUNCTION__, + captureId); + SetLastError(kViECaptureDeviceDoesnNotExist); + return -1; + } + if (!ptrViECapture->IsObserverRegistered()) + { + SetLastError(kViECaptureDeviceObserverNotRegistered); + return -1; + } + + if (ptrViECapture->DeRegisterObserver() != 0) + { + SetLastError(kViECaptureDeviceUnknownError); + return -1; + } + return 0; +} +} // namespace webrtc diff --git a/video_engine/main/source/vie_capture_impl.h b/video_engine/main/source/vie_capture_impl.h new file mode 100644 index 0000000000..4434aaa14a --- /dev/null +++ b/video_engine/main/source/vie_capture_impl.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_capture_impl.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_CAPTURE_IMPL_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_CAPTURE_IMPL_H_ + +#include "vie_defines.h" + +#include "typedefs.h" +#include "vie_capture.h" +#include "vie_ref_count.h" +#include "vie_shared_data.h" + +namespace webrtc +{ + +// ---------------------------------------------------------------------------- +// ViECaptureImpl +// ---------------------------------------------------------------------------- + +class ViECaptureImpl: public virtual ViESharedData, + public ViECapture, + public ViERefCount +{ +public: + virtual int Release(); + + // Available devices + virtual int NumberOfCaptureDevices(); + + virtual int GetCaptureDevice(unsigned int listNumber, char* deviceNameUTF8, + const unsigned int deviceNameUTF8Length, + char* uniqueIdUTF8, + const unsigned int uniqueIdUTF8Length); + + // Allocate capture device + virtual int AllocateCaptureDevice(const char* uniqueIdUTF8, + const unsigned int uniqueIdUTF8Length, + int& captureId); + + // Allocate capture device + virtual int AllocateCaptureDevice(VideoCaptureModule& captureModule, + int& captureId); + // Allocate external capture device + virtual int AllocateExternalCaptureDevice( + int& captureId, ViEExternalCapture *&externalCapture); + + virtual int ReleaseCaptureDevice(const int captureId); + + // Pair capture device and channel + virtual int ConnectCaptureDevice(const int captureId, + const int videoChannel); + + virtual int DisconnectCaptureDevice(const int videoChannel); + + // Start/stop + virtual int StartCapture(const int captureId, + const CaptureCapability captureCapability = + CaptureCapability()); + + virtual int StopCapture(const int captureId); + + virtual int SetRotateCapturedFrames(const int captureId, + const RotateCapturedFrame rotation); + + virtual int SetCaptureDelay(const int captureId, + const unsigned int captureDelayMs); + + // Capture capabilities + virtual int NumberOfCapabilities(const char* uniqueIdUTF8, + const unsigned int uniqueIdUTF8Length); + + virtual int GetCaptureCapability(const char* uniqueIdUTF8, + const unsigned int uniqueIdUTF8Length, + const unsigned int capabilityNumber, + CaptureCapability& capability); + + virtual int ShowCaptureSettingsDialogBox( + const char* uniqueIdUTF8, const unsigned int uniqueIdUTF8Length, + const char* dialogTitle, void* parentWindow = NULL, + const unsigned int x = 200, const unsigned int y = 200); + + virtual int GetOrientation(const char* uniqueIdUTF8, + RotateCapturedFrame &orientation); + + // Callbacks + virtual int EnableBrightnessAlarm(const int captureId, const bool enable); + + virtual int RegisterObserver(const int captureId, + ViECaptureObserver& observer); + + virtual int DeregisterObserver(const int captureId); + +protected: + ViECaptureImpl(); + virtual ~ViECaptureImpl(); +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_CAPTURE_IMPL_H_ diff --git a/video_engine/main/source/vie_capturer.cc b/video_engine/main/source/vie_capturer.cc new file mode 100644 index 0000000000..ea5afd491f --- /dev/null +++ b/video_engine/main/source/vie_capturer.cc @@ -0,0 +1,1092 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_capturer.cc + */ + +#include "vie_capturer.h" +#include "vie_defines.h" + +#include "critical_section_wrapper.h" +#include "event_wrapper.h" +#include "module_common_types.h" +#include "video_capture.h" +#include "video_capture.h" +#include "video_processing.h" +#include "video_render_defines.h" +#include "thread_wrapper.h" +#include "vie_image_process.h" +#include "vie_encoder.h" +#include "process_thread.h" +#include "trace.h" + +namespace webrtc { + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + +ViECapturer::ViECapturer(int captureId, + int engineId, + ProcessThread& moduleProcessThread) + : ViEFrameProviderBase(captureId, engineId), + _captureCritsect(*CriticalSectionWrapper::CreateCriticalSection()), + _deliverCritsect(*CriticalSectionWrapper::CreateCriticalSection()), + _captureModule(NULL), _useExternalModule(false), + _externalCaptureModule(NULL), + _moduleProcessThread(moduleProcessThread), + _captureId(captureId), + _vieCaptureThread(*ThreadWrapper::CreateThread(ViECaptureThreadFunction, + this, kHighPriority, + "ViECaptureThread")), + _vieCaptureEvent(*EventWrapper::Create()), + _vieDeliverEvent(*EventWrapper::Create()), _capturedFrame(), + _deliverFrame(), + _observerCritsect(*CriticalSectionWrapper::CreateCriticalSection()), + _observer(NULL), _effectFilter(NULL), _imageProcModule(NULL), + _imageProcModuleRefCounter(0), _deflickerFrameStats(NULL), + _brightnessFrameStats(NULL), _currentBrightnessLevel(Normal), + _reportedBrightnessLevel(Normal), _denoisingEnabled(false), + _encodingCritsect(*CriticalSectionWrapper::CreateCriticalSection()), + _captureEncoder(NULL), _encodeCompleteCallback(NULL), + _vieEncoder(NULL), _vcm(NULL), _decodeBuffer(), + _decoderInitialized(false), _requestedCapability() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, ViEId(engineId, captureId), + "ViECapturer::ViECapturer(captureId: %d, engineId: %d) - " + "Constructor", captureId, engineId); + + unsigned int tId = 0; + if (_vieCaptureThread.Start(tId)) + { + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(engineId, captureId), + "%s: thread started: %u", __FUNCTION__, tId); + } else + { + assert(false); + } +} + +// ---------------------------------------------------------------------------- +// Destructor +// ---------------------------------------------------------------------------- + +ViECapturer::~ViECapturer() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, + ViEId(_engineId, _captureId), + "ViECapturer Destructor, captureId: %d, engineId: %d", + _captureId, _engineId); + + // Stop the thread + _deliverCritsect.Enter(); + _captureCritsect.Enter(); + _vieCaptureThread.SetNotAlive(); + _vieCaptureEvent.Set(); + _captureCritsect.Leave(); + _deliverCritsect.Leave(); + + _providerCritSect.Enter(); + if (_vieEncoder) + { + _vieEncoder->DeRegisterExternalEncoder(_codec.plType); + } + _providerCritSect.Leave(); + + // Stop the camera input + if (_captureModule) + { + _moduleProcessThread.DeRegisterModule(_captureModule); + _captureModule->DeRegisterCaptureDataCallback(); + } + if (_vieCaptureThread.Stop()) + { + // Thread stopped + delete &_vieCaptureThread; + delete &_vieCaptureEvent; + delete &_vieDeliverEvent; + } else + { + assert(false); + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideoRenderer, ViEId(_engineId, _captureId), + "%s: Not able to stop capture thread for device %d, leaking", + __FUNCTION__, _captureId); + // Not possible to stop the thread, leak it... + } + + if (!_useExternalModule) + { + VideoCaptureModule::Destroy(_captureModule); + } + _captureModule = NULL; + if (_imageProcModule) + { + VideoProcessingModule::Destroy(_imageProcModule); + } + if (_deflickerFrameStats) + { + delete _deflickerFrameStats; + _deflickerFrameStats = NULL; + } + delete _brightnessFrameStats; + if (_vcm) + { + delete _vcm; + } + delete &_captureCritsect; + delete &_deliverCritsect; + delete &_encodingCritsect; + delete &_observerCritsect; +} + +// ---------------------------------------------------------------------------- +// Static factory class +// ---------------------------------------------------------------------------- +ViECapturer* ViECapturer::CreateViECapture(int captureId, + int engineId, + VideoCaptureModule& captureModule, + ProcessThread& moduleProcessThread) +{ + ViECapturer* capture = new ViECapturer(captureId, engineId, + moduleProcessThread); + if (!capture || capture->Init(captureModule) != 0) + { + delete capture; + capture = NULL; + } + return capture; +} + +WebRtc_Word32 ViECapturer::Init(VideoCaptureModule& captureModule) +{ + _captureModule = &captureModule; + _useExternalModule = true; + _captureModule->RegisterCaptureDataCallback(*this); + if (_moduleProcessThread.RegisterModule(_captureModule) != 0) + { + return -1; + } + return 0; +} + +ViECapturer* ViECapturer::CreateViECapture(int captureId, + int engineId, + const WebRtc_UWord8* deviceUniqueIdUTF8, + const WebRtc_UWord32 deviceUniqueIdUTF8Length, + ProcessThread& moduleProcessThread) +{ + ViECapturer* capture = new ViECapturer(captureId, engineId, moduleProcessThread); + if (!capture || + capture->Init(deviceUniqueIdUTF8, deviceUniqueIdUTF8Length) != 0) + { + delete capture; + capture = NULL; + } + return capture; +} +WebRtc_Word32 ViECapturer::Init(const WebRtc_UWord8* deviceUniqueIdUTF8, + const WebRtc_UWord32 deviceUniqueIdUTF8Length) +{ +#ifndef WEBRTC_VIDEO_EXTERNAL_CAPTURE_AND_RENDER + if (deviceUniqueIdUTF8 == NULL) + { + _captureModule = VideoCaptureModule::Create( + ViEModuleId(_engineId, _captureId), + _externalCaptureModule); + } else + { + _captureModule = VideoCaptureModule::Create( + ViEModuleId(_engineId, _captureId), + deviceUniqueIdUTF8); + } +#endif + if (!_captureModule) + return -1; + _captureModule->RegisterCaptureDataCallback(*this); + if (_moduleProcessThread.RegisterModule(_captureModule) != 0) + { + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// FrameCallbackChanged +// ---------------------------------------------------------------------------- +int ViECapturer::FrameCallbackChanged() +{ + if (Started() && !EncoderActive() && !CaptureCapabilityFixed()) // Reconfigure the camera if a new size is required and the capture device does not provide encoded frames. + { + int bestWidth; + int bestHeight; + int bestFrameRate; + VideoCaptureCapability captureSettings; + _captureModule->CaptureSettings(captureSettings); + GetBestFormat(bestWidth, bestHeight, bestFrameRate); + if (bestWidth != 0 && bestHeight != 0 && bestFrameRate != 0) + { + if (bestWidth != captureSettings.width || + bestHeight != captureSettings.height || + bestFrameRate != captureSettings.maxFPS || + captureSettings.codecType != kVideoCodecUnknown) + { + Stop(); + Start(_requestedCapability); + } + } + } + return 0; +} + +// ---------------------------------------------------------------------------- +// Start +// +// Starts the capture device. +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViECapturer::Start(const CaptureCapability captureCapability) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s", __FUNCTION__); + + int width; + int height; + int frameRate; + VideoCaptureCapability capability; + _requestedCapability = captureCapability; + if (EncoderActive()) + { + CriticalSectionScoped cs(_encodingCritsect); + capability.width = _codec.width; + capability.height = _codec.height; + capability.maxFPS = _codec.maxFramerate; + capability.codecType = _codec.codecType; + capability.rawType = kVideoI420; + + } else if (!CaptureCapabilityFixed()) + { + GetBestFormat(width, height, frameRate); // Ask the observers for best size + if (width == 0) + { + width = kViECaptureDefaultWidth; + } + if (height == 0) + { + height = kViECaptureDefaultHeight; + } + if (frameRate == 0) + { + frameRate = kViECaptureDefaultFramerate; + } + capability.height = height; + capability.width = width; + capability.maxFPS = frameRate; + capability.rawType = kVideoI420; + capability.codecType = kVideoCodecUnknown; + } else // Width and heigh and type specified with call to Start - not set by observers. + { + capability.width = _requestedCapability.width; + capability.height = _requestedCapability.height; + capability.maxFPS = _requestedCapability.maxFPS; + capability.rawType = _requestedCapability.rawType; + capability.interlaced = _requestedCapability.interlaced; + } + return _captureModule->StartCapture(capability); +} + +// ---------------------------------------------------------------------------- +// Stop +// +// Stops the capture device +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViECapturer::Stop() +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s", __FUNCTION__); + _requestedCapability = CaptureCapability(); + return _captureModule->StopCapture(); +} + +// ---------------------------------------------------------------------------- +// Started +// +// Returns true if the capture device is started, false otherwise +// ---------------------------------------------------------------------------- + +bool ViECapturer::Started() +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s", __FUNCTION__); + return _captureModule->CaptureStarted(); +} + +const WebRtc_UWord8* ViECapturer::CurrentDeviceName() const +{ + return _captureModule->CurrentDeviceName(); +} + +// ---------------------------------------------------------------------------- +// SetCaptureDelay +// +// Overrides the capture delay +// ---------------------------------------------------------------------------- +WebRtc_Word32 ViECapturer::SetCaptureDelay(WebRtc_Word32 delayMs) +{ + return _captureModule->SetCaptureDelay(delayMs); +} + +// ---------------------------------------------------------------------------- +// SetCapturedFrameRotation +// +// Tell the capture module whether or not to rotate a frame when it is captured +// ---------------------------------------------------------------------------- +WebRtc_Word32 ViECapturer::SetRotateCapturedFrames( + const RotateCapturedFrame rotation) +{ + VideoCaptureRotation convertedRotation = kCameraRotate0; + switch (rotation) + { + case RotateCapturedFrame_0: + convertedRotation = kCameraRotate0; + break; + case RotateCapturedFrame_90: + convertedRotation = kCameraRotate90; + break; + case RotateCapturedFrame_180: + convertedRotation = kCameraRotate180; + break; + case RotateCapturedFrame_270: + convertedRotation = kCameraRotate270; + break; + default: + break; + } + return _captureModule->SetCaptureRotation(convertedRotation); +} + +// ---------------------------------------------------------------------------- +// IncomingFrame +// +// Inherited from ExternalCapture, will deliver a new captured +// frame to the capture module. +// ---------------------------------------------------------------------------- + +int ViECapturer::IncomingFrame(unsigned char* videoFrame, + unsigned int videoFrameLength, + unsigned short width, unsigned short height, + RawVideoType videoType, + unsigned long long captureTime) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%ExternalCapture::IncomingFrame width %d, height %d, captureTime %u", + width, height, captureTime); + + if (!_externalCaptureModule) + { + return -1; + } + VideoCaptureCapability capability; + capability.width = width; + capability.height = height; + capability.rawType = videoType; + return _externalCaptureModule->IncomingFrame(videoFrame, videoFrameLength, + capability, captureTime); +} +// ---------------------------------------------------------------------------- +// OnIncomingCapturedFrame +// +// Inherited from VideoCaptureDataCallback, will deliver a new captured +// frame +// ---------------------------------------------------------------------------- + +void ViECapturer::OnIncomingCapturedFrame(const WebRtc_Word32 captureId, + VideoFrame& videoFrame, + VideoCodecType codecType) +{ + WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s(captureId: %d)", __FUNCTION__, captureId); + + CriticalSectionScoped cs(_captureCritsect); + if (codecType != kVideoCodecUnknown) + { + if (_encodedFrame.Length() != 0) // The last encoded frame has not been sent yet. Need to wait + { + _vieDeliverEvent.Reset(); + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s(captureId: %d) Last encoded frame not yet delivered.", + __FUNCTION__, captureId); + _captureCritsect.Leave(); + _vieDeliverEvent.Wait(500); // Wait 500ms for the coded frame to be sent before unblocking this. + assert(_encodedFrame.Length()==0); + _captureCritsect.Enter(); + } + _encodedFrame.SwapFrame(videoFrame); + } else + { + _capturedFrame.SwapFrame(videoFrame); + } + _vieCaptureEvent.Set(); + return; +} + +void ViECapturer::OnCaptureDelayChanged(const WebRtc_Word32 id, + const WebRtc_Word32 delay) +{ + WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, + ViEId(_engineId, _captureId), + "%s(captureId: %d) delay %d", __FUNCTION__, _captureId, + delay); + + // Deliver the network delay to all registered callbacks + ViEFrameProviderBase::SetFrameDelay(delay); + CriticalSectionScoped cs(_encodingCritsect); + if (_vieEncoder) + { + _vieEncoder->DelayChanged(id, delay); + } +} + +WebRtc_Word32 ViECapturer::RegisterEffectFilter(ViEEffectFilter* effectFilter) +{ + CriticalSectionScoped cs(_deliverCritsect); + + if (effectFilter == NULL) + { + if (_effectFilter == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s: no effect filter added for capture device %d", + __FUNCTION__, _captureId); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId,_captureId), + "%s: deregister effect filter for device %d", __FUNCTION__, + _captureId); + } else + { + if (_effectFilter) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId,_captureId), + "%s: effect filter already added for capture device %d", + __FUNCTION__, _captureId); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s: register effect filter for device %d", __FUNCTION__, + _captureId); + } + _effectFilter = effectFilter; + return 0; +} + +//------------------------------------------------------------------------------------------------ +// +// IncImageProcRefCount +// Help function used for keeping track of VideoImageProcesingModule. Creates the module if it is needed. +// Return 0 on success and guarantee that the image proc module exist. +//------------------------------------------------------------------------------------------------ + +WebRtc_Word32 ViECapturer::IncImageProcRefCount() +{ + if (!_imageProcModule) + { + assert(_imageProcModuleRefCounter==0); + _imageProcModule = VideoProcessingModule::Create(ViEModuleId(_engineId, _captureId)); + if (_imageProcModule == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s: could not create video processing module", + __FUNCTION__); + return -1; + } + } + _imageProcModuleRefCounter++; + return 0; +} +WebRtc_Word32 ViECapturer::DecImageProcRefCount() +{ + _imageProcModuleRefCounter--; + // Destroy module + if (_imageProcModuleRefCounter == 0) + { + VideoProcessingModule::Destroy(_imageProcModule); + _imageProcModule = NULL; + } + return 0; +} + +WebRtc_Word32 ViECapturer::EnableDenoising(bool enable) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s(captureDeviceId: %d, enable: %d)", __FUNCTION__, + _captureId, enable); + + CriticalSectionScoped cs(_deliverCritsect); + if (enable) + { + // Sanity check + if (_denoisingEnabled) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s: denoising already enabled", __FUNCTION__); + return -1; + } + _denoisingEnabled = true; + if (IncImageProcRefCount() != 0) + { + return -1; + } + } else + { + // Sanity check + if (_denoisingEnabled == false) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s: denoising not enabled", __FUNCTION__); + return -1; + } + _denoisingEnabled = false; + DecImageProcRefCount(); + } + + return 0; +} + +WebRtc_Word32 ViECapturer::EnableDeflickering(bool enable) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s(captureDeviceId: %d, enable: %d)", __FUNCTION__, + _captureId, enable); + + CriticalSectionScoped cs(_deliverCritsect); + if (enable) + { + // Sanity check + if (_deflickerFrameStats) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s: deflickering already enabled", __FUNCTION__); + return -1; + } + // Create the module + if (IncImageProcRefCount() != 0) + { + return -1; + } + _deflickerFrameStats = new VideoProcessingModule::FrameStats(); + } else + { + // Sanity check + if (_deflickerFrameStats == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s: deflickering not enabled", __FUNCTION__); + return -1; + } + // Destroy module + DecImageProcRefCount(); + delete _deflickerFrameStats; + _deflickerFrameStats = NULL; + } + return 0; +} +WebRtc_Word32 ViECapturer::EnableBrightnessAlarm(bool enable) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s(captureDeviceId: %d, enable: %d)", __FUNCTION__, + _captureId, enable); + + CriticalSectionScoped cs(_deliverCritsect); + if (enable) + { + // Sanity check + if (_brightnessFrameStats) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s: BrightnessAlarm already enabled", __FUNCTION__); + return -1; + } + if (IncImageProcRefCount() != 0) + { + return -1; + } + _brightnessFrameStats = new VideoProcessingModule::FrameStats(); + } else + { + DecImageProcRefCount(); + // Sanity check + if (_brightnessFrameStats == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s: deflickering not enabled", __FUNCTION__); + return -1; + } + delete _brightnessFrameStats; + _brightnessFrameStats = NULL; + } + return 0; +} + +bool ViECapturer::ViECaptureThreadFunction(void* obj) +{ + return static_cast (obj)->ViECaptureProcess(); +} + +bool ViECapturer::ViECaptureProcess() +{ + if (_vieCaptureEvent.Wait(kThreadWaitTimeMs) == kEventSignaled) + { + _deliverCritsect.Enter(); + if (_capturedFrame.Length() > 0) // New I420 frame + { + _captureCritsect.Enter(); + _deliverFrame.SwapFrame(_capturedFrame); + _capturedFrame.SetLength(0); + _captureCritsect.Leave(); + DeliverI420Frame(_deliverFrame); + } + if (_encodedFrame.Length() > 0) + { + _captureCritsect.Enter(); + _deliverFrame.SwapFrame(_encodedFrame); + _encodedFrame.SetLength(0); + _vieDeliverEvent.Set(); + _captureCritsect.Leave(); + DeliverCodedFrame(_deliverFrame); + } + _deliverCritsect.Leave(); + if (_currentBrightnessLevel != _reportedBrightnessLevel) + { + CriticalSectionScoped cs(_observerCritsect); + if (_observer) + { + _observer->BrightnessAlarm(_id, _currentBrightnessLevel); + _reportedBrightnessLevel = _currentBrightnessLevel; + } + } + } + // We're done! + return true; +} + +void ViECapturer::DeliverI420Frame(VideoFrame& videoFrame) +{ + // Apply image enhancement and effect filter + if (_deflickerFrameStats) + { + if (_imageProcModule->GetFrameStats(*_deflickerFrameStats, videoFrame) == 0) + { + _imageProcModule->Deflickering(videoFrame, *_deflickerFrameStats); + } else + { + WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s: could not get frame stats for captured frame", __FUNCTION__); + } + } + if (_denoisingEnabled) + { + _imageProcModule->Denoising(videoFrame); + } + if (_brightnessFrameStats) + { + if (_imageProcModule->GetFrameStats (*_brightnessFrameStats, videoFrame) == 0) + { + WebRtc_Word32 brightness = _imageProcModule->BrightnessDetection( + videoFrame, + *_brightnessFrameStats); + switch (brightness) + { + case VideoProcessingModule::kNoWarning: + _currentBrightnessLevel = Normal; + break; + case VideoProcessingModule::kDarkWarning: + _currentBrightnessLevel = Dark; + break; + case VideoProcessingModule::kBrightWarning: + _currentBrightnessLevel = Bright; + break; + default: + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, + ViEId(_engineId, _captureId), + "%s: Brightness detection failed", __FUNCTION__); + } + } + } + if (_effectFilter) + { + _effectFilter->Transform(videoFrame.Length(), videoFrame.Buffer(), + videoFrame.TimeStamp(), videoFrame.Width(), + videoFrame.Height()); + } + // Deliver the captured frame to all observers (channels,renderer or file) + ViEFrameProviderBase::DeliverFrame(videoFrame); +} + +void ViECapturer::DeliverCodedFrame(VideoFrame& videoFrame) +{ + if (_encodeCompleteCallback) + { + EncodedImage encodedImage(videoFrame.Buffer(), + videoFrame.Length(), + videoFrame.Size()); + encodedImage._timeStamp = 90*(WebRtc_UWord32) videoFrame.RenderTimeMs(); + _encodeCompleteCallback->Encoded(encodedImage); + } + + if (NumberOfRegistersFrameCallbacks() > 0 && _decoderInitialized) + { + videoFrame.Swap(_decodeBuffer.payloadData, _decodeBuffer.bufferSize, + _decodeBuffer.payloadSize); + _decodeBuffer.encodedHeight = videoFrame.Height(); + _decodeBuffer.encodedWidth = videoFrame.Width(); + _decodeBuffer.renderTimeMs = videoFrame.RenderTimeMs(); + _decodeBuffer.timeStamp = 90*(WebRtc_UWord32) videoFrame.RenderTimeMs(); + _decodeBuffer.payloadType = _codec.plType; + _vcm->DecodeFromStorage(_decodeBuffer); + } + +} +/* + * DeregisterFrameCallback - Overrides ViEFrameProvider + */ +int ViECapturer::DeregisterFrameCallback(const ViEFrameCallback* callbackObject) +{ + + _providerCritSect.Enter(); + if (callbackObject == _vieEncoder) //Don't use this camera as encoder anymore. Need to tell the ViEEncoder. + { + ViEEncoder* vieEncoder = NULL; + vieEncoder = _vieEncoder; + _vieEncoder = NULL; + _providerCritSect.Leave(); + _deliverCritsect.Enter(); //Need to take this here in order to avoid deadlock with VCM. The reason is that VCM will call ::Release and a deadlock can occure. + vieEncoder->DeRegisterExternalEncoder(_codec.plType); + _deliverCritsect.Leave(); + return 0; + } + _providerCritSect.Leave(); + return ViEFrameProviderBase::DeregisterFrameCallback(callbackObject); +} + +/* + * IsFrameCallbackRegistered - Overrides ViEFrameProvider + */ +bool ViECapturer::IsFrameCallbackRegistered(const ViEFrameCallback* callbackObject) +{ + CriticalSectionScoped cs(_providerCritSect); + if (callbackObject == _vieEncoder) + { + return true; + } + return ViEFrameProviderBase::IsFrameCallbackRegistered(callbackObject); + +} + +// ---------------------------------------------------------------------------- +// External encoder using attached capture device. +// Implements VideoEncoderInterface +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViECapturer::PreEncodeToViEEncoder(const VideoCodec& codec, + ViEEncoder& vieEncoder, + WebRtc_Word32 vieEncoderId) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s(captureDeviceId: %d)", __FUNCTION__, _captureId); + { + if (_vieEncoder && &vieEncoder != _vieEncoder) + { + WEBRTC_TRACE( webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s(captureDeviceId: %d Capture device already encoding)", + __FUNCTION__, _captureId); + return -1; + } + } + CriticalSectionScoped cs(_encodingCritsect); + VideoCaptureModule::VideoCaptureEncodeInterface* captureEncoder = + _captureModule->GetEncodeInterface(codec); + if (!captureEncoder) + return -1; // Encoding not supported? + + _captureEncoder = captureEncoder; + // Create VCM module used for decoding frames if needed. + if (!_vcm) + { + _vcm = VideoCodingModule::Create(_captureId); + } + + if (vieEncoder.RegisterExternalEncoder(this, codec.plType) != 0) + { + return -1; + } + if (vieEncoder.SetEncoder(codec) != 0) + { + vieEncoder.DeRegisterExternalEncoder(codec.plType); + return -1; + } + // Make sure the encoder is not an I420 observer. + ViEFrameProviderBase::DeregisterFrameCallback(&vieEncoder); + _vieEncoder = &vieEncoder; // Store the vieEncoder that is using this capture device. + _vieEncoderId = vieEncoderId; + memcpy(&_codec, &codec, sizeof(webrtc::VideoCodec)); + return 0; +} + +bool ViECapturer::EncoderActive() +{ + return _vieEncoder != NULL; +} + +/* + * CaptureCapabilityFixed + * Returns true if width, height and framerate was specified when the Start() was called. + */ +bool ViECapturer::CaptureCapabilityFixed() +{ + return _requestedCapability.width != 0 && _requestedCapability.height != 0 + && _requestedCapability.maxFPS != 0; +} + +// ---------------------------------------------------------------------------- +// Implements VideoEncoder +// +// ---------------------------------------------------------------------------- +WebRtc_Word32 ViECapturer::Version(WebRtc_Word8 *version, WebRtc_Word32 length) const +{ + return 0; +} + +WebRtc_Word32 ViECapturer::InitEncode(const VideoCodec* codecSettings, + WebRtc_Word32 numberOfCores, + WebRtc_UWord32 maxPayloadSize) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s(captureDeviceId: %d)", __FUNCTION__, _captureId); + + CriticalSectionScoped cs(_encodingCritsect); + if (!_captureEncoder || !codecSettings) + return WEBRTC_VIDEO_CODEC_ERROR; + + if (_vcm) // Initialize VCM to be able to decode frames if needed. + { + if (_vcm->InitializeReceiver() == 0) + { + if (_vcm->RegisterReceiveCallback(this) == 0) + { + if (_vcm->RegisterReceiveCodec(codecSettings, numberOfCores, + false) == 0) + { + _decoderInitialized = true; + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s(captureDeviceId: %d) VCM Decoder initialized", + __FUNCTION__, _captureId); + } + } + } + } + return _captureEncoder->ConfigureEncoder(*codecSettings, maxPayloadSize); +} + +/* + * Encode + * Orders the Capture device to create a certain frame type. + */ +WebRtc_Word32 ViECapturer::Encode(const RawImage& inputImage, + const void* codecSpecificInfo, /*= NULL,*/ + VideoFrameType frameType /*= kDeltaFrame*/) +{ + + CriticalSectionScoped cs(_encodingCritsect); + + if (!_captureEncoder) + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + + if (frameType == kKeyFrame) + return _captureEncoder->EncodeFrameType(kVideoFrameKey); + + if (frameType == kSkipFrame) + return _captureEncoder->EncodeFrameType(kFrameEmpty); + + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + +} + +WebRtc_Word32 ViECapturer::RegisterEncodeCompleteCallback( EncodedImageCallback* callback) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s(captureDeviceId: %d)", __FUNCTION__, _captureId); + + CriticalSectionScoped cs(_deliverCritsect); + if (!_captureEncoder) + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + + _encodeCompleteCallback = callback; + return 0; + +} +WebRtc_Word32 ViECapturer::Release() +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s(captureDeviceId: %d)", __FUNCTION__, _captureId); + + { + CriticalSectionScoped cs(_deliverCritsect); + _encodeCompleteCallback = NULL; + } + + { + CriticalSectionScoped cs(_encodingCritsect); + + _decoderInitialized = false; + _codec.codecType = kVideoCodecUnknown; + _captureEncoder->ConfigureEncoder(_codec, 0); // Reset the camera to output I420. + + if (_vieEncoder) // Need to add the encoder as an observer of I420. + { + ViEFrameProviderBase::RegisterFrameCallback(_vieEncoderId, + _vieEncoder); + } + _vieEncoder = NULL; + } + return 0; +} + +/* + * Reset + * Should reset the capture device to the state it was in after the InitEncode function. + * Current implementation do nothing. + */ +WebRtc_Word32 ViECapturer::Reset() +{ + + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s(captureDeviceId: %d)", __FUNCTION__, _captureId); + return 0; + +} +WebRtc_Word32 ViECapturer::SetPacketLoss(WebRtc_UWord32 packetLoss) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s(captureDeviceId: %d)", __FUNCTION__, _captureId); + + CriticalSectionScoped cs(_encodingCritsect); + if (!_captureEncoder) + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + + return _captureEncoder->SetPacketLoss(packetLoss); +} + +WebRtc_Word32 ViECapturer::SetRates(WebRtc_UWord32 newBitRate, + WebRtc_UWord32 frameRate) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s(captureDeviceId: %d)", __FUNCTION__, _captureId); + + CriticalSectionScoped cs(_encodingCritsect); + if (!_captureEncoder) + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + + return _captureEncoder->SetRates(newBitRate, frameRate); +} + +/* + * FrameToRender - implements VCMReceiveCallback. + * (VCM decode callback) Used in order to be able to provide I420 frames to renderer etc. + */ + +WebRtc_Word32 ViECapturer::FrameToRender(VideoFrame& videoFrame) +{ + _deliverCritsect.Enter(); + DeliverI420Frame(videoFrame); + _deliverCritsect.Leave(); + return 0; +} + +/******************************************************************************/ + +// +// Statistics Observer functions +// +WebRtc_Word32 ViECapturer::RegisterObserver(ViECaptureObserver& observer) +{ + if (_observer) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, + _captureId), + "%s Observer already registered", __FUNCTION__, + _captureId); + return -1; + } + if (_captureModule->RegisterCaptureCallback(*this) != 0) + { + return -1; + } + _captureModule->EnableFrameRateCallback(true); + _captureModule->EnableNoPictureAlarm(true); + _observer = &observer; + return 0; +} + +WebRtc_Word32 ViECapturer::DeRegisterObserver() +{ + CriticalSectionScoped cs(_observerCritsect); + if (!_observer) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _captureId), + "%s No observer registered", __FUNCTION__, _captureId); + return -1; + } + _captureModule->EnableFrameRateCallback(false); + _captureModule->EnableNoPictureAlarm(false); + _captureModule->DeRegisterCaptureCallback(); + _observer = NULL; + return 0; + +} +bool ViECapturer::IsObserverRegistered() +{ + CriticalSectionScoped cs(_observerCritsect); + return _observer != NULL; +} +// ---------------------------------------------------------------------------- +// Implements VideoCaptureFeedBack +// +// ---------------------------------------------------------------------------- +void ViECapturer::OnCaptureFrameRate(const WebRtc_Word32 id, + const WebRtc_UWord32 frameRate) +{ + WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, + ViEId(_engineId, _captureId), "OnCaptureFrameRate %d", + frameRate); + + CriticalSectionScoped cs(_observerCritsect); + _observer->CapturedFrameRate(_id, (WebRtc_UWord8) frameRate); +} + +void ViECapturer::OnNoPictureAlarm(const WebRtc_Word32 id, + const VideoCaptureAlarm alarm) +{ + WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, + ViEId(_engineId, _captureId), "OnNoPictureAlarm %d", alarm); + + CriticalSectionScoped cs(_observerCritsect); + CaptureAlarm vieAlarm = (alarm == Raised) ? AlarmRaised : AlarmCleared; + _observer->NoPictureAlarm(id, vieAlarm); +} +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViECapturer::SetCaptureDeviceImage(const VideoFrame& captureDeviceImage) +{ + return _captureModule->StartSendImage(captureDeviceImage, 10); +} + +} // namespace webrtc diff --git a/video_engine/main/source/vie_capturer.h b/video_engine/main/source/vie_capturer.h new file mode 100644 index 0000000000..9e67f505a3 --- /dev/null +++ b/video_engine/main/source/vie_capturer.h @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_capturer.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_CAPTURER_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_CAPTURER_H_ + +// Defines +#include "engine_configurations.h" +#include "vie_defines.h" +#include "typedefs.h" + +#include "video_capture.h" +#include "video_processing.h" +#include "vie_frame_provider_base.h" +#include "video_codec_interface.h" +#include "video_coding.h" +#include "vie_capture.h" +#include "common_types.h" + +// Forward declarations +struct ViEPicture; + +namespace webrtc { +class CriticalSectionWrapper; +class EventWrapper; +class ThreadWrapper; +class ViEEffectFilter; +class ViEEncoder; +class ProcessThread; + +class ViECapturer: public ViEFrameProviderBase, + public ViEExternalCapture, // External capture + protected VideoCaptureDataCallback, + protected VideoEncoder, + protected VCMReceiveCallback, + protected VideoCaptureFeedBack +{ +public: + static ViECapturer* CreateViECapture(int captureId, int, + VideoCaptureModule& captureModule, + ProcessThread& moduleProcessThread); + + static ViECapturer* CreateViECapture(int captureId, int engineId, + const WebRtc_UWord8* deviceUniqueIdUTF8, + WebRtc_UWord32 deviceUniqueIdUTF8Length, + ProcessThread& moduleProcessThread); + ~ViECapturer(); + + //Override ViEFrameProviderBase + int FrameCallbackChanged(); + virtual int DeregisterFrameCallback(const ViEFrameCallback* callbackObject); + bool IsFrameCallbackRegistered(const ViEFrameCallback* callbackObject); + + // Implements ExternalCapture + virtual int IncomingFrame(unsigned char* videoFrame, + unsigned int videoFrameLength, + unsigned short width, unsigned short height, + RawVideoType videoType, + unsigned long long captureTime = 0); + + // Use this capture device as encoder. Returns 0 if the codec is supported by this capture device. + virtual WebRtc_Word32 PreEncodeToViEEncoder(const VideoCodec& codec, + ViEEncoder& vieEncoder, + WebRtc_Word32 vieEncoderId); + + // Start/Stop + WebRtc_Word32 Start(const CaptureCapability captureCapability = + CaptureCapability()); + WebRtc_Word32 Stop(); + bool Started(); + + WebRtc_Word32 SetCaptureDelay(WebRtc_Word32 delayMS); + WebRtc_Word32 SetRotateCapturedFrames(const RotateCapturedFrame rotation); + + // Effect filter + WebRtc_Word32 RegisterEffectFilter(ViEEffectFilter* effectFilter); + WebRtc_Word32 EnableDenoising(bool enable); + WebRtc_Word32 EnableDeflickering(bool enable); + WebRtc_Word32 EnableBrightnessAlarm(bool enable); + + // Statistic observer + WebRtc_Word32 RegisterObserver(ViECaptureObserver& observer); + WebRtc_Word32 DeRegisterObserver(); + bool IsObserverRegistered(); + + //Information + const WebRtc_UWord8* CurrentDeviceName() const; + + // set device images + WebRtc_Word32 SetCaptureDeviceImage(const VideoFrame& captureDeviceImage); + +protected: + ViECapturer(int captureId, int engineId, + ProcessThread& moduleProcessThread); + + WebRtc_Word32 Init(VideoCaptureModule& captureModule); + WebRtc_Word32 Init(const WebRtc_UWord8* deviceUniqueIdUTF8, + const WebRtc_UWord32 deviceUniqueIdUTF8Length); + + // Implements VideoCaptureDataCallback + virtual void OnIncomingCapturedFrame(const WebRtc_Word32 id, + VideoFrame& videoFrame, + VideoCodecType codecType); + virtual void OnCaptureDelayChanged(const WebRtc_Word32 id, + const WebRtc_Word32 delay); + bool EncoderActive(); + bool CaptureCapabilityFixed(); // Returns true if the capture capability has been set in the StartCapture function and may not be changed. + WebRtc_Word32 IncImageProcRefCount(); + WebRtc_Word32 DecImageProcRefCount(); + + // Implements VideoEncoder + virtual WebRtc_Word32 Version(WebRtc_Word8 *version, WebRtc_Word32 length) const; + virtual WebRtc_Word32 InitEncode(const VideoCodec* codecSettings, + WebRtc_Word32 numberOfCores, + WebRtc_UWord32 maxPayloadSize); + virtual WebRtc_Word32 Encode(const RawImage& inputImage, + const void* codecSpecificInfo = NULL, + VideoFrameType frameType = kDeltaFrame); + virtual WebRtc_Word32 RegisterEncodeCompleteCallback( + EncodedImageCallback* callback); + virtual WebRtc_Word32 Release(); + virtual WebRtc_Word32 Reset(); + virtual WebRtc_Word32 SetPacketLoss(WebRtc_UWord32 packetLoss); + virtual WebRtc_Word32 SetRates(WebRtc_UWord32 newBitRate, + WebRtc_UWord32 frameRate); + + // Implements VCMReceiveCallback + virtual WebRtc_Word32 FrameToRender(VideoFrame& videoFrame); + // Implements VideoCaptureFeedBack + virtual void OnCaptureFrameRate(const WebRtc_Word32 id, + const WebRtc_UWord32 frameRate); + virtual void OnNoPictureAlarm(const WebRtc_Word32 id, + const VideoCaptureAlarm alarm); + + // Thread functions for deliver captured frames to receivers + static bool ViECaptureThreadFunction(void* obj); + bool ViECaptureProcess(); + + void DeliverI420Frame(VideoFrame& videoFrame); + void DeliverCodedFrame(VideoFrame& videoFrame); + +private: + enum {kThreadWaitTimeMs = 100}; + + CriticalSectionWrapper& _captureCritsect; // Never take this one before deliverCritsect! + CriticalSectionWrapper& _deliverCritsect; + VideoCaptureModule* _captureModule; + bool _useExternalModule; + VideoCaptureExternal* _externalCaptureModule; + ProcessThread& _moduleProcessThread; + const int _captureId; + + // Capture thread + ThreadWrapper& _vieCaptureThread; + EventWrapper& _vieCaptureEvent; + EventWrapper& _vieDeliverEvent; + + VideoFrame _capturedFrame; + VideoFrame _deliverFrame; + VideoFrame _encodedFrame; + + // Image processing + ViEEffectFilter* _effectFilter; + VideoProcessingModule* _imageProcModule; + int _imageProcModuleRefCounter; + VideoProcessingModule::FrameStats* _deflickerFrameStats; + VideoProcessingModule::FrameStats* _brightnessFrameStats; + Brightness _currentBrightnessLevel; + Brightness _reportedBrightnessLevel; + bool _denoisingEnabled; + + //Statistic observer + CriticalSectionWrapper& _observerCritsect; + ViECaptureObserver* _observer; + + // Encoding using encoding capable cameras + CriticalSectionWrapper& _encodingCritsect; + VideoCaptureModule::VideoCaptureEncodeInterface* _captureEncoder; + EncodedImageCallback* _encodeCompleteCallback; + VideoCodec _codec; + ViEEncoder* _vieEncoder; //ViEEncoder we are encoding for. + WebRtc_Word32 _vieEncoderId; //ViEEncoder id we are encoding for. + VideoCodingModule* _vcm; // Used for decoding preencoded frames + EncodedVideoData _decodeBuffer; // Used for decoding preencoded frames + bool _decoderInitialized; + CaptureCapability _requestedCapability; + + VideoFrame _captureDeviceImage; +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_CAPTURER_H_ diff --git a/video_engine/main/source/vie_channel.cc b/video_engine/main/source/vie_channel.cc new file mode 100644 index 0000000000..aeaf9ffe6b --- /dev/null +++ b/video_engine/main/source/vie_channel.cc @@ -0,0 +1,3222 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * ViEChannel.cpp + */ + +#include "vie_channel.h" +#include "vie_defines.h" + +#include "critical_section_wrapper.h" +#include "rtp_rtcp.h" +#include "udp_transport.h" +#include "video_coding.h" +#include "video_processing.h" +#include "video_render_defines.h" +#ifdef WEBRTC_SRTP +#include "SrtpModule.h" +#endif +#include "process_thread.h" +#include "trace.h" +#include "thread_wrapper.h" +#include "vie_codec.h" +#include "vie_errors.h" +#include "vie_image_process.h" +#include "vie_rtp_rtcp.h" +#include "vie_receiver.h" +#include "vie_sender.h" +#include "vie_sync_module.h" + +namespace webrtc +{ + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + +ViEChannel::ViEChannel(WebRtc_Word32 channelId, WebRtc_Word32 engineId, + WebRtc_UWord32 numberOfCores, + ProcessThread& moduleProcessThread) : + ViEFrameProviderBase(channelId, engineId), + _channelId(channelId), + _engineId(engineId), + _numberOfCores(numberOfCores), + _numSocketThreads(kViESocketThreads), + _callbackCritsect(*CriticalSectionWrapper::CreateCriticalSection()), + _dataCritsect(*CriticalSectionWrapper::CreateCriticalSection()), + _rtpRtcp(*RtpRtcp::CreateRtpRtcp( + ViEModuleId(engineId, channelId), false)), +#ifndef WEBRTC_EXTERNAL_TRANSPORT + _socketTransport( + *UdpTransport::Create( + ViEModuleId(engineId, channelId), _numSocketThreads)), +#endif + _vcm(*VideoCodingModule::Create( + ViEModuleId(engineId, channelId))), + _vieReceiver(*(new ViEReceiver(engineId, channelId, _rtpRtcp, _vcm))), + _vieSender(*(new ViESender(engineId, channelId, _rtpRtcp))), + _vieSync(*(new ViESyncModule(ViEId(engineId, channelId), _vcm, + _rtpRtcp))), + _moduleProcessThread(moduleProcessThread), + _codecObserver(NULL), + _doKeyFrameCallbackRequest(false), + _rtpObserver(NULL), + _rtcpObserver(NULL), + _networkObserver(NULL), + _rtpPacketTimeout(false), + _usingPacketSpread(false), + _ptrExternalTransport(NULL), + _decoderReset(true), + _waitForKeyFrame(false), + _ptrDecodeThread(false), + _ptrSrtpModuleEncryption(NULL), + _ptrSrtpModuleDecryption(NULL), + _ptrExternalEncryption(NULL), + _effectFilter(NULL), + _colorEnhancement(true), + _vcmRTTReported(TickTime::Now()), + _fileRecorder(channelId) +{ + WEBRTC_TRACE( + webrtc::kTraceMemory, webrtc::kTraceVideo, ViEId(engineId, channelId), + "ViEChannel::ViEChannel(channelId: %d, engineId: %d) - Constructor", + channelId, engineId); +} + +WebRtc_Word32 ViEChannel::Init() +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: channelId: %d, engineId: %d)", __FUNCTION__, _channelId, + _engineId); + // RTP/RTCP initialization + if (_rtpRtcp.InitSender() != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: RTP::InitSender failure", + __FUNCTION__); + return -1; + } + if (_rtpRtcp.SetSendingMediaStatus(false) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: RTP::SetSendingMediaStatus failure", __FUNCTION__); + return -1; + } + if (_rtpRtcp.InitReceiver() != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: RTP::InitReceiver failure", __FUNCTION__); + return -1; + } + if (_rtpRtcp.RegisterIncomingDataCallback((RtpData*) &_vieReceiver) + != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: RTP::RegisterIncomingDataCallback failure", + __FUNCTION__); + return -1; + } + if (_rtpRtcp.RegisterSendTransport((Transport*) &_vieSender) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: RTP::RegisterSendTransport failure", __FUNCTION__); + return -1; + } + if (_moduleProcessThread.RegisterModule(&_rtpRtcp) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: RTP::RegisterModule failure", __FUNCTION__); + return -1; + } + if (_rtpRtcp.SetKeyFrameRequestMethod(kKeyFrameReqFirRtp) != 0) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, + ViEId(_engineId,_channelId), + "%s: RTP::SetKeyFrameRequestMethod failure", __FUNCTION__); + } + if (_rtpRtcp.SetRTCPStatus(kRtcpCompound) != 0) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: RTP::SetRTCPStatus failure", __FUNCTION__); + } + if (_rtpRtcp.RegisterIncomingRTPCallback(this) != 0) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: RTP::RegisterIncomingRTPCallback failure", + __FUNCTION__); + return -1; + } + + if (_rtpRtcp.RegisterIncomingRTCPCallback(this) != 0) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: RTP::RegisterIncomingRTCPCallback failure", + __FUNCTION__); + return -1; + } + + // VCM initialization + if (_vcm.InitializeReceiver() != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: VCM::InitializeReceiver failure", __FUNCTION__); + return -1; + } + if (_vcm.RegisterReceiveCallback(this) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: VCM::RegisterReceiveCallback failure", __FUNCTION__); + return -1; + } + if (_vcm.RegisterFrameTypeCallback(this) != 0) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: VCM::RegisterFrameTypeCallback failure", __FUNCTION__); + } + if (_vcm.RegisterReceiveStatisticsCallback(this) != 0) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: VCM::RegisterReceiveStatisticsCallback failure", + __FUNCTION__); + } + if (_vcm.SetRenderDelay(kViEDefaultRenderDelayMs) != 0) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: VCM::SetRenderDelay failure", __FUNCTION__); + } + if (_moduleProcessThread.RegisterModule(&_vcm) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: VCM::RegisterModule(vcm) failure", __FUNCTION__); + return -1; + } +#ifdef VIDEOCODEC_VP8 + VideoCodec videoCodec; + if (_vcm.Codec(kVideoCodecVP8, &videoCodec) == VCM_OK) + { + _rtpRtcp.RegisterSendPayload(videoCodec.plName, videoCodec.plType); + _rtpRtcp.RegisterReceivePayload(videoCodec.plName, videoCodec.plType); + _vcm.RegisterReceiveCodec(&videoCodec, _numberOfCores); + _vcm.RegisterSendCodec(&videoCodec, _numberOfCores, + _rtpRtcp.MaxDataPayloadLength()); + } + else + { + assert(false); + } +#endif + + return 0; +} + +// ---------------------------------------------------------------------------- +// Destructor +// ---------------------------------------------------------------------------- + +ViEChannel::~ViEChannel() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "ViEChannel Destructor, channelId: %d, engineId: %d", + _channelId, _engineId); + + // Make sure we don't get more callbacks from the RTP module. + _rtpRtcp.RegisterIncomingRTPCallback(NULL); + _rtpRtcp.RegisterSendTransport(NULL); +#ifndef WEBRTC_EXTERNAL_TRANSPORT + _socketTransport.StopReceiving(); +#endif + _moduleProcessThread.DeRegisterModule(&_rtpRtcp); + _moduleProcessThread.DeRegisterModule(&_vcm); + _moduleProcessThread.DeRegisterModule(&_vieSync); + + if (_ptrDecodeThread) + { + StopDecodeThread(); + } + + delete &_vieReceiver; + delete &_vieSender; + delete &_vieSync; + + delete &_callbackCritsect; + delete &_dataCritsect; + + // Release modules + + RtpRtcp::DestroyRtpRtcp(&_rtpRtcp); +#ifndef WEBRTC_EXTERNAL_TRANSPORT + UdpTransport::Destroy( + &_socketTransport); +#endif + VideoCodingModule::Destroy(&_vcm); +} + +// ============================================================================ +// Codec +// ============================================================================ + +// ---------------------------------------------------------------------------- +// SetSendCodec +// +// videoCodec: encoder settings +// newStream: the encoder type has changed and we should start a new RTP stream +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::SetSendCodec(const VideoCodec& videoCodec, + bool newStream) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: codecType: %d", __FUNCTION__, videoCodec.codecType); + + if (videoCodec.codecType == kVideoCodecRED || + videoCodec.codecType == kVideoCodecULPFEC) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: codecType: %d is not a valid send codec.", + __FUNCTION__, videoCodec.codecType); + return -1; + } + + // Update the RTP module with the settigns + // Stop and Start the RTP module -> trigger new SSRC + bool restartRtp = false; + if (_rtpRtcp.Sending() && newStream) + { + restartRtp = true; + _rtpRtcp.SetSendingStatus(false); + } + + if (_rtpRtcp.SetSendBitrate(videoCodec.startBitrate * 1000, + videoCodec.minBitrate, videoCodec.maxBitrate) + != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: could not set send bitrates", __FUNCTION__); + return -1; + } + /* TODO Enable this if H264 is available. + * This sets the wanted packetization mode. + if(videoCodec.plType==kVideoCodecH264) + { + if (videoCodec.codecSpecific.H264.packetization == kH264SingleMode) + { + _rtpRtcp.SetH264PacketizationMode (H264_SINGLE_NAL_MODE); + } + else + { + _rtpRtcp.SetH264PacketizationMode(H264_NON_INTERLEAVED_MODE); + } + if (videoCodec.codecSpecific.H264.configParametersSize > 0) + { + _rtpRtcp.SetH264SendModeNALU_PPS_SPS(true); + } + }*/ + + // Don't log this error, no way to chek in advance if this plType is + // registered or not... + _rtpRtcp.DeRegisterSendPayload(videoCodec.plType); + if (_rtpRtcp.RegisterSendPayload(videoCodec.plName, videoCodec.plType) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: could not register payload type", __FUNCTION__); + return -1; + } + + if (restartRtp) + { + _rtpRtcp.SetSendingStatus(true); + } + return 0; +} + +// ---------------------------------------------------------------------------- +// SetReceiveCodec +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::SetReceiveCodec(const VideoCodec& videoCodec) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + _rtpRtcp.DeRegisterReceivePayload(videoCodec.plType); + if (_rtpRtcp.RegisterReceivePayload(videoCodec.plName, videoCodec.plType) + != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not register receive payload type", __FUNCTION__); + return -1; + } + + if (videoCodec.codecType != kVideoCodecRED && + videoCodec.codecType != kVideoCodecULPFEC) + { //Register codec type with VCM. But do not register RED or ULPFEC + if (_vcm.RegisterReceiveCodec(&videoCodec, _numberOfCores, + _waitForKeyFrame) != VCM_OK) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not register decoder", __FUNCTION__); + return -1; + } + } + return 0; +} + +// ---------------------------------------------------------------------------- +// GetReceiveCodec +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::GetReceiveCodec(VideoCodec& videoCodec) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + if (_vcm.ReceiveCodec(&videoCodec) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not get receive codec", __FUNCTION__); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// RegisterCodecObserver +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::RegisterCodecObserver(ViEDecoderObserver* observer) +{ + CriticalSectionScoped cs(_callbackCritsect); + if (observer) + { + if (_codecObserver) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: alread added", + __FUNCTION__); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: observer added", __FUNCTION__); + _codecObserver = observer; + } + else + { + if (!_codecObserver) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: no observer added", + __FUNCTION__); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: observer removed", __FUNCTION__); + _codecObserver = NULL; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// RegisterExternalDecoder +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::RegisterExternalDecoder( + const WebRtc_UWord8 plType, VideoDecoder* decoder, + bool decoderRender, //Decoder also render + WebRtc_Word32 renderDelay) // Decode and render delay +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + WebRtc_Word32 result = 0; + result = _vcm.RegisterExternalDecoder(decoder, plType, decoderRender); + if (decoderRender && result == 0) + { + // Let VCM know how long before the actual render time the decoder needs + // to get a frame for decoding. + result = _vcm.SetRenderDelay(renderDelay); + } + return result; +} + +// ---------------------------------------------------------------------------- +// DeRegisterExternalDecoder +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::DeRegisterExternalDecoder(const WebRtc_UWord8 plType) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s plType", __FUNCTION__, plType); + + VideoCodec currentReceiveCodec; + WebRtc_Word32 result = 0; + result = _vcm.ReceiveCodec(¤tReceiveCodec); + if (_vcm.RegisterExternalDecoder(NULL, plType, false) != VCM_OK) + { + return -1; + } + + if (result == 0 && currentReceiveCodec.plType == plType) + { + result = _vcm.RegisterReceiveCodec(¤tReceiveCodec, + _numberOfCores, _waitForKeyFrame); + } + + return result; +} + +// ---------------------------------------------------------------------------- +// ReceiveCodecStatistics +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::ReceiveCodecStatistics(WebRtc_UWord32& numKeyFrames, + WebRtc_UWord32& numDeltaFrames) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + VCMFrameCount receivedFrames; + if (_vcm.ReceivedFrameCount(receivedFrames) != VCM_OK) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not get received frame information", __FUNCTION__); + return -1; + } + numKeyFrames = receivedFrames.numKeyFrames; + numDeltaFrames = receivedFrames.numDeltaFrames; + return 0; +} + +// ---------------------------------------------------------------------------- +// WaitForKeyFrame +// +// Only affects calls to SetReceiveCodec done after this call. +// Default = false +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::WaitForKeyFrame(bool wait) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s(wait: %d)", __FUNCTION__, wait); + + _waitForKeyFrame = wait; + return 0; +} + +// ---------------------------------------------------------------------------- +// SetSignalPacketLossStatus +// +// If enabled, a key frame request will be sent as soon as there are lost +// packets. If onlyKeyFrames are set, requests are only sent for loss in key +// frames. +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::SetSignalPacketLossStatus(bool enable, + bool onlyKeyFrames) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s(enable: %d)", __FUNCTION__, enable); + + if (enable) + { + if (onlyKeyFrames) + { + _vcm.SetVideoProtection(kProtectionKeyOnLoss, false); + if (_vcm.SetVideoProtection(kProtectionKeyOnKeyLoss, true) + != VCM_OK) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, + _channelId), + "%s failed %d", __FUNCTION__, enable); + return -1; + } + } + else + { + _vcm.SetVideoProtection(kProtectionKeyOnKeyLoss, false); + if (_vcm.SetVideoProtection(kProtectionKeyOnLoss, true) + != VCM_OK) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, + _channelId), + "%s failed %d", __FUNCTION__, enable); + return -1; + } + } + } + else + { + _vcm.SetVideoProtection(kProtectionKeyOnLoss, false); + _vcm.SetVideoProtection(kProtectionKeyOnKeyLoss, false); + } + return 0; +} + +// ============================================================================ +// RTP/RTCP +// ============================================================================ + +// ---------------------------------------------------------------------------- +// SetRTCPMode +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::SetRTCPMode(const RTCPMethod rtcpMode) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: %d", __FUNCTION__, rtcpMode); + + return _rtpRtcp.SetRTCPStatus(rtcpMode); +} + +// ---------------------------------------------------------------------------- +// GetRTCPMode +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::GetRTCPMode(RTCPMethod& rtcpMode) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + rtcpMode = _rtpRtcp.RTCP(); + return 0; +} + +// ---------------------------------------------------------------------------- +// EnableNACKStatus +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::SetNACKStatus(const bool enable) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s(enable: %d)", __FUNCTION__, enable); + + // Update the decoding VCM + if (_vcm.SetVideoProtection(kProtectionNack, enable) != VCM_OK) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not set VCM NACK protection: %d", __FUNCTION__, + enable); + return -1; + } + if (enable) + { + // Disable possible FEC + SetFECStatus(false, 0, 0); + + // Turn on NACK, + NACKMethod nackMethod = kNackRtcp; + if (_rtpRtcp.RTCP() == kRtcpOff) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not enable NACK, RTPC not on ", __FUNCTION__); + return -1; + } + if (_rtpRtcp.SetNACKStatus(nackMethod) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not set NACK method %d", __FUNCTION__, + nackMethod); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: Using NACK method %d", __FUNCTION__, nackMethod); + _rtpRtcp.SetStorePacketsStatus(true, kNackHistorySize); + _vcm.RegisterPacketRequestCallback(this); + } + else + { + _rtpRtcp.SetStorePacketsStatus(false); + _vcm.RegisterPacketRequestCallback(NULL); + if (_rtpRtcp.SetNACKStatus(kNackOff) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not turn off NACK", __FUNCTION__); + return -1; + } + } + return 0; +} + +// ---------------------------------------------------------------------------- +// SetFECStatus +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::SetFECStatus(const bool enable, + const unsigned char payloadTypeRED, + const unsigned char payloadTypeFEC) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s(enable: %d, payloadTypeRED: %u, payloadTypeFEC: %u)", + __FUNCTION__, enable, payloadTypeRED, payloadTypeFEC); + + // Disable possible NACK + if (enable) + { + SetNACKStatus(false); + } + + if (_rtpRtcp.SetGenericFECStatus(enable, payloadTypeRED, payloadTypeFEC) + != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not change FEC status to %d", __FUNCTION__, + enable); + return -1; + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// KeyFrameRequestMethod +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::SetKeyFrameRequestMethod( + const KeyFrameRequestMethod method) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: %d", __FUNCTION__, method); + + return _rtpRtcp.SetKeyFrameRequestMethod(method); +} + +// ---------------------------------------------------------------------------- +// EnableTMMBR +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::EnableTMMBR(const bool enable) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: %d", __FUNCTION__, enable); + + return _rtpRtcp.SetTMMBRStatus(enable); +} + +// ---------------------------------------------------------------------------- +// EnableKeyFrameRequestCallback +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::EnableKeyFrameRequestCallback(const bool enable) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: %d", __FUNCTION__, enable); + + CriticalSectionScoped cs(_callbackCritsect); + if (enable && _codecObserver == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: No ViECodecObserver set", __FUNCTION__, enable); + return -1; + } + _doKeyFrameCallbackRequest = enable; + return 0; + +} + +// ---------------------------------------------------------------------------- +// SetSSRC +// +// Sets SSRC for outgoing stream +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::SetSSRC(const WebRtc_UWord32 SSRC) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s(SSRC: %u)", __FUNCTION__, SSRC); + + return _rtpRtcp.SetSSRC(SSRC); +} + +// ---------------------------------------------------------------------------- +// SetSSRC +// +// Gets SSRC for outgoing stream +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::GetLocalSSRC(WebRtc_UWord32& SSRC) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + SSRC = _rtpRtcp.SSRC(); + return 0; +} + +// ---------------------------------------------------------------------------- +// GetRemoteSSRC +// +// Gets SSRC for the incoming stream +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::GetRemoteSSRC(WebRtc_UWord32& SSRC) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + SSRC = _rtpRtcp.RemoteSSRC(); + return 0; +} + +// ---------------------------------------------------------------------------- +// GetRemoteCSRC +// +// Gets the CSRC for the incoming stream +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::GetRemoteCSRC(unsigned int CSRCs[kRtpCsrcSize]) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + WebRtc_UWord32 arrayCSRC[kRtpCsrcSize]; + memset(arrayCSRC, 0, sizeof(arrayCSRC)); + + WebRtc_Word32 numCSRCs = _rtpRtcp.RemoteCSRCs(arrayCSRC); + if (numCSRCs > 0) + { + memcpy(CSRCs, arrayCSRC, numCSRCs * sizeof(WebRtc_UWord32)); + for (int idx = 0; idx < numCSRCs; idx++) + { + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "\tCSRC[%d] = %lu", idx, + CSRCs[idx]); + } + } + else + { + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: CSRC list is empty", __FUNCTION__); + } + return 0; +} + +// ---------------------------------------------------------------------------- +// SetStartSequenceNumber +// +// Sets the starting sequence number, must be called before StartSend. +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::SetStartSequenceNumber(WebRtc_UWord16 sequenceNumber) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + if (_rtpRtcp.Sending()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: already sending", + __FUNCTION__); + return -1; + } + return _rtpRtcp.SetSequenceNumber(sequenceNumber); +} + +// ---------------------------------------------------------------------------- +// SetRTCPCName +// +// Sets the CName for the outgoing stream on the channel +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::SetRTCPCName(const WebRtc_Word8 rtcpCName[]) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + if (_rtpRtcp.Sending()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: already sending", + __FUNCTION__); + return -1; + } + return _rtpRtcp.SetCNAME(rtcpCName); +} + +// ---------------------------------------------------------------------------- +// GetRTCPCName +// +// Gets the CName for the outgoing stream on the channel +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::GetRTCPCName(WebRtc_Word8 rtcpCName[]) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + return _rtpRtcp.CNAME(rtcpCName); +} + +// ---------------------------------------------------------------------------- +// GetRemoteRTCPCName +// +// Gets the CName of the incoming stream +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::GetRemoteRTCPCName(WebRtc_Word8 rtcpCName[]) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + WebRtc_UWord32 remoteSSRC = _rtpRtcp.RemoteSSRC(); + return _rtpRtcp.RemoteCNAME(remoteSSRC, rtcpCName); +} + +// ---------------------------------------------------------------------------- +// RegisterRtpObserver +// +// Registers an RTP observer +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::RegisterRtpObserver(ViERTPObserver* observer) +{ + CriticalSectionScoped cs(_callbackCritsect); + if (observer) + { + if (_rtpObserver) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: observer alread added", __FUNCTION__); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: observer added", __FUNCTION__); + _rtpObserver = observer; + } + else + { + if (!_rtpObserver) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: no observer added", + __FUNCTION__); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: observer removed", __FUNCTION__); + _rtpObserver = NULL; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// RegisterRtcpObserver +// +// Registers an RTPC observer +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::RegisterRtcpObserver(ViERTCPObserver* observer) +{ + CriticalSectionScoped cs(_callbackCritsect); + if (observer) + { + if (_rtcpObserver) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: observer alread added", __FUNCTION__); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: observer added", __FUNCTION__); + _rtcpObserver = observer; + } + else + { + if (!_rtcpObserver) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: no observer added", + __FUNCTION__); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: observer removed", __FUNCTION__); + _rtcpObserver = NULL; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// SendApplicationDefinedRTCPPacket +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::SendApplicationDefinedRTCPPacket( + const WebRtc_UWord8 subType, WebRtc_UWord32 name, const WebRtc_UWord8* data, + WebRtc_UWord16 dataLengthInBytes) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + // Sanity checks + if (!_rtpRtcp.Sending()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: not sending", + __FUNCTION__); + return -1; + } + if (data == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: no input argument", + __FUNCTION__); + return -1; + } + if (dataLengthInBytes % 4 != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: input length error", + __FUNCTION__); + return -1; + } + RTCPMethod rtcpMethod = _rtpRtcp.RTCP(); + if (rtcpMethod == kRtcpOff) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: RTCP not enabled", + __FUNCTION__); + return -1; + } + // Create and send packet + if (_rtpRtcp.SetRTCPApplicationSpecificData(subType, name, data, + dataLengthInBytes) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not send RTCP application data", __FUNCTION__); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// GetSendRtcpStatistics +// +// Gets statistics sent in RTCP packets to remote side +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::GetSendRtcpStatistics(WebRtc_UWord16& fractionLost, + WebRtc_UWord32& cumulativeLost, + WebRtc_UWord32& extendedMax, + WebRtc_UWord32& jitterSamples, + WebRtc_Word32& rttMs) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + WebRtc_UWord32 remoteSSRC = _rtpRtcp.RemoteSSRC(); + + RTCPReportBlock remoteStat; + if (_rtpRtcp.RemoteRTCPStat(remoteSSRC, &remoteStat) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not get remote stats", __FUNCTION__); + return -1; + } + fractionLost = remoteStat.fractionLost; + cumulativeLost = remoteStat.cumulativeLost; + extendedMax = remoteStat.extendedHighSeqNum; + jitterSamples = remoteStat.jitter; + + WebRtc_UWord16 dummy; + WebRtc_UWord16 rtt = 0; + if (_rtpRtcp.RTT(remoteSSRC, &rtt, &dummy, &dummy, &dummy) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: Could not get RTT", + __FUNCTION__); + return -1; + } + rttMs = rtt; + return 0; +} + +// ---------------------------------------------------------------------------- +// GetSendRtcpStatistics +// +// Gets statistics received in RTCP packets from remote side +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::GetReceivedRtcpStatistics( + WebRtc_UWord16& fractionLost, WebRtc_UWord32& cumulativeLost, + WebRtc_UWord32& extendedMax, WebRtc_UWord32& jitterSamples, + WebRtc_Word32& rttMs) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + WebRtc_UWord8 fracLost = 0; + if (_rtpRtcp.StatisticsRTP(&fracLost, &cumulativeLost, &extendedMax, + &jitterSamples) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not get received RTP statistics", __FUNCTION__); + return -1; + } + fractionLost = fracLost; + + WebRtc_UWord32 remoteSSRC = _rtpRtcp.RemoteSSRC(); + WebRtc_UWord16 dummy = 0; + WebRtc_UWord16 rtt = 0; + if (_rtpRtcp.RTT(remoteSSRC, &rtt, &dummy, &dummy, &dummy) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: Could not get RTT", + __FUNCTION__); + return -1; + } + rttMs = rtt; + return 0; +} + +// ---------------------------------------------------------------------------- +// GetRtpStatistics +// +// Gets sent/received packets statistics +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::GetRtpStatistics( + WebRtc_UWord32& bytesSent, WebRtc_UWord32& packetsSent, + WebRtc_UWord32& bytesReceived, WebRtc_UWord32& packetsReceived) const +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + if (_rtpRtcp.DataCountersRTP(&bytesSent, &packetsSent, &bytesReceived, + &packetsReceived) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: Could not get RTT", + __FUNCTION__); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// SetKeepAliveStatus +// +// Enables/disbles RTP keeoalive +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::SetKeepAliveStatus( + const bool enable, const WebRtc_Word8 unknownPayloadType, + const WebRtc_UWord16 deltaTransmitTimeMS) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + if (enable && _rtpRtcp.RTPKeepalive()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: RTP keepalive already enabled", __FUNCTION__); + return -1; + } + else if (!enable && !_rtpRtcp.RTPKeepalive()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: RTP keepalive already disabled", __FUNCTION__); + return -1; + } + + if (_rtpRtcp.SetRTPKeepaliveStatus(enable, unknownPayloadType, + deltaTransmitTimeMS) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not set RTP keepalive status %d", __FUNCTION__, + enable); + if (enable == false && !_rtpRtcp.DefaultModuleRegistered()) + { + // Not sending media and we try to disable keep alive + _rtpRtcp.ResetSendDataCountersRTP(); + _rtpRtcp.SetSendingStatus(false); + } + return -1; + } + + if (enable && !_rtpRtcp.Sending()) + { + // Enable sending to start sending Sender reports instead of receive + // reports + if (_rtpRtcp.SetSendingStatus(true) != 0) + { + _rtpRtcp.SetRTPKeepaliveStatus(false, 0, 0); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not start sending", __FUNCTION__); + return -1; + } + } + else if (!enable && !_rtpRtcp.SendingMedia()) + { + // Not sending media and we're disabling keep alive + _rtpRtcp.ResetSendDataCountersRTP(); + if (_rtpRtcp.SetSendingStatus(false) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not stop sending", __FUNCTION__); + return -1; + } + } + return 0; +} + +// ---------------------------------------------------------------------------- +// GetKeepAliveStatus +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::GetKeepAliveStatus( + bool& enabled, WebRtc_Word8& unknownPayloadType, + WebRtc_UWord16& deltaTransmitTimeMs) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + if (_rtpRtcp.RTPKeepaliveStatus(&enabled, &unknownPayloadType, + &deltaTransmitTimeMs) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not get RTP keepalive status", __FUNCTION__); + return -1; + } + WEBRTC_TRACE( + webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: enabled = %d, unknownPayloadType = %d, deltaTransmitTimeMs = %ul", + __FUNCTION__, enabled, (WebRtc_Word32) unknownPayloadType, + deltaTransmitTimeMs); + + return 0; +} + +// ---------------------------------------------------------------------------- +// StartRTPDump +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::StartRTPDump(const char fileNameUTF8[1024], + RTPDirections direction) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + if (direction != kRtpIncoming && direction != kRtpOutgoing) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: invalid input", + __FUNCTION__); + return -1; + } + RtpDump* rtpDump = NULL; + if (direction == kRtpIncoming) + { + return _vieReceiver.StartRTPDump(fileNameUTF8); + } + else + { + return _vieSender.StartRTPDump(fileNameUTF8); + } +} + +// ---------------------------------------------------------------------------- +// StopRTPDump +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::StopRTPDump(RTPDirections direction) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + if (direction != kRtpIncoming && direction != kRtpOutgoing) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: invalid input", + __FUNCTION__); + return -1; + } + RtpDump* rtpDump = NULL; + if (direction == kRtpIncoming) + { + return _vieReceiver.StopRTPDump(); + } + else + { + return _vieSender.StopRTPDump(); + } +} + +// ============================================================================ +// Network +// ============================================================================ + +// ---------------------------------------------------------------------------- +// SetLocalReceiver +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::SetLocalReceiver(const WebRtc_UWord16 rtpPort, + const WebRtc_UWord16 rtcpPort, + const WebRtc_Word8* ipAddress) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + _callbackCritsect.Enter(); + if (_ptrExternalTransport) + { + _callbackCritsect.Leave(); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: external transport registered", __FUNCTION__); + return -1; + } + _callbackCritsect.Leave(); + +#ifndef WEBRTC_EXTERNAL_TRANSPORT + if (_socketTransport.Receiving()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: already receiving", + __FUNCTION__); + return -1; + } + + const WebRtc_Word8* multicastIpAddress = NULL; + if (_socketTransport.InitializeReceiveSockets(&_vieReceiver, rtpPort, + ipAddress, + multicastIpAddress, rtcpPort) + != 0) + { + WebRtc_Word32 socketError = _socketTransport.LastError(); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: could not initialize receive sockets. Socket error: %d", + __FUNCTION__, socketError); + return -1; + } + + return 0; +#else + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: not available for external transport", __FUNCTION__); + return -1; +#endif +} + +// ---------------------------------------------------------------------------- +// GetLocalReceiver +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::GetLocalReceiver(WebRtc_UWord16& rtpPort, + WebRtc_UWord16& rtcpPort, + WebRtc_Word8* ipAddress) const +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + _callbackCritsect.Enter(); + if (_ptrExternalTransport) + { + _callbackCritsect.Leave(); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: external transport registered", __FUNCTION__); + return -1; + } + _callbackCritsect.Leave(); + +#ifndef WEBRTC_EXTERNAL_TRANSPORT + if (_socketTransport.ReceiveSocketsInitialized() == false) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: receive sockets not initialized", __FUNCTION__); + return -1; + } + + WebRtc_Word8 multicastIpAddress[UdpTransport:: + kIpAddressVersion6Length]; + if (_socketTransport.ReceiveSocketInformation(ipAddress, rtpPort, rtcpPort, + multicastIpAddress) != 0) + { + WebRtc_Word32 socketError = _socketTransport.LastError(); + WEBRTC_TRACE( + webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: could not get receive socket information. Socket error: %d", + __FUNCTION__, socketError); + return -1; + } + return 0; +#else + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: not available for external transport", __FUNCTION__); + return -1; +#endif +} + +// ---------------------------------------------------------------------------- +// SetSendDestination +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::SetSendDestination( + const WebRtc_Word8* ipAddress, const WebRtc_UWord16 rtpPort, + const WebRtc_UWord16 rtcpPort, const WebRtc_UWord16 sourceRtpPort, + const WebRtc_UWord16 sourceRtcpPort) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + _callbackCritsect.Enter(); + if (_ptrExternalTransport) + { + _callbackCritsect.Leave(); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: external transport registered", __FUNCTION__); + return -1; + } + _callbackCritsect.Leave(); + +#ifndef WEBRTC_EXTERNAL_TRANSPORT + const bool isIPv6 = _socketTransport.IpV6Enabled(); + if (UdpTransport::IsIpAddressValid(ipAddress, isIPv6) == false) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Not a valid IP address: %s", __FUNCTION__, ipAddress); + return -1; + } + if (_socketTransport.InitializeSendSockets(ipAddress, rtpPort, rtcpPort) + != 0) + { + WebRtc_Word32 socketError = _socketTransport.LastError(); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: could not initialize send socket. Socket error: %d", + __FUNCTION__, socketError); + return -1; + } + + if (sourceRtpPort != 0) + { + WebRtc_UWord16 receiveRtpPort = 0; + WebRtc_UWord16 receiveRtcpPort = 0; + if (_socketTransport.ReceiveSocketInformation(NULL, receiveRtpPort, + receiveRtcpPort, NULL) + != 0) + { + WebRtc_Word32 socketError = _socketTransport.LastError(); + WEBRTC_TRACE( + webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: could not get receive port information. Socket error: %d", + __FUNCTION__, socketError); + return -1; + } + // Initialize an extra socket only if send port differs from receive + // port + if (sourceRtpPort != receiveRtpPort) + { + if (_socketTransport.InitializeSourcePorts(sourceRtpPort, + sourceRtcpPort) != 0) + { + WebRtc_Word32 socketError = _socketTransport.LastError(); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: could not set source ports. Socket error: %d", + __FUNCTION__, socketError); + return -1; + } + } + } + _vieSender.RegisterSendTransport(&_socketTransport); + + // Workaround to avoid SSRC colision detection in loppback tests + if (!isIPv6) + { + WebRtc_UWord32 localHostAddress = 0; + const WebRtc_UWord32 currentIpAddress = + UdpTransport::InetAddrIPV4(ipAddress); + + if ((UdpTransport::LocalHostAddress(localHostAddress) == 0 + && localHostAddress == currentIpAddress) || + strncmp("127.0.0.1", ipAddress, 9) == 0) + { + _rtpRtcp.SetSSRC(0xFFFFFFFF); + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "Running in loopback. Forcing fixed SSRC"); + } + } + else + { + WebRtc_UWord8 localHostAddress[16]; + WebRtc_UWord8 currentIpAddress[16]; + + WebRtc_Word32 convResult = + UdpTransport::LocalHostAddressIPV6(localHostAddress); + convResult + += _socketTransport.InetPresentationToNumeric(23, ipAddress, + currentIpAddress); + if (convResult == 0) + { + bool localHost = true; + for (WebRtc_Word32 i = 0; i < 16; i++) + { + if (localHostAddress[i] != currentIpAddress[i]) + { + localHost = false; + break; + } + } + if (!localHost) + { + localHost = true; + for (WebRtc_Word32 i = 0; i < 15; i++) + { + if (currentIpAddress[i] != 0) + { + localHost = false; + break; + } + } + if (localHost == true && currentIpAddress[15] != 1) + { + localHost = false; + } + } + if (localHost) + { + _rtpRtcp.SetSSRC(0xFFFFFFFF); + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "Running in loopback. Forcing fixed SSRC"); + } + } + } + return 0; +#else + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: not available for external transport", __FUNCTION__); + return -1; +#endif +} + +// ---------------------------------------------------------------------------- +// GetSendDestination +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::GetSendDestination( + WebRtc_Word8* ipAddress, WebRtc_UWord16& rtpPort, WebRtc_UWord16& rtcpPort, + WebRtc_UWord16& sourceRtpPort, WebRtc_UWord16& sourceRtcpPort) const +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + _callbackCritsect.Enter(); + if (_ptrExternalTransport) + { + _callbackCritsect.Leave(); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: external transport registered", __FUNCTION__); + return -1; + } + _callbackCritsect.Leave(); + +#ifndef WEBRTC_EXTERNAL_TRANSPORT + if (_socketTransport.SendSocketsInitialized() == false) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: send sockets not initialized", __FUNCTION__); + return -1; + } + if (_socketTransport.SendSocketInformation(ipAddress, rtpPort, rtcpPort) + != 0) + { + WebRtc_Word32 socketError = _socketTransport.LastError(); + WEBRTC_TRACE( + webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: could not get send socket information. Socket error: %d", + __FUNCTION__, socketError); + return -1; + } + sourceRtpPort = 0; + sourceRtcpPort = 0; + if (_socketTransport.SourcePortsInitialized()) + { + _socketTransport.SourcePorts(sourceRtpPort, sourceRtcpPort); + } + return 0; +#else + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: not available for external transport", __FUNCTION__); + return -1; +#endif +} + +// ---------------------------------------------------------------------------- +// StartSend +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::StartSend() +{ + CriticalSectionScoped cs(_callbackCritsect); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + +#ifndef WEBRTC_EXTERNAL_TRANSPORT + if (!_ptrExternalTransport) + { + if (_socketTransport.SendSocketsInitialized() == false) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId,_channelId), + "%s: send sockets not initialized", __FUNCTION__); + return -1; + } + } +#endif + _rtpRtcp.SetSendingMediaStatus(true); + + if (_rtpRtcp.Sending() && !_rtpRtcp.RTPKeepalive()) + { + if (_rtpRtcp.RTPKeepalive()) + { + // Sending Keep alive, don't trigger an error + return 0; + } + // Already sending + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: Already sending", + __FUNCTION__); + return kViEBaseAlreadySending; + } + if (_rtpRtcp.SetSendingStatus(true) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not start sending RTP", __FUNCTION__); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// StopSend +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::StopSend() +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + _rtpRtcp.SetSendingMediaStatus(false); + if (_rtpRtcp.RTPKeepalive()) + { + // Don't turn off sending since we'll send keep alive packets + return 0; + } + if (!_rtpRtcp.Sending()) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: Not sending", + __FUNCTION__); + return kViEBaseNotSending; + } + // Reset + _rtpRtcp.ResetSendDataCountersRTP(); + if (_rtpRtcp.SetSendingStatus(false) != 0) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: could not stop RTP sending", __FUNCTION__); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// Sending +// ---------------------------------------------------------------------------- + +bool ViEChannel::Sending() +{ + return _rtpRtcp.Sending(); +} + +// ---------------------------------------------------------------------------- +// StartReceive +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::StartReceive() +{ + CriticalSectionScoped cs(_callbackCritsect); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + +#ifndef WEBRTC_EXTERNAL_TRANSPORT + if (!_ptrExternalTransport) + { + if (_socketTransport.Receiving()) + { + // Warning, don't return error + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: already receiving", + __FUNCTION__); + return 0; + } + if (_socketTransport.ReceiveSocketsInitialized() == false) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: receive sockets not initialized", __FUNCTION__); + return -1; + } + if (_socketTransport.StartReceiving(kViENumReceiveSocketBuffers) + != 0) + { + WebRtc_Word32 socketError = _socketTransport.LastError(); + WEBRTC_TRACE( + webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: could not get receive socket information. Socket error:%d", + __FUNCTION__, socketError); + return -1; + } + } +#endif + if (StartDecodeThread() != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: could not start decoder thread", __FUNCTION__); + +#ifndef WEBRTC_EXTERNAL_TRANSPORT + _socketTransport.StopReceiving(); +#endif + _vieReceiver.StopReceive(); + return -1; + } + _vieReceiver.StartReceive(); + + return 0; +} + +// ---------------------------------------------------------------------------- +// StopReceive +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::StopReceive() +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + _vieReceiver.StopReceive(); + StopDecodeThread(); + _vcm.ResetDecoder(); + { + CriticalSectionScoped cs(_callbackCritsect); + if (_ptrExternalTransport) + { + return 0; + } + } + +#ifndef WEBRTC_EXTERNAL_TRANSPORT + if (_socketTransport.Receiving() == false) + { + // Warning, don't return error + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: not receiving", + __FUNCTION__); + return 0; + } + if (_socketTransport.StopReceiving() != 0) + { + WebRtc_Word32 socketError = _socketTransport.LastError(); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: Socket error: %d", __FUNCTION__, socketError); + return -1; + } +#endif + + return 0; +} + +bool ViEChannel::Receiving() +{ +#ifndef WEBRTC_EXTERNAL_TRANSPORT + return _socketTransport.Receiving(); +#else + return false; +#endif +} + +// ---------------------------------------------------------------------------- +// GetSourceInfo +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::GetSourceInfo(WebRtc_UWord16& rtpPort, + WebRtc_UWord16& rtcpPort, + WebRtc_Word8* ipAddress, + WebRtc_UWord32 ipAddressLength) +{ + { + CriticalSectionScoped cs(_callbackCritsect); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + if (_ptrExternalTransport) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: external transport registered", __FUNCTION__); + return -1; + } + } +#ifndef WEBRTC_EXTERNAL_TRANSPORT + + if (_socketTransport.IpV6Enabled() && ipAddressLength + < UdpTransport::kIpAddressVersion6Length) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: IP address length is too small for IPv6", __FUNCTION__); + return -1; + } + else if (ipAddressLength < + UdpTransport::kIpAddressVersion4Length) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: IP address length is too small for IPv4", __FUNCTION__); + return -1; + } + + if (_socketTransport.RemoteSocketInformation(ipAddress, rtpPort, rtcpPort) + != 0) + { + WebRtc_Word32 socketError = _socketTransport.LastError(); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Error getting source ports. Socket error: %d", + __FUNCTION__, socketError); + return -1; + } + + return 0; +#else + + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: not available for external transport", + __FUNCTION__); + return -1; +#endif +} + +// ---------------------------------------------------------------------------- +// RegisterSendTransport +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::RegisterSendTransport(Transport& transport) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + +#ifndef WEBRTC_EXTERNAL_TRANSPORT + if (_socketTransport.SendSocketsInitialized() + || _socketTransport.ReceiveSocketsInitialized()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: socket transport already initialized", + __FUNCTION__); + return -1; + } +#endif + + if (_rtpRtcp.Sending()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: Sending", __FUNCTION__); + return -1; + } + + CriticalSectionScoped cs(_callbackCritsect); + if (_ptrExternalTransport) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: transport already registered", __FUNCTION__); + return -1; + } + _ptrExternalTransport = &transport; + _vieSender.RegisterSendTransport(&transport); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: Transport registered: 0x%p", __FUNCTION__, + &_ptrExternalTransport); + + return 0; +} + +// ---------------------------------------------------------------------------- +// DeregisterSendTransport +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::DeregisterSendTransport() +{ + CriticalSectionScoped cs(_callbackCritsect); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + if (_ptrExternalTransport == NULL) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: no transport registered", __FUNCTION__); + return -1; + } + if (_rtpRtcp.Sending()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: Sending", __FUNCTION__); + return -1; + } + _ptrExternalTransport = NULL; + _vieSender.DeregisterSendTransport(); + return 0; +} + +// ---------------------------------------------------------------------------- +// ReceivedRTPPacket +// +// Incoming packet from external transport +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::ReceivedRTPPacket(const void* rtpPacket, + const WebRtc_Word32 rtpPacketLength) +{ + { + CriticalSectionScoped cs(_callbackCritsect); + if (!_ptrExternalTransport) + { + return -1; + } + } + return _vieReceiver.ReceivedRTPPacket(rtpPacket, rtpPacketLength); +} + +// ---------------------------------------------------------------------------- +// ReceivedRTPPacket +// +// Incoming packet from external transport +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::ReceivedRTCPPacket( + const void* rtcpPacket, const WebRtc_Word32 rtcpPacketLength) +{ + { + CriticalSectionScoped cs(_callbackCritsect); + if (!_ptrExternalTransport) + { + return -1; + } + } + return _vieReceiver.ReceivedRTCPPacket(rtcpPacket, rtcpPacketLength); +} + +// ---------------------------------------------------------------------------- +// EnableIPv6 +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::EnableIPv6() +{ + _callbackCritsect.Enter(); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + if (_ptrExternalTransport) + { + _callbackCritsect.Leave(); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: External transport registered", __FUNCTION__); + return -1; + } + _callbackCritsect.Leave(); + +#ifndef WEBRTC_EXTERNAL_TRANSPORT + if (_socketTransport.IpV6Enabled()) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, ViEId(_engineId, + _channelId), + "%s: IPv6 already enabled", __FUNCTION__); + return -1; + } + + if (_socketTransport.EnableIpV6() != 0) + { + WebRtc_Word32 socketError = _socketTransport.LastError(); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: could not enable IPv6. Socket error: %d", __FUNCTION__, + socketError); + return -1; + } + return 0; +#else + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: not available for external transport", __FUNCTION__); + return -1; +#endif + +} + +// ---------------------------------------------------------------------------- +// IsIPv6Enabled +// ---------------------------------------------------------------------------- + +bool ViEChannel::IsIPv6Enabled() +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + { + CriticalSectionScoped cs(_callbackCritsect); + if (_ptrExternalTransport) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: External transport registered", __FUNCTION__); + return false; + } + } +#ifndef WEBRTC_EXTERNAL_TRANSPORT + return _socketTransport.IpV6Enabled(); +#else + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: not available for external transport", __FUNCTION__); + return false; +#endif +} + +// ---------------------------------------------------------------------------- +// SetSourceFilter +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::SetSourceFilter(const WebRtc_UWord16 rtpPort, + const WebRtc_UWord16 rtcpPort, + const WebRtc_Word8* ipAddress) +{ + _callbackCritsect.Enter(); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + if (_ptrExternalTransport) + { + _callbackCritsect.Leave(); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: External transport registered", __FUNCTION__); + return -1; + } + _callbackCritsect.Leave(); + +#ifndef WEBRTC_EXTERNAL_TRANSPORT + if (_socketTransport.SetFilterIP(ipAddress) != 0) + { + // Logging done in module + return -1; + } + if (_socketTransport.SetFilterPorts(rtpPort, rtcpPort) != 0) + { + // Logging done + return -1; + } + return 0; +#else + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: not available for external transport", __FUNCTION__); + return -1; +#endif +} + +// ---------------------------------------------------------------------------- +// GetSourceFilter +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::GetSourceFilter(WebRtc_UWord16& rtpPort, + WebRtc_UWord16& rtcpPort, + WebRtc_Word8* ipAddress) const +{ + _callbackCritsect.Enter(); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + if (_ptrExternalTransport) + { + _callbackCritsect.Leave(); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: External transport registered", __FUNCTION__); + return -1; + } + _callbackCritsect.Leave(); + +#ifndef WEBRTC_EXTERNAL_TRANSPORT + if (_socketTransport.FilterIP(ipAddress) != 0) + { + // Logging done in module + return -1; + } + if (_socketTransport.FilterPorts(rtpPort, rtcpPort) != 0) + { + // Logging done in module + return -1; + } + + return 0; +#else + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: not available for external transport", __FUNCTION__); + return -1; +#endif +} + +// ---------------------------------------------------------------------------- +// SetToS +// ---------------------------------------------------------------------------- + +// ToS +WebRtc_Word32 ViEChannel::SetToS(const WebRtc_Word32 DSCP, + const bool useSetSockOpt) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + { + CriticalSectionScoped cs(_callbackCritsect); + if (_ptrExternalTransport) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: External transport registered", __FUNCTION__); + return -1; + } + } +#ifndef WEBRTC_EXTERNAL_TRANSPORT + if (_socketTransport.SetToS(DSCP, useSetSockOpt) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: Socket error: %d", + __FUNCTION__, _socketTransport.LastError()); + return -1; + } + return 0; +#else + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: not available for external transport", __FUNCTION__); + return -1; +#endif +} + +// ---------------------------------------------------------------------------- +// GetToS +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::GetToS(WebRtc_Word32& DSCP, bool& useSetSockOpt) const +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + { + CriticalSectionScoped cs(_callbackCritsect); + if (_ptrExternalTransport) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: External transport registered", __FUNCTION__); + return -1; + } + } +#ifndef WEBRTC_EXTERNAL_TRANSPORT + if (_socketTransport.ToS(DSCP, useSetSockOpt) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: Socket error: %d", + __FUNCTION__, _socketTransport.LastError()); + return -1; + } + return 0; +#else + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: not available for external transport", __FUNCTION__); + return -1; +#endif +} + +// ---------------------------------------------------------------------------- +// SetSendGQoS +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::SetSendGQoS(const bool enable, + const WebRtc_Word32 serviceType, + const WebRtc_UWord32 maxBitrate, + const WebRtc_Word32 overrideDSCP) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + { + CriticalSectionScoped cs(_callbackCritsect); + if (_ptrExternalTransport) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: External transport registered", __FUNCTION__); + return -1; + } + } +#ifndef WEBRTC_EXTERNAL_TRANSPORT + if (_socketTransport.SetQoS(enable, serviceType, maxBitrate, overrideDSCP, + false) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: Socket error: %d", + __FUNCTION__, _socketTransport.LastError()); + return -1; + } + return 0; +#else + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: not available for external transport", __FUNCTION__); + return -1; +#endif +} + +// ---------------------------------------------------------------------------- +// GetSendGQoS +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::GetSendGQoS(bool& enabled, + WebRtc_Word32& serviceType, + WebRtc_Word32& overrideDSCP) const +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + { + CriticalSectionScoped cs(_callbackCritsect); + if (_ptrExternalTransport) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: External transport registered", __FUNCTION__); + return -1; + } + } +#ifndef WEBRTC_EXTERNAL_TRANSPORT + if (_socketTransport.QoS(enabled, serviceType, overrideDSCP) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: Socket error: %d", + __FUNCTION__, _socketTransport.LastError()); + return -1; + } + return 0; +#else + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: not available for external transport", __FUNCTION__); + return -1; +#endif +} + +// ---------------------------------------------------------------------------- +// SetMTU +// +// Sets the maximum transfer unit size for the network link, i.e. including +// IP, UDP[/TCP] and RTP headers, +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::SetMTU(WebRtc_UWord16 mtu) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + if (_rtpRtcp.SetMaxTransferUnit(mtu) != 0) + { + // Logging done + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// GetMaxDataPayloadLength +// +// maxDataPayloadLength: maximum allowed payload size, i.e. the maximum +// allowed size of encoded data in each packet. +// ---------------------------------------------------------------------------- + +WebRtc_UWord16 ViEChannel::MaxDataPayloadLength() const +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + return _rtpRtcp.MaxDataPayloadLength(); +} + +// ---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- +// SetPacketTimeoutNotification +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::SetPacketTimeoutNotification( + bool enable, + WebRtc_UWord32 timeoutSeconds) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + if (enable) + { + WebRtc_UWord32 timeoutMs = 1000 * timeoutSeconds; + if (_rtpRtcp.SetPacketTimeout(timeoutMs, 0) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s", + __FUNCTION__); + return -1; + } + } + else + { + if (_rtpRtcp.SetPacketTimeout(0, 0) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId,_channelId), "%s", + __FUNCTION__); + return -1; + } + } + return 0; +} + +// ---------------------------------------------------------------------------- +// RegisterObserver +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::RegisterNetworkObserver( + ViENetworkObserver* observer) +{ + CriticalSectionScoped cs(_callbackCritsect); + if (observer) + { + if (_networkObserver) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: observer alread added", __FUNCTION__); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: observer added", __FUNCTION__); + _networkObserver = observer; + } + else + { + if (!_networkObserver) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: no observer added", + __FUNCTION__); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: observer removed", __FUNCTION__); + _networkObserver = NULL; + } + return 0; +} +bool ViEChannel::NetworkObserverRegistered() +{ + CriticalSectionScoped cs(_callbackCritsect); + return _networkObserver != NULL; +} + +// ---------------------------------------------------------------------------- +// SetPeriodicDeadOrAliveStatus +// ---------------------------------------------------------------------------- +WebRtc_Word32 ViEChannel::SetPeriodicDeadOrAliveStatus( + const bool enable, const WebRtc_UWord32 sampleTimeSeconds) +{ + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + CriticalSectionScoped cs(_callbackCritsect); + if (_networkObserver == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: no observer added", __FUNCTION__); + return -1; + } + + bool enabled = false; + WebRtc_UWord8 currentSampletimeSeconds = 0; + + // Get old settings + _rtpRtcp.PeriodicDeadOrAliveStatus(enabled, currentSampletimeSeconds); + // Set new settings + if (_rtpRtcp.SetPeriodicDeadOrAliveStatus( + enable, (WebRtc_UWord8) sampleTimeSeconds) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not set periodic dead-or-alive status", + __FUNCTION__); + return -1; + } + if (!enable) + { + // Restore last utilized sample time. + // Without this trick, the sample time would always be reset to default + // (2 sec), each time dead-or-alive was disabled without sample-time + // parameter. + _rtpRtcp.SetPeriodicDeadOrAliveStatus(enable, currentSampletimeSeconds); + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// SendUDPPacket +// ---------------------------------------------------------------------------- +WebRtc_Word32 ViEChannel::SendUDPPacket(const WebRtc_Word8* data, + const WebRtc_UWord32 length, + WebRtc_Word32& transmittedBytes, + bool useRtcpSocket) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + { + CriticalSectionScoped cs(_callbackCritsect); + if (_ptrExternalTransport) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: External transport registered", __FUNCTION__); + return -1; + } + } +#ifndef WEBRTC_EXTERNAL_TRANSPORT + transmittedBytes = _socketTransport.SendRaw(data, length, useRtcpSocket); + if (transmittedBytes == -1) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s", __FUNCTION__); + return -1; + } + return 0; +#else + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: not available for external transport", __FUNCTION__); + return -1; +#endif +} + +// ============================================================================ +// Color enahncement +// ============================================================================ + +// ---------------------------------------------------------------------------- +// EnableColorEnhancement +// +// Enables/disables color enhancement for all decoded frames +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::EnableColorEnhancement(bool enable) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s(enable: %d)", __FUNCTION__, enable); + + CriticalSectionScoped cs(_callbackCritsect); + if (enable && _colorEnhancement) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: Already enabled", + __FUNCTION__); + return -1; + } + else if (enable == false && _colorEnhancement == false) + + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: not enabled", + __FUNCTION__); + return -1; + } + _colorEnhancement = enable; + return 0; +} + +// ============================================================================ +// Register sender +// ============================================================================ + +// ---------------------------------------------------------------------------- +// RegisterSendRtpRtcpModule +// +// Register send RTP RTCP module, which will deliver the frames to send +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::RegisterSendRtpRtcpModule( + RtpRtcp& sendRtpRtcpModule) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + return _rtpRtcp.RegisterDefaultModule(&sendRtpRtcpModule); +} + +// ---------------------------------------------------------------------------- +// RegisterSendDeregisterSendRtpRtcpModuleRtpRtcpModule +// +// Deregisters the send RTP RTCP module, which will stop the encoder input to +// the channel +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::DeregisterSendRtpRtcpModule() +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + return _rtpRtcp.DeRegisterDefaultModule(); +} + +// ============================================================================ +// FrameToRender +// Called by VCM when a frame have been decoded. +// ============================================================================ + +WebRtc_Word32 ViEChannel::FrameToRender(VideoFrame& videoFrame) +{ + CriticalSectionScoped cs(_callbackCritsect); + + if (_decoderReset) + { + // Trigger a callback to the user if the incoming codec has changed. + if (_codecObserver) + { + VideoCodec decoder; + memset(&decoder, 0, sizeof(decoder)); + if (_vcm.ReceiveCodec(&decoder) == VCM_OK) + { + // VCM::ReceiveCodec returns the codec set by + // RegisterReceiveCodec, which might not be the size we're + // actually decoding + decoder.width = (unsigned short) videoFrame.Width(); + decoder.height = (unsigned short) videoFrame.Height(); + _codecObserver->IncomingCodecChanged(_channelId, decoder); + } + else + { + assert(false); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not get receive codec", __FUNCTION__); + } + } + _decoderReset = false; + } + if (_effectFilter) + { + _effectFilter->Transform(videoFrame.Length(), videoFrame.Buffer(), + videoFrame.TimeStamp(), videoFrame.Width(), + videoFrame.Height()); + } + if (_colorEnhancement) + { + VideoProcessingModule::ColorEnhancement(videoFrame); + } + + // Record videoframe + _fileRecorder.RecordVideoFrame(videoFrame); + + WebRtc_UWord32 arrOfCSRC[kRtpCsrcSize]; + WebRtc_Word32 noOfCSRCs = _rtpRtcp.RemoteCSRCs(arrOfCSRC); + if (noOfCSRCs <= 0) + { + arrOfCSRC[0] = _rtpRtcp.RemoteSSRC(); + noOfCSRCs = 1; + } + + DeliverFrame(videoFrame, noOfCSRCs, arrOfCSRC); + + return 0; +} + +WebRtc_Word32 ViEChannel::ReceivedDecodedReferenceFrame( + const WebRtc_UWord64 pictureId) +{ + return _rtpRtcp.SendRTCPReferencePictureSelection(pictureId); +} + +// ============================================================================ +// StoreReceivedFrame +// Called by VCM before a frame have been decoded. could be used for recording +// incoming video. +// ============================================================================ + +WebRtc_Word32 ViEChannel::StoreReceivedFrame( + const EncodedVideoData& frameToStore) +{ + return 0; +} + +// ---------------------------------------------------------------------------- +// ReceiveStatistics +// +// Called by VCM with information about received video stream +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::ReceiveStatistics(const WebRtc_UWord32 bitRate, + const WebRtc_UWord32 frameRate) +{ + CriticalSectionScoped cs(_callbackCritsect); + if (_codecObserver) + { + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: bitrate %u, framerate %u", __FUNCTION__, bitRate, + frameRate); + _codecObserver->IncomingRate(_channelId, frameRate, bitRate); + } + return 0; +} + +// ---------------------------------------------------------------------------- +// FrameTypeRequest +// +// Called by VCM when a certain frame type is needed to continue decoding +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::FrameTypeRequest(const FrameType frameType) +{ + WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s(frameType: %d)", __FUNCTION__, frameType); + + { + CriticalSectionScoped cs(_callbackCritsect); + if (_codecObserver && _doKeyFrameCallbackRequest) + { + _codecObserver->RequestNewKeyFrame(_channelId); + } + } + return _rtpRtcp.RequestKeyFrame(frameType); +} + +WebRtc_Word32 ViEChannel::SliceLossIndicationRequest( + const WebRtc_UWord64 pictureId) +{ + return _rtpRtcp.SendRTCPSliceLossIndication((WebRtc_UWord8) pictureId); +} + +// ---------------------------------------------------------------------------- +// ResendPackets +// +// Called by VCM when VCM wants to request resend of packets (NACK) +// ---------------------------------------------------------------------------- +WebRtc_Word32 ViEChannel::ResendPackets(const WebRtc_UWord16* sequenceNumbers, + WebRtc_UWord16 length) +{ + WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s(length: %d)", __FUNCTION__, length); + + return _rtpRtcp.SendNACK(sequenceNumbers, length); +} + +// ============================================================================ +// Protected methods +// ============================================================================ + +// ---------------------------------------------------------------------------- +// ChannelDecodeThreadFunction +// ---------------------------------------------------------------------------- + +bool ViEChannel::ChannelDecodeThreadFunction(void* obj) +{ + return static_cast (obj)->ChannelDecodeProcess(); +} + +// ---------------------------------------------------------------------------- +// ChannelDecodeThreadFunction +// ---------------------------------------------------------------------------- + +bool ViEChannel::ChannelDecodeProcess() +{ + // Decode is blocking, but sleep some time anyway to not get a spin + _vcm.Decode(50); + + if ((TickTime::Now() - _vcmRTTReported).Milliseconds() > 1000) + { + WebRtc_UWord16 RTT; + WebRtc_UWord16 avgRTT; + WebRtc_UWord16 minRTT; + WebRtc_UWord16 maxRTT; + + if (_rtpRtcp.RTT(_rtpRtcp.RemoteSSRC(), &RTT, &avgRTT, &minRTT, &maxRTT) + == 0) + { + _vcm.SetReceiveChannelParameters(RTT); + } + _vcmRTTReported = TickTime::Now(); + } + return true; +} + +// ============================================================================ +// Private methods +// ============================================================================ + +// ---------------------------------------------------------------------------- +// StartDecodeThread +// +// Assumed to be critsect protected if needed +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::StartDecodeThread() +{ + // Start the decode thread + if (_ptrDecodeThread) + { + // Already started + return 0; + } + _ptrDecodeThread = ThreadWrapper::CreateThread(ChannelDecodeThreadFunction, + this, kHighestPriority, + "DecodingThread"); + if (_ptrDecodeThread == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: could not create decode thread", __FUNCTION__); + return -1; + } + + unsigned int threadId; + if (_ptrDecodeThread->Start(threadId) == false) + { + delete _ptrDecodeThread; + _ptrDecodeThread = NULL; + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: could not start decode thread", __FUNCTION__); + return -1; + } + + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: decode thread with id %u started", __FUNCTION__); + return 0; +} + +// ---------------------------------------------------------------------------- +// StopDecodeThread +// +// Assumed to be critsect protected if needed +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEChannel::StopDecodeThread() +{ + if (_ptrDecodeThread == NULL) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: decode thread not running", __FUNCTION__); + return 0; + } + + _ptrDecodeThread->SetNotAlive(); + if (_ptrDecodeThread->Stop()) + { + delete _ptrDecodeThread; + } + else + { + // Couldn't stop the thread, leak instead of crash... + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: could not stop decode thread", __FUNCTION__); + assert(!"could not stop decode thread"); + } + _ptrDecodeThread = NULL; + return 0; +} + +#ifdef WEBRTC_SRTP + +WebRtc_Word32 +ViEChannel::EnableSRTPSend(const SrtpModule::CipherTypes cipherType, + const unsigned int cipherKeyLength, + const SrtpModule::AuthenticationTypes authType, + const unsigned int authKeyLength, const unsigned int authTagLength, + const SrtpModule::SecurityLevels level, const WebRtc_UWord8* key, + const bool useForRTCP) +{ + + _callbackCritsect.Enter(); + if (_ptrExternalEncryption) + { + _callbackCritsect.Leave(); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: external encryption already registered", __FUNCTION__); + return -1; + } + _callbackCritsect.Leave(); + + if (!_ptrSrtpModuleEncryption) + { + _ptrSrtpModuleEncryption = + SrtpModule::CreateSrtpModule( + ViEModuleId(_engineId, _channelId)); + if(!_ptrSrtpModuleEncryption) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "Failed to create SRTP module"); + return -1; + } + } + + const WebRtc_Word32 result = + _ptrSrtpModuleEncryption->EnableSRTPEncrypt( + !useForRTCP, cipherType, cipherKeyLength, authType, authKeyLength, + authTagLength,level,key); + if (0 != result) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "EnableSRTPEncrypt result %d, Apply To RTCP %d", result, + useForRTCP); + } + else + { + _vieSender.RegisterSRTPModule(_ptrSrtpModuleEncryption); + if (useForRTCP) + { + _vieSender.RegisterSRTCPModule(_ptrSrtpModuleEncryption); + } + } + return result; +} + +WebRtc_Word32 ViEChannel::DisableSRTPSend() +{ + WebRtc_Word32 result = -1; + if (_ptrSrtpModuleEncryption) + { + result = _ptrSrtpModuleEncryption->DisableSRTPEncrypt(); + _vieSender.DeregisterSRTPModule(); + _vieSender.DeregisterSRTCPModule(); + } + +} + +WebRtc_Word32 +ViEChannel::EnableSRTPReceive(const SrtpModule::CipherTypes cipherType, + const unsigned int cipherKeyLength, + const SrtpModule::AuthenticationTypes authType, + const unsigned int authKeyLength, const unsigned int authTagLength, + const SrtpModule::SecurityLevels level, const WebRtc_UWord8* key, + const bool useForRTCP) +{ + _callbackCritsect.Enter(); + if (_ptrExternalEncryption) + { + _callbackCritsect.Leave(); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: external encryption already registered", __FUNCTION__); + return -1; + } + _callbackCritsect.Leave(); + + if (!_ptrSrtpModuleDecryption) + { + _ptrSrtpModuleDecryption = + SrtpModule::CreateSrtpModule( + ViEModuleId(_engineId, _channelId)); + if(!_ptrSrtpModuleDecryption) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "Failed to create SRTP module"); + return -1; + } + } + + const WebRtc_Word32 result = + _ptrSrtpModuleDecryption->EnableSRTPDecrypt( + !useForRTCP, cipherType, cipherKeyLength, authType, authKeyLength, + authTagLength,level,key); + if (0 != result) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "EnableSRTPEncrypt result %d, Apply To RTCP %d", result, + useForRTCP); + } + else + { + _vieReceiver.RegisterSRTPModule(_ptrSrtpModuleDecryption); + if (useForRTCP) + { + _vieReceiver.RegisterSRTCPModule(_ptrSrtpModuleDecryption); + } + } + return result; +} + +WebRtc_Word32 ViEChannel::DisableSRTPReceive() +{ + WebRtc_Word32 result = -1; + if (_ptrSrtpModuleDecryption) + { + result = _ptrSrtpModuleDecryption->DisableSRTPDecrypt(); + _vieReceiver.DeregisterSRTPModule(); + _vieReceiver.DeregisterSRTPModule(); + } + return result; +} +#endif + +WebRtc_Word32 ViEChannel::RegisterExternalEncryption( + Encryption* encryption) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + CriticalSectionScoped cs(_callbackCritsect); + if (_ptrExternalEncryption) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: external encryption already registered", __FUNCTION__); + return -1; + } + + _ptrExternalEncryption = encryption; + + _vieReceiver.RegisterExternalDecryption(encryption); + _vieSender.RegisterExternalEncryption(encryption); + + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", "external encryption object registerd with channel=%d", + _channelId); + return 0; +} + +WebRtc_Word32 ViEChannel::DeRegisterExternalEncryption() +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + CriticalSectionScoped cs(_callbackCritsect); + if (!_ptrExternalEncryption) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: external encryption is not registered", __FUNCTION__); + return -1; + } + + _ptrExternalTransport = NULL; + _vieReceiver.DeregisterExternalDecryption(); + _vieSender.DeregisterExternalEncryption(); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s external encryption object de-registerd with channel=%d", + __FUNCTION__, _channelId); + return 0; +} + +WebRtc_Word32 ViEChannel::SetVoiceChannel(WebRtc_Word32 veChannelId, + VoEVideoSync* veSyncInterface) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s, audio channel %d, video channel %d", __FUNCTION__, + veChannelId, _channelId); + + if (veSyncInterface) + { + // Register lip sync + _moduleProcessThread.RegisterModule(&_vieSync); + } + else + { + _moduleProcessThread.DeRegisterModule(&_vieSync); + } + return _vieSync.SetVoiceChannel(veChannelId, veSyncInterface); +} + +WebRtc_Word32 ViEChannel::VoiceChannel() +{ + return _vieSync.VoiceChannel(); +} + +WebRtc_Word32 ViEChannel::RegisterEffectFilter(ViEEffectFilter* effectFilter) +{ + CriticalSectionScoped cs(_callbackCritsect); + if (effectFilter == NULL) + { + if (_effectFilter == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: no effect filter added for channel %d", + __FUNCTION__, _channelId); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: deregister effect filter for device %d", __FUNCTION__, + _channelId); + } + else + { + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: register effect filter for device %d", __FUNCTION__, + _channelId); + if (_effectFilter) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: effect filter already added for channel %d", + __FUNCTION__, _channelId); + return -1; + } + } + _effectFilter = effectFilter; + return 0; +} + +ViEFileRecorder& ViEChannel::GetIncomingFileRecorder() +{ + // Start getting callback of all frames before they are decoded + _vcm.RegisterFrameStorageCallback(this); + return _fileRecorder; +} + +void ViEChannel::ReleaseIncomingFileRecorder() +{ + // Stop getting callback of all frames before they are decoded + _vcm.RegisterFrameStorageCallback(NULL); +} + +void ViEChannel::OnLipSyncUpdate(const WebRtc_Word32 id, + const WebRtc_Word32 audioVideoOffset) +{ + if (_channelId != ChannelId(id)) + { + WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s, incorrect id", + __FUNCTION__, id); + return; + } + _vieSync.SetNetworkDelay(audioVideoOffset); +} + +void ViEChannel::OnApplicationDataReceived(const WebRtc_Word32 id, + const WebRtc_UWord8 subType, + const WebRtc_UWord32 name, + const WebRtc_UWord16 length, + const WebRtc_UWord8* data) +{ + if (_channelId != ChannelId(id)) + { + WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s, incorrect id", + __FUNCTION__, id); + return; + } + CriticalSectionScoped cs(_callbackCritsect); + { + if (_rtcpObserver) + { + _rtcpObserver->OnApplicationDataReceived(_channelId, subType, name, + (const char*) data, length); + } + } +} + +WebRtc_Word32 ViEChannel::OnInitializeDecoder( + const WebRtc_Word32 id, const WebRtc_Word8 payloadType, + const WebRtc_Word8 payloadName[RTP_PAYLOAD_NAME_SIZE], + const WebRtc_UWord32 frequency, const WebRtc_UWord8 channels, + const WebRtc_UWord32 rate) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: payloadType %d, payloadName %s", __FUNCTION__, payloadType, + payloadName); + + _vcm.ResetDecoder(); + + _callbackCritsect.Enter(); + _decoderReset = true; + _callbackCritsect.Leave(); + + return 0; +} + +void ViEChannel::OnPacketTimeout(const WebRtc_Word32 id) +{ + assert(ChannelId(id) == _channelId); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + CriticalSectionScoped cs(_callbackCritsect); + if (_networkObserver) + { +#ifndef WEBRTC_EXTERNAL_TRANSPORT + if (_socketTransport.Receiving() || _ptrExternalTransport) +#else + if (_ptrExternalTransport) +#endif + { + _networkObserver->PacketTimeout(_channelId, NoPacket); + _rtpPacketTimeout = true; + } + } +} + +// +// Called by the rtp module when the first packet is received and +// when first packet after a timeout is received. +// +void ViEChannel::OnReceivedPacket(const WebRtc_Word32 id, + const RtpRtcpPacketType packetType) +{ + assert(ChannelId(id) == _channelId); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + if (_rtpPacketTimeout && packetType == kPacketRtp) + { + CriticalSectionScoped cs(_callbackCritsect); + if (_networkObserver) + { + _networkObserver->PacketTimeout(_channelId, PacketReceived); + } + // Reset even if no observer set, might have been removed during timeout + _rtpPacketTimeout = false; + } +} + +void ViEChannel::OnPeriodicDeadOrAlive(const WebRtc_Word32 id, + const RTPAliveType alive) +{ + assert(ChannelId(id) == _channelId); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s(id=%d, alive=%d)", __FUNCTION__, id, alive); + + CriticalSectionScoped cs(_callbackCritsect); + if (!_networkObserver) + { + return; + } + bool isAlive = true; + if (alive == kRtpDead) + { + isAlive = false; + } + _networkObserver->OnPeriodicDeadOrAlive(_channelId, isAlive); + return; +} + +void ViEChannel::OnIncomingSSRCChanged(const WebRtc_Word32 id, + const WebRtc_UWord32 SSRC) +{ + if (_channelId != ChannelId(id)) + { + assert(false); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s, incorrect id", __FUNCTION__, id); + return; + } + + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: %u", __FUNCTION__, SSRC); + + CriticalSectionScoped cs(_callbackCritsect); + { + if (_rtpObserver) + { + _rtpObserver->IncomingSSRCChanged(_channelId, SSRC); + } + } +} + +void ViEChannel::OnIncomingCSRCChanged(const WebRtc_Word32 id, + const WebRtc_UWord32 CSRC, + const bool added) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: %u added: %d", __FUNCTION__, CSRC, added); + + if (_channelId != ChannelId(id)) + { + assert(false); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s, incorrect id", __FUNCTION__, id); + return; + } + + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: %u", __FUNCTION__, CSRC); + + CriticalSectionScoped cs(_callbackCritsect); + { + if (_rtpObserver) + { + _rtpObserver->IncomingCSRCChanged(_channelId, CSRC, added); + } + } +} + +WebRtc_Word32 ViEChannel::SetInverseH263Logic(const bool enable) +{ + return _rtpRtcp.SetH263InverseLogic(enable); +} + +} // namespace webrtc diff --git a/video_engine/main/source/vie_channel.h b/video_engine/main/source/vie_channel.h new file mode 100644 index 0000000000..c4908dbfb7 --- /dev/null +++ b/video_engine/main/source/vie_channel.h @@ -0,0 +1,474 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_channel.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_CHANNEL_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_CHANNEL_H_ + +// Defines +#include "vie_defines.h" + +#include "typedefs.h" +#include "vie_network.h" +#include "rtp_rtcp_defines.h" +#include "udp_transport.h" +#include "video_coding_defines.h" +#ifdef WEBRTC_SRTP +#include "SrtpModule.h" +#endif +#include "tick_util.h" +#include "vie_frame_provider_base.h" +#include "vie_file_recorder.h" + +// Forward declarations + +class SrtpModule; +class VideoRenderCallback; + +namespace webrtc +{ +class CriticalSectionWrapper; +class Encryption; +class ProcessThread; +class RtpRtcp; +class ThreadWrapper; +class VideoCodingModule; +class VideoDecoder; +class ViEDecoderObserver; +class ViEEffectFilter; +class ViENetworkObserver; +class ViEReceiver; +class ViERTCPObserver; +class ViERTPObserver; +class ViESender; +class ViESyncModule; +class VoEVideoSync; + +class ViEChannel: + public VCMFrameTypeCallback, // VCM Module + public VCMReceiveCallback, // VCM Module + public VCMReceiveStatisticsCallback, // VCM Module + public VCMPacketRequestCallback, // VCM Module + public VCMFrameStorageCallback, // VCM Module + public RtcpFeedback, // RTP/RTCP Module + public RtpFeedback, // RTP/RTCP Module + public ViEFrameProviderBase +{ +public: + ViEChannel(WebRtc_Word32 channelId, WebRtc_Word32 engineId, + WebRtc_UWord32 numberOfCores, + ProcessThread& moduleProcessThread); + ~ViEChannel(); + + WebRtc_Word32 Init(); + + //----------------------------------------------------------------- + // Codecs + //----------------------------------------------------------------- + + WebRtc_Word32 SetSendCodec(const VideoCodec& videoCodec, + bool newStream = true); + + WebRtc_Word32 SetReceiveCodec(const VideoCodec& videoCodec); + + WebRtc_Word32 GetReceiveCodec(VideoCodec& videoCodec); + + WebRtc_Word32 RegisterCodecObserver(ViEDecoderObserver* observer); + + WebRtc_Word32 RegisterExternalDecoder(const WebRtc_UWord8 plType, + VideoDecoder* decoder, + bool decoderRender, + WebRtc_Word32 renderDelay); + + WebRtc_Word32 DeRegisterExternalDecoder(const WebRtc_UWord8 plType); + + WebRtc_Word32 ReceiveCodecStatistics(WebRtc_UWord32& numKeyFrames, + WebRtc_UWord32& numDeltaFrames); + + WebRtc_Word32 WaitForKeyFrame(bool wait); + + WebRtc_Word32 SetSignalPacketLossStatus(bool enable, bool onlyKeyFrames); + + //----------------------------------------------------------------- + // RTP/RTCP + //----------------------------------------------------------------- + WebRtc_Word32 SetRTCPMode(const RTCPMethod rtcpMode); + + WebRtc_Word32 GetRTCPMode(RTCPMethod& rtcpMode); + + WebRtc_Word32 SetNACKStatus(const bool enable); + + WebRtc_Word32 SetFECStatus(const bool enable, + const unsigned char payloadTypeRED, + const unsigned char payloadTypeFEC); + + WebRtc_Word32 + SetKeyFrameRequestMethod(const KeyFrameRequestMethod method); + + WebRtc_Word32 EnableTMMBR(const bool enable); + + WebRtc_Word32 EnableKeyFrameRequestCallback(const bool enable); + + WebRtc_Word32 SetSSRC(const WebRtc_UWord32 SSRC); + + WebRtc_Word32 GetLocalSSRC(WebRtc_UWord32& SSRC); + + WebRtc_Word32 GetRemoteSSRC(WebRtc_UWord32& SSRC); + + WebRtc_Word32 GetRemoteCSRC(unsigned int CSRCs[kRtpCsrcSize]); + + WebRtc_Word32 SetStartSequenceNumber(WebRtc_UWord16 sequenceNumber); + + WebRtc_Word32 SetRTCPCName(const WebRtc_Word8 rtcpCName[]); + + WebRtc_Word32 GetRTCPCName(WebRtc_Word8 rtcpCName[]); + + WebRtc_Word32 GetRemoteRTCPCName(WebRtc_Word8 rtcpCName[]); + + WebRtc_Word32 RegisterRtpObserver(ViERTPObserver* observer); + + WebRtc_Word32 RegisterRtcpObserver(ViERTCPObserver* observer); + + WebRtc_Word32 SendApplicationDefinedRTCPPacket( + const WebRtc_UWord8 subType, + WebRtc_UWord32 name, + const WebRtc_UWord8* data, + WebRtc_UWord16 dataLengthInBytes); + + WebRtc_Word32 GetSendRtcpStatistics(WebRtc_UWord16& fractionLost, + WebRtc_UWord32& cumulativeLost, + WebRtc_UWord32& extendedMax, + WebRtc_UWord32& jitterSamples, + WebRtc_Word32& rttMs); + + WebRtc_Word32 GetReceivedRtcpStatistics(WebRtc_UWord16& fractionLost, + WebRtc_UWord32& cumulativeLost, + WebRtc_UWord32& extendedMax, + WebRtc_UWord32& jitterSamples, + WebRtc_Word32& rttMs); + + WebRtc_Word32 GetRtpStatistics(WebRtc_UWord32& bytesSent, + WebRtc_UWord32& packetsSent, + WebRtc_UWord32& bytesReceived, + WebRtc_UWord32& packetsReceived) const; + + WebRtc_Word32 SetKeepAliveStatus(const bool enable, + const WebRtc_Word8 unknownPayloadType, + const WebRtc_UWord16 deltaTransmitTimeMS); + + WebRtc_Word32 GetKeepAliveStatus(bool& enable, + WebRtc_Word8& unknownPayloadType, + WebRtc_UWord16& deltaTransmitTimeMS); + + WebRtc_Word32 StartRTPDump(const char fileNameUTF8[1024], + RTPDirections direction); + + WebRtc_Word32 StopRTPDump(RTPDirections direction); + + // Implements RtcpFeedback + virtual void OnLipSyncUpdate(const WebRtc_Word32 id, + const WebRtc_Word32 audioVideoOffset); + + virtual void OnApplicationDataReceived(const WebRtc_Word32 id, + const WebRtc_UWord8 subType, + const WebRtc_UWord32 name, + const WebRtc_UWord16 length, + const WebRtc_UWord8* data); + + // Implements RtpFeedback + virtual WebRtc_Word32 OnInitializeDecoder( + const WebRtc_Word32 id, + const WebRtc_Word8 payloadType, + const WebRtc_Word8 payloadName[RTP_PAYLOAD_NAME_SIZE], + const WebRtc_UWord32 frequency, + const WebRtc_UWord8 channels, + const WebRtc_UWord32 rate); + + virtual void OnPacketTimeout(const WebRtc_Word32 id); + + virtual void OnReceivedPacket(const WebRtc_Word32 id, + const RtpRtcpPacketType packetType); + + virtual void OnPeriodicDeadOrAlive(const WebRtc_Word32 id, + const RTPAliveType alive); + + virtual void OnIncomingSSRCChanged(const WebRtc_Word32 id, + const WebRtc_UWord32 SSRC); + + virtual void OnIncomingCSRCChanged(const WebRtc_Word32 id, + const WebRtc_UWord32 CSRC, + const bool added); + + //----------------------------------------------------------------- + // Network + //----------------------------------------------------------------- + + // Soure and destination + WebRtc_Word32 SetLocalReceiver(const WebRtc_UWord16 rtpPort, + const WebRtc_UWord16 rtcpPort, + const WebRtc_Word8* ipAddress); + + WebRtc_Word32 GetLocalReceiver(WebRtc_UWord16& rtpPort, + WebRtc_UWord16& rtcpPort, + WebRtc_Word8* ipAddress) const; + + WebRtc_Word32 SetSendDestination(const WebRtc_Word8* ipAddress, + const WebRtc_UWord16 rtpPort, + const WebRtc_UWord16 rtcpPort, + const WebRtc_UWord16 sourceRtpPort, + const WebRtc_UWord16 sourceRtcpPort); + + WebRtc_Word32 GetSendDestination(WebRtc_Word8* ipAddress, + WebRtc_UWord16& rtpPort, + WebRtc_UWord16& rtcpPort, + WebRtc_UWord16& sourceRtpPort, + WebRtc_UWord16& sourceRtcpPort) const; + + WebRtc_Word32 GetSourceInfo(WebRtc_UWord16& rtpPort, + WebRtc_UWord16& rtcpPort, + WebRtc_Word8* ipAddress, + WebRtc_UWord32 ipAddressLength); + + // Start/Stop Send/Receive + WebRtc_Word32 StartSend(); + WebRtc_Word32 StopSend(); + bool Sending(); + WebRtc_Word32 StartReceive(); + WebRtc_Word32 StopReceive(); + bool Receiving(); + + // External transport + WebRtc_Word32 RegisterSendTransport(Transport& transport); + + WebRtc_Word32 DeregisterSendTransport(); + + WebRtc_Word32 ReceivedRTPPacket(const void* rtpPacket, + const WebRtc_Word32 rtpPacketLength); + + WebRtc_Word32 ReceivedRTCPPacket(const void* rtcpPacket, + const WebRtc_Word32 rtcpPacketLength); + + // IPv6 + WebRtc_Word32 EnableIPv6(); + + bool IsIPv6Enabled(); + + // Source IP address and port filter + WebRtc_Word32 SetSourceFilter(const WebRtc_UWord16 rtpPort, + const WebRtc_UWord16 rtcpPort, + const WebRtc_Word8* ipAddress); + + WebRtc_Word32 GetSourceFilter(WebRtc_UWord16& rtpPort, + WebRtc_UWord16& rtcpPort, + WebRtc_Word8* ipAddress) const; + + // ToS + WebRtc_Word32 SetToS(const WebRtc_Word32 DSCP, const bool useSetSockOpt); + + WebRtc_Word32 GetToS(WebRtc_Word32& DSCP, bool& useSetSockOpt) const; + + // GQoS + WebRtc_Word32 SetSendGQoS(const bool enable, + const WebRtc_Word32 serviceType, + const WebRtc_UWord32 maxBitrate, + const WebRtc_Word32 overrideDSCP); + + WebRtc_Word32 GetSendGQoS(bool& enabled, WebRtc_Word32& serviceType, + WebRtc_Word32& overrideDSCP) const; + + // Network settings + WebRtc_Word32 SetMTU(WebRtc_UWord16 mtu); + + WebRtc_UWord16 MaxDataPayloadLength() const; + + WebRtc_Word32 SetMaxPacketBurstSize(WebRtc_UWord16 maxNumberOfPackets); + + WebRtc_Word32 SetPacketBurstSpreadState(bool enable, + const WebRtc_UWord16 framePeriodMS); + + // Packet timout notification + WebRtc_Word32 SetPacketTimeoutNotification(bool enable, + WebRtc_UWord32 timeoutSeconds); + + // Periodic dead-or-alive reports + WebRtc_Word32 RegisterNetworkObserver(ViENetworkObserver* observer); + bool NetworkObserverRegistered(); + + WebRtc_Word32 + SetPeriodicDeadOrAliveStatus(const bool enable, + const WebRtc_UWord32 sampleTimeSeconds); + + WebRtc_Word32 SendUDPPacket(const WebRtc_Word8* data, + const WebRtc_UWord32 length, + WebRtc_Word32& transmittedBytes, + bool useRtcpSocket); + + //----------------------------------------------------------------- + // Image processing + //----------------------------------------------------------------- + WebRtc_Word32 EnableColorEnhancement(bool enable); + + //----------------------------------------------------------------- + // Register sender + //----------------------------------------------------------------- + WebRtc_Word32 + RegisterSendRtpRtcpModule(RtpRtcp& sendRtpRtcpModule); + + WebRtc_Word32 DeregisterSendRtpRtcpModule(); + + // Implements VCM::VCMReceiveCallback, getting decoded frames from + // VCM. + virtual WebRtc_Word32 FrameToRender(VideoFrame& videoFrame); + + // Implements VCM::VCMReceiveCallback, getting info about decoded + // frames from VCM. + virtual WebRtc_Word32 ReceivedDecodedReferenceFrame( + const WebRtc_UWord64 pictureId); + + //Implements VCM::VideoFrameStorageCallback + virtual WebRtc_Word32 StoreReceivedFrame( + const EncodedVideoData& frameToStore); + + // Implements VCM::VideoReceiveStatisticsCallback + virtual WebRtc_Word32 ReceiveStatistics(const WebRtc_UWord32 bitRate, + const WebRtc_UWord32 frameRate); + + // Implements VCM::VideoFrameTypeCallback + virtual WebRtc_Word32 FrameTypeRequest(const FrameType frameType); + + // Implements VCM::VideoFrameTypeCallback + virtual WebRtc_Word32 SliceLossIndicationRequest( + const WebRtc_UWord64 pictureId); + + // Implements VCM::VideoPacketRequestCallback + virtual WebRtc_Word32 ResendPackets(const WebRtc_UWord16* sequenceNumbers, + WebRtc_UWord16 length); + +#ifdef WEBRTC_SRTP + //SRTP + WebRtc_Word32 EnableSRTPSend( + const SrtpModule::CipherTypes cipherType, + const unsigned int cipherKeyLength, + const SrtpModule::AuthenticationTypes authType, + const unsigned int authKeyLength, + const unsigned int authTagLength, + const SrtpModule::SecurityLevels level, + const WebRtc_UWord8* key, + const bool useForRTCP); + + WebRtc_Word32 DisableSRTPSend(); + + WebRtc_Word32 EnableSRTPReceive( + const SrtpModule::CipherTypes cipherType, + const unsigned int cipherKeyLength, + const SrtpModule::AuthenticationTypes authType, + const unsigned int authKeyLength, + const unsigned int authTagLength, + const SrtpModule::SecurityLevels level, + const WebRtc_UWord8* key, + const bool useForRTCP); + WebRtc_Word32 DisableSRTPReceive(); +#endif + + WebRtc_Word32 RegisterExternalEncryption(Encryption* encryption); + WebRtc_Word32 DeRegisterExternalEncryption(); + + //Voice Engine + WebRtc_Word32 SetVoiceChannel(WebRtc_Word32 veChannelId, + VoEVideoSync* veSyncInterface); + WebRtc_Word32 VoiceChannel(); + + //ViEFrameProviderBase + virtual int FrameCallbackChanged(){return -1;} + + // Effect filter + WebRtc_Word32 RegisterEffectFilter(ViEEffectFilter* effectFilter); + + WebRtc_Word32 SetInverseH263Logic(const bool enable); + + // File recording + ViEFileRecorder& GetIncomingFileRecorder(); + void ReleaseIncomingFileRecorder(); + +protected: + // Thread function according to ThreadWrapper + static bool ChannelDecodeThreadFunction(void* obj); + bool ChannelDecodeProcess(); + +private: + + WebRtc_Word32 StartDecodeThread(); + WebRtc_Word32 StopDecodeThread(); + + // General members + WebRtc_Word32 _channelId; + WebRtc_Word32 _engineId; + WebRtc_UWord32 _numberOfCores; + WebRtc_UWord8 _numSocketThreads; + + // Critical sections + // Used for all registered callbacks except rendering. + CriticalSectionWrapper& _callbackCritsect; + // Use the same as above instead a seperate? + CriticalSectionWrapper& _dataCritsect; + + // Owned modules/classes + RtpRtcp& _rtpRtcp; +#ifndef WEBRTC_EXTERNAL_TRANSPORT + UdpTransport& _socketTransport; +#endif + VideoCodingModule& _vcm; + ViEReceiver& _vieReceiver; + ViESender& _vieSender; + ViESyncModule& _vieSync;//Lip syncronization + + //Uses + ProcessThread& _moduleProcessThread; + ViEDecoderObserver* _codecObserver; + bool _doKeyFrameCallbackRequest; + ViERTPObserver* _rtpObserver; + ViERTCPObserver* _rtcpObserver; + ViENetworkObserver* _networkObserver; + bool _rtpPacketTimeout; + bool _usingPacketSpread; + + // Registered members + Transport* _ptrExternalTransport; + + // Codec + bool _decoderReset; + bool _waitForKeyFrame; + + // Decoder + ThreadWrapper* _ptrDecodeThread; + + //SRTP - using seperate pointers for encryption and decryption to support + // simultaneous operations. + SrtpModule* _ptrSrtpModuleEncryption; + SrtpModule* _ptrSrtpModuleDecryption; + Encryption* _ptrExternalEncryption; + + // Effect filter and color enhancement + ViEEffectFilter* _effectFilter; + bool _colorEnhancement; + + // Time when RTT time was last reported to VCM JB. + TickTime _vcmRTTReported; + + //Recording + ViEFileRecorder _fileRecorder; +}; + +} // namespace webrtc + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_CHANNEL_H_ diff --git a/video_engine/main/source/vie_channel_manager.cc b/video_engine/main/source/vie_channel_manager.cc new file mode 100644 index 0000000000..7429c637c0 --- /dev/null +++ b/video_engine/main/source/vie_channel_manager.cc @@ -0,0 +1,594 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_channel_manager.cc + */ + +#include "vie_channel_manager.h" +#include "engine_configurations.h" +#include "vie_defines.h" + +#include "critical_section_wrapper.h" +#include "trace.h" +#include "vie_channel.h" +#include "vie_encoder.h" +#include "process_thread.h" + +// VoiceEngine +#include "voe_video_sync.h" + +namespace webrtc +{ + +ViEChannelManagerScoped::ViEChannelManagerScoped( + const ViEChannelManager& vieChannelManager) + : ViEManagerScopedBase(vieChannelManager) +{ +} + +ViEChannel* ViEChannelManagerScoped::Channel(int vieChannelId) const +{ + return static_cast + (_vieManager)->ViEChannelPtr(vieChannelId); +} +ViEEncoder* ViEChannelManagerScoped::Encoder(int vieChannelId) const +{ + return static_cast + (_vieManager)->ViEEncoderPtr(vieChannelId); +} + +bool ViEChannelManagerScoped::ChannelUsingViEEncoder(int channelId) const +{ + return (static_cast + (_vieManager))->ChannelUsingViEEncoder( channelId); +} + +// ============================================================================ +// VieChannelManager +// ============================================================================ + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + +ViEChannelManager::ViEChannelManager(int engineId, + int numberOfCores, + ViEPerformanceMonitor& viePerformanceMonitor) + : _ptrChannelIdCritsect(CriticalSectionWrapper::CreateCriticalSection()), + _engineId(engineId), _numberOfCores(numberOfCores), + _viePerformanceMonitor(viePerformanceMonitor), _channelMap(), + _freeChannelIds(new bool[kViEMaxNumberOfChannels]), + _freeChannelIdsSize(kViEMaxNumberOfChannels), _vieEncoderMap(), + _voiceSyncInterface(NULL), _voiceEngine(NULL), + _moduleProcessThread(NULL) +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, ViEId(engineId), + "ViEChannelManager::ViEChannelManager(engineId: %d) - Constructor", + engineId); + + for (int idx = 0; idx < _freeChannelIdsSize; idx++) + { + _freeChannelIds[idx] = true; + } +} +// ---------------------------------------------------------------------------- +// SetModuleProcessThread +// Initialize the thread context used by none time critical tasks in video channels. +// ---------------------------------------------------------------------------- +void ViEChannelManager::SetModuleProcessThread( ProcessThread& moduleProcessThread) +{ + assert(!_moduleProcessThread); + _moduleProcessThread = &moduleProcessThread; +} + +// ---------------------------------------------------------------------------- +// Destructor +// ---------------------------------------------------------------------------- + +ViEChannelManager::~ViEChannelManager() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, ViEId(_engineId), + "ViEChannelManager Destructor, engineId: %d", _engineId); + + while (_channelMap.Size() != 0) + { + MapItem* item = _channelMap.First(); + const int channelId = item->GetId(); + item = NULL; + DeleteChannel(channelId); + } + + if (_voiceSyncInterface) + _voiceSyncInterface->Release(); + if (_ptrChannelIdCritsect) + { + delete _ptrChannelIdCritsect; + _ptrChannelIdCritsect = NULL; + } + if (_freeChannelIds) + { + delete[] _freeChannelIds; + _freeChannelIds = NULL; + _freeChannelIdsSize = 0; + } +} + +// ---------------------------------------------------------------------------- +// CreateChannel +// +// Creates a new channel. 'channelId' will be the id of the created channel +// ---------------------------------------------------------------------------- +int ViEChannelManager::CreateChannel(int& channelId) +{ + CriticalSectionScoped cs(*_ptrChannelIdCritsect); + + // Get a free id for the new channel + if (GetFreeChannelId(channelId) == false) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "Max number of channels reached: %d", _channelMap.Size()); + return -1; + } + + ViEChannel* vieChannel = new ViEChannel(channelId, _engineId, + _numberOfCores, + *_moduleProcessThread); + if (vieChannel == NULL) + { + ReturnChannelId(channelId); + return -1; + } + if (vieChannel->Init() != 0) + { + // Could not init channel + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s could not init channel", __FUNCTION__, channelId); + ReturnChannelId(channelId); + delete vieChannel; + vieChannel = NULL; + return -1; + + } + // There is no ViEEncoder for this channel, create one with default settings + ViEEncoder* vieEncoder = new ViEEncoder(_engineId, channelId, + _numberOfCores, + *_moduleProcessThread); + if (vieEncoder == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s(videoChannelId: %d) - Could not create a new encoder", + __FUNCTION__, channelId); + delete vieChannel; + return -1; + } + + // Add to the map + if (_vieEncoderMap.Insert(channelId, vieEncoder) != 0) + { + // Could not add to the map + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s: Could not add new encoder for video channel %d", + __FUNCTION__, channelId); + delete vieChannel; + delete vieEncoder; + return -1; + } + _channelMap.Insert(channelId, vieChannel); + // Register the channel at the encoder + RtpRtcp* ptrSendRtpRtcpModule = vieEncoder->SendRtpRtcpModule(); + if (vieChannel->RegisterSendRtpRtcpModule(*ptrSendRtpRtcpModule) != 0) + { + assert(false); + _vieEncoderMap.Erase(channelId); + _channelMap.Erase(channelId); + ReturnChannelId(channelId); + delete vieChannel; + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, channelId), + "%s: Could not register rtp module %d", __FUNCTION__, + channelId); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// CreateChannel +// +// Creates a channel and attaches to an already existing ViEEncoder +// ---------------------------------------------------------------------------- + +int ViEChannelManager::CreateChannel(int& channelId, int originalChannel) +{ + CriticalSectionScoped cs(*_ptrChannelIdCritsect); + + // Check that originalChannel already exists + ViEEncoder* vieEncoder = ViEEncoderPtr(originalChannel); + if (vieEncoder == NULL) + { + // The original channel doesn't exist + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s: Original channel doesn't exist", __FUNCTION__, + originalChannel); + return -1; + } + // Get a free id for the new channel + if (GetFreeChannelId(channelId) == false) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "Max number of channels reached: %d", _channelMap.Size()); + return -1; + } + ViEChannel* vieChannel = new ViEChannel(channelId, _engineId, + _numberOfCores, + *_moduleProcessThread); + if (vieChannel == NULL) + { + ReturnChannelId(channelId); + return -1; + } + if (vieChannel->Init() != 0) + { + // Could not init channel + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s could not init channel", __FUNCTION__, channelId); + ReturnChannelId(channelId); + delete vieChannel; + vieChannel = NULL; + return -1; + } + if (_vieEncoderMap.Insert(channelId, vieEncoder) != 0) + { + // Could not add to the map + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s: Could not add new encoder for video channel %d", + __FUNCTION__, channelId); + ReturnChannelId(channelId); + delete vieChannel; + return -1; + } + + // Set the same encoder settings for the channel as used by the master channel. + // Do this before attaching rtp module to ensure all rtp cihldren has the same codec type + VideoCodec encoder; + if (vieEncoder->GetEncoder(encoder) == 0) + { + vieChannel->SetSendCodec(encoder); + } + _channelMap.Insert(channelId, vieChannel); + + // Register the channel at the encoder + RtpRtcp* ptrSendRtpRtcpModule = vieEncoder->SendRtpRtcpModule(); + if (vieChannel->RegisterSendRtpRtcpModule(*ptrSendRtpRtcpModule) != 0) + { + assert(false); + _vieEncoderMap.Erase(channelId); + _channelMap.Erase(channelId); + ReturnChannelId(channelId); + delete vieChannel; + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, channelId), + "%s: Could not register rtp module %d", __FUNCTION__, + channelId); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// DeleteChannel +// ---------------------------------------------------------------------------- + +int ViEChannelManager::DeleteChannel(int channelId) +{ + ViEChannel* vieChannel = NULL; + ViEEncoder* vieEncoder = NULL; + { + // Write lock to make sure no one is using the channel + ViEManagerWriteScoped wl(*this); + + // Protect the map + CriticalSectionScoped cs(*_ptrChannelIdCritsect); + + MapItem* mapItem = _channelMap.Find(channelId); + if (mapItem == NULL) + { + // No such channel + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s Channel doesn't exist: %d", __FUNCTION__, channelId); + return -1; + } + vieChannel = reinterpret_cast (mapItem->GetItem()); + _channelMap.Erase(mapItem); + // Deregister the channel from the ViEEncoder to stop the media flow + vieChannel->DeregisterSendRtpRtcpModule(); + ReturnChannelId(channelId); + + // Find the encoder object + mapItem = _vieEncoderMap.Find(channelId); + if (mapItem == NULL) + { + assert(false); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), + "%s ViEEncoder not found for channel %d", __FUNCTION__, + channelId); + return -1; + } + // Get the ViEEncoder item + vieEncoder = reinterpret_cast (mapItem->GetItem()); + + // Check if other channels are using the same encoder + if (ChannelUsingViEEncoder(channelId)) + { + // Don't delete the ViEEncoder, at least on other channel is using it. + WEBRTC_TRACE( + webrtc::kTraceInfo, + webrtc::kTraceVideo, + ViEId(_engineId), + "%s ViEEncoder removed from map for channel %d, not deleted", + __FUNCTION__, channelId); + vieEncoder = NULL; + } else + { + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), + "%s ViEEncoder deleted for channel %d", __FUNCTION__, + channelId); + // Delete later when we've released the critsect + } + // We can't erase the item before we've checked for other channels using same ViEEncoder + _vieEncoderMap.Erase(mapItem); + + } + // Leave the write critsect before deleting the objects. + // Deleting a channel can cause other objects, such as renderers, to be deleted and might take time + if (vieEncoder) + { + delete vieEncoder; + } + delete vieChannel; + + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), + "%s Channel %d deleted", __FUNCTION__, channelId); + return 0; +} + +// ---------------------------------------------------------------------------- +// Channel +// +// Returns a pointer to the channel with id 'channelId' +// ---------------------------------------------------------------------------- + +ViEChannel* ViEChannelManager::ViEChannelPtr(int channelId) const +{ + CriticalSectionScoped cs(*_ptrChannelIdCritsect); + MapItem* mapItem = _channelMap.Find(channelId); + if (mapItem == NULL) + { + // No such channel + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s Channel doesn't exist: %d", __FUNCTION__, channelId); + return NULL; + } + ViEChannel* vieChannel = reinterpret_cast (mapItem->GetItem()); + return vieChannel; +} + +// ---------------------------------------------------------------------------- +// GetChannels +// +// Adds all channels to channelMap +// ---------------------------------------------------------------------------- + +void ViEChannelManager::GetViEChannels(MapWrapper& channelMap) +{ + CriticalSectionScoped cs(*_ptrChannelIdCritsect); + if (channelMap.Size() == 0) + { + // No channels + return; + } + // Add all items to 'channelMap' + for (MapItem* item = _channelMap.First(); item != NULL; item + = _channelMap.Next(item)) + { + channelMap.Insert(item->GetId(), item->GetItem()); + } + return; +} + +// ---------------------------------------------------------------------------- +// ViEEncoderPtr +// +// Gets the ViEEncoder used as input for videoChannelId +// ---------------------------------------------------------------------------- + +ViEEncoder* ViEChannelManager::ViEEncoderPtr(int videoChannelId) const +{ + CriticalSectionScoped cs(*_ptrChannelIdCritsect); + MapItem* mapItem = _vieEncoderMap.Find(videoChannelId); + if (mapItem == NULL) + { + // No ViEEncoder for this channel... + return NULL; + } + ViEEncoder* vieEncoder = static_cast (mapItem->GetItem()); + return vieEncoder; +} + +// ---------------------------------------------------------------------------- +// GetFreeChannelId +// +// Returns true if we found a new channel id, freeChannelId, false otherwise +// ---------------------------------------------------------------------------- +bool ViEChannelManager::GetFreeChannelId(int& freeChannelId) +{ + CriticalSectionScoped cs(*_ptrChannelIdCritsect); + int idx = 0; + while (idx < _freeChannelIdsSize) + { + if (_freeChannelIds[idx] == true) + { + // We've found a free id, allocate it and return + _freeChannelIds[idx] = false; + freeChannelId = idx + kViEChannelIdBase; + return true; + } + idx++; + } + // No free channel id + freeChannelId = -1; + return false; +} + +// ---------------------------------------------------------------------------- +// ReturnChannelID +// +// Returns a previously allocated channel id +// ---------------------------------------------------------------------------- +void ViEChannelManager::ReturnChannelId(int channelId) +{ + CriticalSectionScoped cs(*_ptrChannelIdCritsect); + assert(channelId < kViEMaxNumberOfChannels+kViEChannelIdBase && channelId>=kViEChannelIdBase); + _freeChannelIds[channelId - kViEChannelIdBase] = true; +} + +// ---------------------------------------------------------------------------- +// ChannelUsingViEEncoder +// +// Returns true if at least one nother channel is using the same encoder +// ---------------------------------------------------------------------------- + +bool ViEChannelManager::ChannelUsingViEEncoder(int channelId) const +{ + CriticalSectionScoped cs(*_ptrChannelIdCritsect); + MapItem* channelItem = _vieEncoderMap.Find(channelId); + if (channelItem == NULL) + { + // No ViEEncoder for this channel... + return false; + } + ViEEncoder* channelEncoder = + static_cast (channelItem->GetItem()); + + // Loop through all other channels to see if anyone points at the same ViEEncoder + MapItem* mapItem = _vieEncoderMap.First(); + while (mapItem) + { + ViEEncoder* vieEncoder = static_cast (mapItem->GetItem()); + if (mapItem->GetId() != channelId) + { + if (channelEncoder == static_cast (mapItem->GetItem())) + { + // We've found another channel using the same ViEEncoder + return true; + } + } + mapItem = _vieEncoderMap.Next(mapItem); + } + return false; +} + +// ---------------------------------------------------------------------------- +// SetVoiceEngine +// +// Set the voice engine instance to be used by all video channels. We are interested in the voice engine sync interfaces +// ---------------------------------------------------------------------------- +int ViEChannelManager::SetVoiceEngine(VoiceEngine* voiceEngine) +{ + + // Write lock to make sure no one is using the channel + ViEManagerWriteScoped wl(*this); + + CriticalSectionScoped cs(*_ptrChannelIdCritsect); + + VoEVideoSync* syncInterface = NULL; + if (voiceEngine) + { + // Get new sync interface; + syncInterface = VoEVideoSync::GetInterface(voiceEngine); + if (!syncInterface) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s Can't get audio sync interface from VoiceEngine.", + __FUNCTION__); + + if (syncInterface) + { + syncInterface->Release(); + } + return -1; + } + } + + for (MapItem* item = _channelMap.First(); item != NULL; item + = _channelMap.Next(item)) + { + ViEChannel* channel = static_cast (item->GetItem()); + assert(channel); + channel->SetVoiceChannel(-1, syncInterface); + } + if (_voiceSyncInterface) + { + _voiceSyncInterface->Release(); + } + _voiceEngine = voiceEngine; + _voiceSyncInterface = syncInterface; + return 0; + +} +VoiceEngine* ViEChannelManager::GetVoiceEngine() +{ + CriticalSectionScoped cs(*_ptrChannelIdCritsect); + return _voiceEngine; + +} + +// ---------------------------------------------------------------------------- +// ConnectVoiceChannel +// +// Enables lip sync of the channel. +// ---------------------------------------------------------------------------- +int ViEChannelManager::ConnectVoiceChannel(int channelId, int audioChannelId) +{ + CriticalSectionScoped cs(*_ptrChannelIdCritsect); + + if (_voiceSyncInterface == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, channelId), + "No VoE set"); + return -1; + } + ViEChannel* channel = ViEChannelPtr(channelId); + if (!channel) + { + return -1; + } + return channel->SetVoiceChannel(audioChannelId, _voiceSyncInterface); + +} + +// ---------------------------------------------------------------------------- +// DisconnectVoiceChannel +// +// Disables lip sync of the channel. +// ---------------------------------------------------------------------------- +int ViEChannelManager::DisconnectVoiceChannel(int channelId) +{ + CriticalSectionScoped cs(*_ptrChannelIdCritsect); + ViEChannel* channel = ViEChannelPtr(channelId); + if (channel) + { + channel->SetVoiceChannel(-1, NULL); + return 0; + } else + { + return -1; + } +} +} // namespace webrtc diff --git a/video_engine/main/source/vie_channel_manager.h b/video_engine/main/source/vie_channel_manager.h new file mode 100644 index 0000000000..2fb6c8499b --- /dev/null +++ b/video_engine/main/source/vie_channel_manager.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_channel_manager.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_CHANNEL_MANAGER_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_CHANNEL_MANAGER_H_ + +// Defines +#include "engine_configurations.h" +#include "vie_defines.h" +#include "typedefs.h" +#include "map_wrapper.h" +#include "vie_manager_base.h" + +namespace webrtc +{ +class CriticalSectionWrapper; +//class VoiceEngine; +class ProcessThread; +class ViEChannel; +class VoEVideoSync; +class ViEPerformanceMonitor; +class ViEEncoder; +class VoiceEngine; + +// ------------------------------------------------------------------ +// ViEChannelManager +// ------------------------------------------------------------------ + +class ViEChannelManager: private ViEManagerBase +{ + friend class ViEChannelManagerScoped; + +public: + ViEChannelManager(int engineId, int numberOfCores, + ViEPerformanceMonitor& viePerformanceMonitor); + ~ViEChannelManager(); + + void SetModuleProcessThread(ProcessThread& moduleProcessThread); + int CreateChannel(int& channelId); + int CreateChannel(int& channelId, int originalChannel); + int DeleteChannel(int channelId); + int SetVoiceEngine(VoiceEngine* voiceEngine); + int ConnectVoiceChannel(int channelId, int audioChannelId); + int DisconnectVoiceChannel(int channelId); + VoiceEngine* GetVoiceEngine(); + +private: + // Used by ViEChannelScoped, forcing a manager user to use scoped + ViEChannel* ViEChannelPtr(int channelId) const; + void GetViEChannels(MapWrapper& channelMap); + + // Methods used by ViECaptureScoped and ViEEncoderScoped + ViEEncoder* ViEEncoderPtr(int videoChannelId) const; + + bool GetFreeChannelId(int& freeChannelId); + void ReturnChannelId(int channelId); + + // Returns true if at least one other channels uses the same ViEEncoder as channelId + bool ChannelUsingViEEncoder(int channelId) const; + + // Members + CriticalSectionWrapper* _ptrChannelIdCritsect; // protecting _channelMap and _freeChannelIds + int _engineId; + int _numberOfCores; + ViEPerformanceMonitor& _viePerformanceMonitor; + MapWrapper _channelMap; + bool* _freeChannelIds; + int _freeChannelIdsSize; + // Encoder + MapWrapper _vieEncoderMap; // Channel id -> ViEEncoder + VoEVideoSync* _voiceSyncInterface; + VoiceEngine* _voiceEngine; + ProcessThread* _moduleProcessThread; +}; + +// ------------------------------------------------------------------ +// ViEChannelManagerScoped +// ------------------------------------------------------------------ +class ViEChannelManagerScoped: private ViEManagerScopedBase +{ +public: + ViEChannelManagerScoped(const ViEChannelManager& vieChannelManager); + ViEChannel* Channel(int vieChannelId) const; + ViEEncoder* Encoder(int vieChannelId) const; + + // Returns true if at lease one other channels uses the same ViEEncoder as channelId + bool ChannelUsingViEEncoder(int channelId) const; +}; + +} //namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_CHANNEL_MANAGER_H_ diff --git a/video_engine/main/source/vie_codec_impl.cc b/video_engine/main/source/vie_codec_impl.cc new file mode 100644 index 0000000000..a2f3aa9fac --- /dev/null +++ b/video_engine/main/source/vie_codec_impl.cc @@ -0,0 +1,977 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_codec_impl.cc + */ + +#include "vie_codec_impl.h" + +// Defines +#include "engine_configurations.h" +#include "vie_defines.h" + +#include "video_coding.h" +#include "trace.h" +#include "vie_errors.h" +#include "vie_impl.h" +#include "vie_channel.h" +#include "vie_channel_manager.h" +#include "vie_encoder.h" +#include "vie_input_manager.h" +#include "vie_capturer.h" + +#include + +namespace webrtc +{ + +// ---------------------------------------------------------------------------- +// GetInterface +// ---------------------------------------------------------------------------- + +ViECodec* ViECodec::GetInterface(VideoEngine* videoEngine) +{ +#ifdef WEBRTC_VIDEO_ENGINE_CODEC_API + if (videoEngine == NULL) + { + return NULL; + } + VideoEngineImpl* vieImpl = reinterpret_cast (videoEngine); + ViECodecImpl* vieCodecImpl = vieImpl; + (*vieCodecImpl)++; // Increase ref count + + return vieCodecImpl; +#else + return NULL; +#endif +} + +// ---------------------------------------------------------------------------- +// Release +// +// Releases the interface, i.e. reduces the reference counter. The number of +// remaining references is returned, -1 if released too many times. +// ---------------------------------------------------------------------------- + +int ViECodecImpl::Release() +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, _instanceId, + "ViECodecImpl::Release()"); + (*this)--; // Decrease ref count + + WebRtc_Word32 refCount = GetCount(); + if (refCount < 0) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, _instanceId, + "ViECodec released too many times"); + SetLastError(kViEAPIDoesNotExist); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, _instanceId, + "ViECodec reference count: %d", refCount); + return refCount; +} + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + +ViECodecImpl::ViECodecImpl() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, _instanceId, + "ViECodecImpl::ViECodecImpl() Ctor"); +} + +// ---------------------------------------------------------------------------- +// Destructor +// ---------------------------------------------------------------------------- + +ViECodecImpl::~ViECodecImpl() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, _instanceId, + "ViECodecImpl::~ViECodecImpl() Dtor"); +} + +// Available codecs +// ---------------------------------------------------------------------------- +// NumberOfCodecs +// +// Returns the number of available codecs +// ---------------------------------------------------------------------------- + +int ViECodecImpl::NumberOfCodecs() const +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s", + __FUNCTION__); + + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + // +2 because of FEC(RED and ULPFEC) + return (int) (VideoCodingModule::NumberOfCodecs() + 2); +} + +// ---------------------------------------------------------------------------- +// GetCodec +// +// Return the video codec with listNumber +// ---------------------------------------------------------------------------- + +int ViECodecImpl::GetCodec(const unsigned char listNumber, + VideoCodec& videoCodec) const +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(listNumber: %d, codecType: %d)", __FUNCTION__, listNumber); + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + if (listNumber == VideoCodingModule::NumberOfCodecs()) + { + memset(&videoCodec, 0, sizeof(webrtc::VideoCodec)); + strcpy(videoCodec.plName, "RED"); + videoCodec.codecType = kVideoCodecRED; + videoCodec.plType = VCM_RED_PAYLOAD_TYPE; + } + else if (listNumber == VideoCodingModule::NumberOfCodecs() + 1) + { + memset(&videoCodec, 0, sizeof(webrtc::VideoCodec)); + strcpy(videoCodec.plName, "ULPFEC"); + videoCodec.codecType = kVideoCodecULPFEC; + videoCodec.plType = VCM_ULPFEC_PAYLOAD_TYPE; + } + else if (VideoCodingModule::Codec(listNumber, &videoCodec) + != VCM_OK) + { + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: Could not get codec for listNumber: %u", __FUNCTION__, + listNumber); + SetLastError(kViECodecInvalidArgument); + return -1; + } + return 0; +} + +// Codec settings +// ---------------------------------------------------------------------------- +// SetSendCodec +// +// Sets the send codec for videoChannel +// This call will affect all channels using the same encoder +// ---------------------------------------------------------------------------- + +int ViECodecImpl::SetSendCodec(const int videoChannel, + const VideoCodec& videoCodec) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId,videoChannel), + "%s(videoChannel: %d, codecType: %d)", __FUNCTION__, + videoChannel, videoCodec.codecType); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_instanceId, videoChannel), + "%s: codec: %d, plType: %d, width: %d, height: %d, bitrate: %d" + "maxBr: %d, minBr: %d, frameRate: %d)", __FUNCTION__, + videoCodec.codecType, videoCodec.plType, videoCodec.width, + videoCodec.height, videoCodec.startBitrate, + videoCodec.maxBitrate, videoCodec.minBitrate, + videoCodec.maxFramerate); + + if (CodecValid(videoCodec) == false) + { + // Error logged + SetLastError(kViECodecInvalidCodec); + return -1; + } + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (vieChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s: No channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViECodecInvalidChannelId); + return -1; + } + + // Set a maxBitrate if the user hasn't... + VideoCodec videoCodecInternal; + memcpy(&videoCodecInternal, &videoCodec, sizeof(webrtc::VideoCodec)); + if (videoCodecInternal.maxBitrate == 0) + { + // Max is one bit per pixel ... + videoCodecInternal.maxBitrate = (videoCodecInternal.width + * videoCodecInternal.height * videoCodecInternal.maxFramerate) + / 1000; + if (videoCodecInternal.startBitrate > videoCodecInternal.maxBitrate) + { + // ... but should'nt limit the set start bitrate. + videoCodecInternal.maxBitrate = videoCodecInternal.startBitrate; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_instanceId, + videoChannel), + "%s: New max bitrate set to %d kbps", __FUNCTION__, + videoCodecInternal.maxBitrate); + } + + ViEEncoder* vieEncoder = cs.Encoder(videoChannel); + if (vieEncoder == NULL) + { + assert(false); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: No encoder found for channel %d", __FUNCTION__); + SetLastError(kViECodecInvalidChannelId); + return -1; + } + + // We need to check if the codec settings changed, + // then we need a new SSRC + bool newRtpStream = false; + + VideoCodec encoder; + vieEncoder->GetEncoder(encoder); + if (encoder.codecType != videoCodecInternal.codecType || + encoder.width != videoCodecInternal.width || + encoder.height != videoCodecInternal.height) + { + if (cs.ChannelUsingViEEncoder(videoChannel)) + { + // We don't allow changing codec type or size when several + // channels share encoder. + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Settings differs from other channels using encoder", + __FUNCTION__); + SetLastError(kViECodecInUse); + return -1; + } + newRtpStream = true; + } + + ViEInputManagerScoped is(_inputManager); + ViEFrameProviderBase* frameProvider = NULL; + + // Stop the media flow while reconfiguring + vieEncoder->Pause(); + + // Check if we have a frame provider that is a camera and can provide this + // codec for us. + bool useCaptureDeviceAsEncoder = false; + frameProvider = is.FrameProvider(vieEncoder); + if (frameProvider) + { + ViECapturer* vieCapture = static_cast (frameProvider); + // Try to get preencoded. Nothing to do if it is not supported. + if (vieCapture && vieCapture->PreEncodeToViEEncoder(videoCodecInternal, + *vieEncoder, + videoChannel) == 0) + { + useCaptureDeviceAsEncoder = true; + } + } + + // Update the encoder settings if we are not using a capture device capable + // of this codec. + if (!useCaptureDeviceAsEncoder + && vieEncoder->SetEncoder(videoCodecInternal) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Could not change encoder for channel %d", __FUNCTION__, + videoChannel); + SetLastError(kViECodecUnknownError); + return -1; + } + + // Give the channel the new information + if (vieChannel->SetSendCodec(videoCodecInternal, newRtpStream) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Could not set send codec for channel %d", __FUNCTION__, + videoChannel); + SetLastError(kViECodecUnknownError); + return -1; + } + + // Update the protection mode, we might be switching NACK/FEC + vieEncoder->UpdateProtectionMethod(); + // Get new best format for frame provider + if (frameProvider) + { + frameProvider->FrameCallbackChanged(); + } + // Restart the media flow + if (newRtpStream) + { + // Stream settings changed, make sure we get a key frame + vieEncoder->SendKeyFrame(); + } + vieEncoder->Restart(); + + return 0; +} + +// ---------------------------------------------------------------------------- +// GetSendCodec +// +// Gets the current send codec +// ---------------------------------------------------------------------------- + +int ViECodecImpl::GetSendCodec(const int videoChannel, + VideoCodec& videoCodec) const +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(videoChannel: %d)", + __FUNCTION__, videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEEncoder* vieEncoder = cs.Encoder(videoChannel); + if (vieEncoder == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: No encoder for channel %d", __FUNCTION__, videoChannel); + SetLastError(kViECodecInvalidChannelId); + return -1; + } + + return vieEncoder->GetEncoder(videoCodec); +} + +// ---------------------------------------------------------------------------- +// SetReceiveCodec +// +// Registers a possible receive codec +// ---------------------------------------------------------------------------- + +int ViECodecImpl::SetReceiveCodec(const int videoChannel, + const VideoCodec& videoCodec) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(videoChannel: %d, codecType: %d)", __FUNCTION__, + videoChannel, videoCodec.codecType); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_instanceId, videoChannel), + "%s: codec: %d, plType: %d, width: %d, height: %d, bitrate: %d," + "maxBr: %d, minBr: %d, frameRate: %d", __FUNCTION__, + videoCodec.codecType, videoCodec.plType, videoCodec.width, + videoCodec.height, videoCodec.startBitrate, + videoCodec.maxBitrate, videoCodec.minBitrate, + videoCodec.maxFramerate); + + if (CodecValid(videoCodec) == false) + { + // Error logged + SetLastError(kViECodecInvalidCodec); + return -1; + } + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (vieChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s: No channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViECodecInvalidChannelId); + return -1; + } + + if (vieChannel->SetReceiveCodec(videoCodec) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId, + videoChannel), + "%s: Could not set receive codec for channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViECodecUnknownError); + return -1; + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// GetReceiveCodec +// +// Gets the current receive codec +// ---------------------------------------------------------------------------- + +int ViECodecImpl::GetReceiveCodec(const int videoChannel, + VideoCodec& videoCodec) const +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(videoChannel: %d, codecType: %d)", __FUNCTION__, + videoChannel, videoCodec.codecType); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (vieChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s: No channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViECodecInvalidChannelId); + return -1; + } + + if (vieChannel->GetReceiveCodec(videoCodec) != 0) + { + SetLastError(kViECodecUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// GetCodecConfigParameters +// +// Gets the codec config parameters to be sent out-of-band. +// ---------------------------------------------------------------------------- + +int ViECodecImpl::GetCodecConfigParameters( + const int videoChannel, + unsigned char configParameters[kConfigParameterSize], + unsigned char& configParametersSize) const +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(videoChannel: %d)", + __FUNCTION__, videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEEncoder* vieEncoder = cs.Encoder(videoChannel); + if (vieEncoder == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: No encoder for channel %d", __FUNCTION__, videoChannel); + SetLastError(kViECodecInvalidChannelId); + return -1; + } + + if (vieEncoder->GetCodecConfigParameters(configParameters, + configParametersSize) != 0) + { + SetLastError(kViECodecUnknownError); + return -1; + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// SetImageScaleStatus +// +// Enables scaling of the encoded image instead of padding black border or +// cropping +// ---------------------------------------------------------------------------- + +int ViECodecImpl::SetImageScaleStatus(const int videoChannel, const bool enable) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(videoChannel: %d, enable: %d)", __FUNCTION__, videoChannel, + enable); + + ViEChannelManagerScoped cs(_channelManager); + ViEEncoder* vieEncoder = cs.Encoder(videoChannel); + if (vieEncoder == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s: No channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViECodecInvalidChannelId); + return -1; + } + + if (vieEncoder->ScaleInputImage(enable) != 0) + { + SetLastError(kViECodecUnknownError); + return -1; + } + return 0; +} + +// Codec statistics +// ---------------------------------------------------------------------------- +// GetSendCodecStastistics +// +// Get codec statistics for outgoing stream +// ---------------------------------------------------------------------------- + + +int ViECodecImpl::GetSendCodecStastistics(const int videoChannel, + unsigned int& keyFrames, + unsigned int& deltaFrames) const +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(videoChannel %d)", + __FUNCTION__, videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEEncoder* vieEncoder = cs.Encoder(videoChannel); + if (vieEncoder == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: No send codec for channel %d", __FUNCTION__, + videoChannel); + SetLastError(kViECodecInvalidChannelId); + return -1; + } + + if (vieEncoder->SendCodecStatistics((WebRtc_UWord32&) keyFrames, + (WebRtc_UWord32&) deltaFrames) != 0) + { + SetLastError(kViECodecUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// GetReceiveCodecStastistics +// +// Get codec statistics for incoming stream +// ---------------------------------------------------------------------------- + +int ViECodecImpl::GetReceiveCodecStastistics(const int videoChannel, + unsigned int& keyFrames, + unsigned int& deltaFrames) const +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(videoChannel: %d, codecType: %d)", __FUNCTION__, + videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (vieChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s: No channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViECodecInvalidChannelId); + return -1; + } + if (vieChannel->ReceiveCodecStatistics((WebRtc_UWord32&) keyFrames, + (WebRtc_UWord32&) deltaFrames) != 0) + { + SetLastError(kViECodecUnknownError); + return -1; + } + return 0; + +} + +// Callbacks +// ---------------------------------------------------------------------------- +// SetKeyFrameRequestCallbackStatus +// +// Enables a kecallback for keyframe request instead of using RTCP +// ---------------------------------------------------------------------------- + +int ViECodecImpl::SetKeyFrameRequestCallbackStatus(const int videoChannel, + const bool enable) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(videoChannel: %d)", + __FUNCTION__, videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (vieChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s: No channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViECodecInvalidChannelId); + return -1; + } + if (vieChannel->EnableKeyFrameRequestCallback(enable) != 0) + { + SetLastError(kViECodecUnknownError); + return -1; + } + return 0; + +} + +// ---------------------------------------------------------------------------- +// SetSignalKeyPacketLossStatus +// +// Triggers a key frame request when there is packet loss in a received key +// frame +// ---------------------------------------------------------------------------- + +int ViECodecImpl::SetSignalKeyPacketLossStatus(const int videoChannel, + const bool enable, + const bool onlyKeyFrames) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(videoChannel: %d, enable: %d, onlyKeyFrames: %d)", + __FUNCTION__, videoChannel, enable); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (vieChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s: No channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViECodecInvalidChannelId); + return -1; + } + if (vieChannel->SetSignalPacketLossStatus(enable, onlyKeyFrames) != 0) + { + SetLastError(kViECodecUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// RegisterEncoderObserver +// ---------------------------------------------------------------------------- + +int ViECodecImpl::RegisterEncoderObserver(const int videoChannel, + ViEEncoderObserver& observer) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s", + __FUNCTION__); + + ViEChannelManagerScoped cs(_channelManager); + ViEEncoder* vieEncoder = cs.Encoder(videoChannel); + if (vieEncoder == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: No encoder for channel %d", __FUNCTION__, videoChannel); + SetLastError(kViECodecInvalidChannelId); + return -1; + } + if (vieEncoder->RegisterCodecObserver(&observer) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Could not register codec observer at channel", + __FUNCTION__); + SetLastError(kViECodecObserverAlreadyRegistered); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// DeregisterEncoderObserver +// ---------------------------------------------------------------------------- + +int ViECodecImpl::DeregisterEncoderObserver(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s", + __FUNCTION__); + + ViEChannelManagerScoped cs(_channelManager); + ViEEncoder* vieEncoder = cs.Encoder(videoChannel); + if (vieEncoder == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: No encoder for channel %d", __FUNCTION__, videoChannel); + SetLastError(kViECodecInvalidChannelId); + return -1; + } + if (vieEncoder->RegisterCodecObserver(NULL) != 0) + { + SetLastError(kViECodecObserverNotRegistered); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// RegisterDecoderObserver +// ---------------------------------------------------------------------------- + +int ViECodecImpl::RegisterDecoderObserver(const int videoChannel, + ViEDecoderObserver& observer) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s", + __FUNCTION__); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (vieChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s: No channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViECodecInvalidChannelId); + return -1; + } + if (vieChannel->RegisterCodecObserver(&observer) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Could not register codec observer at channel", + __FUNCTION__); + SetLastError(kViECodecObserverAlreadyRegistered); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// DeregisterDecoderObserver +// ---------------------------------------------------------------------------- + +int ViECodecImpl::DeregisterDecoderObserver(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s", + __FUNCTION__); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (vieChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s: No channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViECodecInvalidChannelId); + return -1; + } + if (vieChannel->RegisterCodecObserver(NULL) != 0) + { + SetLastError(kViECodecObserverNotRegistered); + return -1; + } + + return 0; +} + +// Force a key frame +// ---------------------------------------------------------------------------- +// SendKeyFrame +// +// Force the next frame to be a key frame +// ---------------------------------------------------------------------------- + +int ViECodecImpl::SendKeyFrame(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(videoChannel: %d)", __FUNCTION__, videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEEncoder* vieEncoder = cs.Encoder(videoChannel); + if (vieEncoder == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s: No channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViECodecInvalidChannelId); + return -1; + } + if (vieEncoder->SendKeyFrame() != 0) + { + SetLastError(kViECodecUnknownError); + return -1; + } + return 0; + +} + +// ---------------------------------------------------------------------------- +// WaitForFirstKeyFrame +// +// Forc the next frame to be a key frame +// ---------------------------------------------------------------------------- + +int ViECodecImpl::WaitForFirstKeyFrame(const int videoChannel, const bool wait) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(videoChannel: %d, wait: %d)", __FUNCTION__, videoChannel, + wait); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (vieChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s: No channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViECodecInvalidChannelId); + return -1; + } + if (vieChannel->WaitForKeyFrame(wait) != 0) + { + SetLastError(kViECodecUnknownError); + return -1; + } + return 0; +} + +// H263 Specific +// ---------------------------------------------------------------------------- +// SetInverseH263Logic +// +// Used to interoperate with old MS H.263 where key frames are marked as delta +// and the oposite. +// ---------------------------------------------------------------------------- + +int ViECodecImpl::SetInverseH263Logic(int videoChannel, bool enable) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(videoChannel: %d)", __FUNCTION__, videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (vieChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId,videoChannel), "%s: No channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViECodecInvalidChannelId); + return -1; + } + if (vieChannel->SetInverseH263Logic(enable) != 0) + { + SetLastError(kViECodecUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// CodecValid +// ---------------------------------------------------------------------------- + +bool ViECodecImpl::CodecValid(const VideoCodec& videoCodec) +{ + // Check plName matches codecType + if (videoCodec.codecType == kVideoCodecRED) + { +#if defined(WIN32) + if (_strnicmp(videoCodec.plName, "red", 3) == 0) +#elif defined(WEBRTC_MAC_INTEL) || defined(WEBRTC_LINUX) + if (strncasecmp(videoCodec.plName, "red",3) == 0) +#endif + { + // We only care about the type and name for red + return true; + } + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, -1, + "Codec type doesn't match plName", videoCodec.plType); + return false; + } + else if (videoCodec.codecType == kVideoCodecULPFEC) + { +#if defined(WIN32) + if (_strnicmp(videoCodec.plName, "ULPFEC", 6) == 0) +#elif defined(WEBRTC_MAC_INTEL)|| defined(WEBRTC_LINUX) + if (strncasecmp(videoCodec.plName, "ULPFEC",6) == 0) +#endif + { + // We only care about the type and name for ULPFEC + return true; + } + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, -1, + "Codec type doesn't match plName", videoCodec.plType); + return false; + } + else if ((videoCodec.codecType == kVideoCodecH263 && + strncmp(videoCodec.plName, "H263", 4) == 0) + || (videoCodec.codecType == kVideoCodecH263 + && strncmp(videoCodec.plName, "H263-1998", 9) == 0) + || (videoCodec.codecType == kVideoCodecVP8 + && strncmp(videoCodec.plName, "VP8", 4) == 0) + || (videoCodec.codecType == kVideoCodecI420 + && strncmp(videoCodec.plName, "I420", 4) == 0) + || (videoCodec.codecType == kVideoCodecH264 + && strncmp(videoCodec.plName, "H264", 4) == 0)) + // || (videoCodec.codecType == kVideoCodecMPEG4 + // && strncmp(videoCodec.plName, "MP4V-ES", 7) == 0) + { + // ok + } + else + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, -1, + "Codec type doesn't match plName", videoCodec.plType); + return false; + } + + // pltype + if (videoCodec.plType == 0 && videoCodec.plType > 127) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, -1, + "Invalid codec payload type: %d", videoCodec.plType); + return false; + } + + // Size + if (videoCodec.width > kViEMaxCodecWidth || videoCodec.height + > kViEMaxCodecHeight) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, -1, + "Invalid codec size: %u x %u", videoCodec.width, + videoCodec.height); + return false; + } + + if (videoCodec.startBitrate < kViEMinCodecBitrate) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, -1, + "Invalid startBitrate: %u", videoCodec.startBitrate); + return false; + } + if (videoCodec.minBitrate < kViEMinCodecBitrate) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, -1, + "Invalid minBitrate: %u", videoCodec.minBitrate); + return false; + } + if (videoCodec.startBitrate < kViEMinCodecBitrate) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, -1, + "Invalid minBitrate: %u", videoCodec.minBitrate); + return false; + } + + if (videoCodec.codecType == kVideoCodecH263) + { + if ((videoCodec.width == 704 && videoCodec.height == 576) + || (videoCodec.width == 352 && videoCodec.height == 288) + || (videoCodec.width == 176 && videoCodec.height == 144) + || (videoCodec.width == 128 && videoCodec.height == 96)) + { + // ok + } + else + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, -1, + "Invalid size for H.263"); + return false; + } + } + return true; +} +} // namespace webrtc diff --git a/video_engine/main/source/vie_codec_impl.h b/video_engine/main/source/vie_codec_impl.h new file mode 100644 index 0000000000..b47c7a2281 --- /dev/null +++ b/video_engine/main/source/vie_codec_impl.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_codec_impl.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_CODEC_IMPL_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_CODEC_IMPL_H_ + +#include "vie_defines.h" + +#include "typedefs.h" +#include "vie_ref_count.h" +#include "vie_shared_data.h" +#include "vie_codec.h" + +namespace webrtc +{ + +// ---------------------------------------------------------------------------- +// ViECodecImpl +// ---------------------------------------------------------------------------- + +class ViECodecImpl : public virtual ViESharedData, + public ViECodec, + public ViERefCount +{ +public: + virtual int Release(); + + // Available codecs + virtual int NumberOfCodecs() const; + + virtual int GetCodec(const unsigned char listNumber, + VideoCodec& videoCodec) const; + + // Codec settings + virtual int SetSendCodec(const int videoChannel, + const VideoCodec& videoCodec); + + virtual int GetSendCodec(const int videoChannel, + VideoCodec& videoCodec) const; + + virtual int SetReceiveCodec(const int videoChannel, + const VideoCodec& videoCodec); + + virtual int GetReceiveCodec(const int videoChannel, + VideoCodec& videoCodec) const; + + virtual int GetCodecConfigParameters( + const int videoChannel, + unsigned char configParameters[kConfigParameterSize], + unsigned char& configParametersSize) const; + + // Input image scaling + virtual int SetImageScaleStatus(const int videoChannel, const bool enable); + + // Codec statistics + virtual int GetSendCodecStastistics(const int videoChannel, + unsigned int& keyFrames, + unsigned int& deltaFrames) const; + + virtual int GetReceiveCodecStastistics(const int videoChannel, + unsigned int& keyFrames, + unsigned int& deltaFrames) const; + + // Callbacks + virtual int SetKeyFrameRequestCallbackStatus(const int videoChannel, + const bool enable); + + virtual int SetSignalKeyPacketLossStatus(const int videoChannel, + const bool enable, + const bool onlyKeyFrames = false); + + virtual int RegisterEncoderObserver(const int videoChannel, + ViEEncoderObserver& observer); + + virtual int DeregisterEncoderObserver(const int videoChannel); + + virtual int RegisterDecoderObserver(const int videoChannel, + ViEDecoderObserver& observer); + + virtual int DeregisterDecoderObserver(const int videoChannel); + + // Key frame settings + virtual int SendKeyFrame(const int videoChannel); + + virtual int WaitForFirstKeyFrame(const int videoChannel, const bool wait); + + // H263 Specific + virtual int SetInverseH263Logic(int videoChannel, bool enable); + +protected: + ViECodecImpl(); + virtual ~ViECodecImpl(); + +private: + bool CodecValid(const VideoCodec& videoCodec); +}; +} // namespace webrtc + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_CODEC_IMPL_H_ diff --git a/video_engine/main/source/vie_defines.h b/video_engine/main/source/vie_defines.h new file mode 100644 index 0000000000..42f720b5c5 --- /dev/null +++ b/video_engine/main/source/vie_defines.h @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_defines.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_DEFINES_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_DEFINES_H_ + +#include "engine_configurations.h" + +#ifdef WEBRTC_MAC_INTEL +#include +#include +#endif + +#ifdef ANDROID +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +namespace webrtc +{ +// =================================================== +// ViE Defines +// =================================================== + +// General +enum { kViEMinKeyRequestIntervalMs = 300}; + +// ViEBase +enum { kViEMaxNumberOfChannels = 4}; +enum { kViEVersionMaxMessageSize = 1024 }; +enum { kViEMaxModuleVersionSize = 960 }; + +// ViECapture +enum { kViEMaxCaptureDevices=10}; +// Width used if no send codec has been set when a capture device is started +enum { kViECaptureDefaultWidth = 352}; +// Height used if no send codec has been set when a capture device is started +enum { kViECaptureDefaultHeight = 288}; +enum { kViECaptureDefaultFramerate = 30}; +enum { kViECaptureMaxSnapshotWaitTimeMs = 500 }; + +// ViECodec +enum { kViEMaxCodecWidth = 1920}; +enum { kViEMaxCodecHeight = 1200}; +enum { kViEMaxCodecFramerate = 60}; +enum { kViEMinCodecBitrate = 30}; + +// ViEEncryption +enum { kViEMaxSrtpKeyLength = 30}; +enum { kViEMinSrtpEncryptLength = 16}; +enum { kViEMaxSrtpEncryptLength = 256}; +enum { kViEMaxSrtpAuthSh1Length = 20}; +enum { kViEMaxSrtpTagAuthNullLength = 12}; +enum { kViEMaxSrtpKeyAuthNullLength = 256}; + +// ViEExternalCodec + +// ViEFile +enum { kViEMaxFilePlayers = 3}; + +// ViEImageProcess + +// ViENetwork +enum { kViEMaxMtu = 1500}; +enum { kViESocketThreads = 1}; +enum { kViENumReceiveSocketBuffers = 500}; + +// ViERender +// Max valid time set in SetRenderTimeoutImage +enum { kViEMaxRenderTimeoutTimeMs = 10000}; +// Min valid time set in SetRenderTimeoutImage +enum { kViEMinRenderTimeoutTimeMs = 33}; +enum { kViEDefaultRenderDelayMs = 10}; + +// ViERTP_RTCP +enum { kNackHistorySize = 400}; + +// Id definitions +enum { + kViEChannelIdBase=0x0, + kViEChannelIdMax=0xFF, + kViECaptureIdBase=0x1001, + kViECaptureIdMax=0x10FF, + kViEFileIdBase=0x2000, + kViEFileIdMax=0x200F, + kViEDummyChannelId=0xFFFF +}; + +// Module id +// Create a unique id based on the ViE instance id and the +// channel id. ViE id > 0 and 0 <= channel id <= 255 + +inline int ViEId(const int vieId, const int channelId = -1) +{ + if (channelId == -1) + { + return (int) ((vieId << 16) + kViEDummyChannelId); + } + return (int) ((vieId << 16) + channelId); +} + +inline int ViEModuleId(const int vieId, const int channelId = -1) +{ + if (channelId == -1) + { + return (int) ((vieId << 16) + kViEDummyChannelId); + } + return (int) ((vieId << 16) + channelId); +} + +inline int ChannelId(const int moduleId) +{ + return (int) (moduleId & 0xffff); +} + + +// ============================================================================ +// Platform specifics +// ============================================================================ + +//------------------------------------- +// Windows +//------------------------------------- + +#if defined(_WIN32) +// Build information macros + #if defined(_DEBUG) + #define BUILDMODE TEXT("d") + #elif defined(DEBUG) + #define BUILDMODE TEXT("d") + #elif defined(NDEBUG) + #define BUILDMODE TEXT("r") + #else + #define BUILDMODE TEXT("?") + #endif + + #define BUILDTIME TEXT(__TIME__) + #define BUILDDATE TEXT(__DATE__) + + // example: "Oct 10 2002 12:05:30 r" + #define BUILDINFO BUILDDATE TEXT(" ") BUILDTIME TEXT(" ") BUILDMODE + + + #define RENDER_MODULE_TYPE kRenderWindows + // Warning pragmas + #pragma warning(disable: 4351) // new behavior: elements of array 'XXX' will be default initialized + #pragma warning(disable: 4355) // 'this' : used in base member initializer list + #pragma warning(disable: 4731) // frame pointer register 'ebp' modified by inline assembly code + + // Include libraries + #pragma comment( lib, "winmm.lib" ) +#ifndef WEBRTC_EXTERNAL_TRANSPORT + #pragma comment( lib, "ws2_32.lib" ) + #pragma comment( lib, "Iphlpapi.lib" ) // _GetAdaptersAddresses +#endif +#endif + + +//------------------------------------- +// Mac +//------------------------------------- + +#ifdef WEBRTC_MAC_INTEL + #define SLEEP(x) usleep(x * 1000) + + // Build information macros + #define TEXT(x) x + #if defined(_DEBUG) + #define BUILDM//#define webrtc::kFileFormatAviFile 3 +//#define __LINUX__ // needed for InterObjects +ODE TEXT("d") + #elif defined(DEBUG) + #define BUILDMODE TEXT("d") + #elif defined(NDEBUG) + #define BUILDMODE TEXT("r") + #else + #define BUILDMODE TEXT("?") + #endif + + #define BUILDTIME TEXT(__TIME__) + #define BUILDDATE TEXT(__DATE__) + + // example: "Oct 10 2002 12:05:30 r" + #define BUILDINFO BUILDDATE TEXT(" ") BUILDTIME TEXT(" ") BUILDMODE + + #define RENDER_MODULE_TYPE kRenderWindows +#endif + +//#define webrtc::kFileFormatAviFile 3 +//#define __LINUX__ // needed for InterObjects + +//------------------------------------- +// Linux +//------------------------------------- + +#ifndef ANDROID +#ifdef WEBRTC_LINUX + +// Build information macros +#if defined(_DEBUG) + #define BUILDMODE "d" +#elif defined(DEBUG) + #define BUILDMODE "d" +#elif defined(NDEBUG) + #define BUILDMODE "r" +#else + #define BUILDMODE "?" +#endif + +#define BUILDTIME __TIME__ +#define BUILDDATE __DATE__ + +// example: "Oct 10 2002 12:05:30 r" +#define BUILDINFO BUILDDATE " " BUILDTIME " " BUILDMODE + +#endif // ifdef WEBRTC_LINUX +#endif // ifndef ANDROID + +#ifdef ANDROID + + #define DWORD unsigned long int + #define FALSE 0 + #define TRUE 1 + #define LONG int + #define LONGLONG long long + #define FAR + #define __cdecl + + #if defined(_DEBUG) + #define BUILDMODE "d" + #elif defined(DEBUG) + #define BUILDMODE "d" + #elif defined(NDEBUG) + #define BUILDMODE "r" + #else + #define BUILDMODE "?" + #endif + + #define BUILDTIME __TIME__ + #define BUILDDATE __DATE__ + + // example: "Oct 10 2002 12:05:30 r" + #define BUILDINFO BUILDDATE " " BUILDTIME " " BUILDMODE + + namespace + { + void Sleep(unsigned long x) + { + timespec t; + t.tv_sec = x/1000; + t.tv_nsec = (x-(x/1000)*1000)*1000000; + nanosleep(&t,NULL); + } + + DWORD timeGetTime() + { + struct timeval tv; + struct timezone tz; + unsigned long val; + + gettimeofday(&tv, &tz); + val= tv.tv_sec*1000+ tv.tv_usec/1000; + return(val); + } + } + + #define SLEEP(x) ::Sleep(x) + #define GET_TIME_IN_MS timeGetTime + +#endif // #ifdef ANDROID + +} //namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_DEFINES_H_ + diff --git a/video_engine/main/source/vie_encoder.cc b/video_engine/main/source/vie_encoder.cc new file mode 100644 index 0000000000..82c3889ee4 --- /dev/null +++ b/video_engine/main/source/vie_encoder.cc @@ -0,0 +1,1017 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * ViEEncoder.cpp + */ + +#include "vie_encoder.h" +#include "vie_defines.h" + +#include "critical_section_wrapper.h" +#include "process_thread.h" +#include "rtp_rtcp.h" +#include "video_coding.h" +#include "video_coding_defines.h" +#include "video_codec_interface.h" +#include "vie_codec.h" +#include "vie_image_process.h" +#include "tick_util.h" +#include "trace.h" + +#include +namespace webrtc { + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + +ViEEncoder::ViEEncoder(WebRtc_Word32 engineId, WebRtc_Word32 channelId, + WebRtc_UWord32 numberOfCores, + ProcessThread& moduleProcessThread) + : + _engineId(engineId), + _channelId(channelId), + _numberOfCores(numberOfCores), + _vcm(*webrtc::VideoCodingModule::Create(ViEModuleId(engineId, channelId))), + _vpm(*webrtc::VideoProcessingModule::Create(ViEModuleId(engineId, channelId))), + _rtpRtcp(*RtpRtcp::CreateRtpRtcp(ViEModuleId(engineId, + channelId), + false)), + _callbackCritsect(*CriticalSectionWrapper::CreateCriticalSection()), + _dataCritsect(*CriticalSectionWrapper::CreateCriticalSection()), + _paused(false), _timeLastIntraRequestMs(0), + _channelsDroppingDeltaFrames(0), _dropNextFrame(false), + _fecEnabled(false), _codecObserver(NULL), _effectFilter(NULL), + _moduleProcessThread(moduleProcessThread), _hasReceivedSLI(false), + _pictureIdSLI(0), _hasReceivedRPSI(false), _pictureIdRPSI(0), + _fileRecorder(channelId) +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, ViEId(engineId, channelId), + "%s(engineId: %d) 0x%p - Constructor", __FUNCTION__, engineId, + this); + + _vcm.InitializeSender(); + _vpm.EnableTemporalDecimation(true); + + // Enable/disable content analysis: off by default for now + _vpm.EnableContentAnalysis(false); + + _moduleProcessThread.RegisterModule(&_vcm); + _rtpRtcp.InitSender(); + _rtpRtcp.RegisterIncomingVideoCallback(this); + _rtpRtcp.RegisterIncomingRTCPCallback(this); + _moduleProcessThread.RegisterModule(&_rtpRtcp); + + // + _qmCallback = new QMTestVideoSettingsCallback(); + _qmCallback->RegisterVPM(&_vpm); + _qmCallback->RegisterVCM(&_vcm); + _qmCallback->SetNumOfCores(_numberOfCores); + +#ifdef VIDEOCODEC_VP8 + VideoCodec videoCodec; + if (_vcm.Codec(webrtc::kVideoCodecVP8, &videoCodec) == VCM_OK) + { + _vcm.RegisterSendCodec(&videoCodec, _numberOfCores, _rtpRtcp.MaxDataPayloadLength()); + _rtpRtcp.RegisterSendPayload(videoCodec.plName, videoCodec.plType); + } + else + { + assert(false); + } +#else + VideoCodec videoCodec; + if (_vcm.Codec(webrtc::kVideoCodecI420, &videoCodec) == VCM_OK) + { + _vcm.RegisterSendCodec(&videoCodec, _numberOfCores, _rtpRtcp.MaxDataPayloadLength()); + _rtpRtcp.RegisterSendPayload(videoCodec.plName, videoCodec.plType); + } + else + { + assert(false); + } +#endif + + if (_vcm.RegisterTransportCallback(this) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: VCM::RegisterTransportCallback failure"); + } + if (_vcm.RegisterSendStatisticsCallback(this) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: VCM::RegisterSendStatisticsCallback failure"); + } + + if (_vcm.RegisterVideoQMCallback(_qmCallback) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "VCM::RegisterQMCallback failure"); + } +} + +// ---------------------------------------------------------------------------- +// Destructor +// ---------------------------------------------------------------------------- + +ViEEncoder::~ViEEncoder() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "ViEEncoder Destructor 0x%p, engineId: %d", this, _engineId); + + if (_rtpRtcp.NumberChildModules() > 0) + { + assert(false); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "Channels still attached %d, leaking memory", + _rtpRtcp.NumberChildModules()); + return; + } + _moduleProcessThread.DeRegisterModule(&_vcm); + _moduleProcessThread.DeRegisterModule(&_vpm); + _moduleProcessThread.DeRegisterModule(&_rtpRtcp); + delete &_vcm; + delete &_vpm; + delete &_rtpRtcp; + delete &_callbackCritsect; + delete &_dataCritsect; +} + +// ============================================================================ +// Start/Stop +// ============================================================================ + +// ---------------------------------------------------------------------------- +// Pause / Retart +// +// Call this to start/stop sending +// ---------------------------------------------------------------------------- + +void ViEEncoder::Pause() +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + CriticalSectionScoped cs(_dataCritsect); + _paused = true; +} + +void ViEEncoder::Restart() +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + CriticalSectionScoped cs(_dataCritsect); + _paused = false; +} + +// ---------------------------------------------------------------------------- +// DropDeltaAfterKey +// +// Drops the first delta frame after a key frame is encoded. +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEEncoder::DropDeltaAfterKey(bool enable) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s(%d)", __FUNCTION__, enable); + CriticalSectionScoped cs(_dataCritsect); + + if (enable) + { + _channelsDroppingDeltaFrames++; + } else + { + _channelsDroppingDeltaFrames--; + if (_channelsDroppingDeltaFrames < 0) + { + _channelsDroppingDeltaFrames = 0; + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, + _channelId), + "%s: Called too many times", __FUNCTION__, enable); + return -1; + } + } + return 0; +} + +// ============================================================================ +// Codec settigns +// ============================================================================ + +// ---------------------------------------------------------------------------- +// NumberOfCodecs +// ---------------------------------------------------------------------------- + +WebRtc_UWord8 ViEEncoder::NumberOfCodecs() +{ + return _vcm.NumberOfCodecs(); +} + +// ---------------------------------------------------------------------------- +// GetCodec +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEEncoder::GetCodec(WebRtc_UWord8 listIndex, + webrtc::VideoCodec& videoCodec) +{ + if (_vcm.Codec(listIndex, &videoCodec) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: Could not get codec", + __FUNCTION__); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// External encoder +// ---------------------------------------------------------------------------- +WebRtc_Word32 ViEEncoder::RegisterExternalEncoder( + webrtc::VideoEncoder* encoder, + WebRtc_UWord8 plType) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: pltype %u", __FUNCTION__, plType); + + if (encoder == NULL) + return -1; + + if (_vcm.RegisterExternalEncoder(encoder, plType) != VCM_OK) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "Could not register external encoder"); + return -1; + } + return 0; +} + +WebRtc_Word32 ViEEncoder::DeRegisterExternalEncoder(WebRtc_UWord8 plType) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: pltype %u", __FUNCTION__, plType); + + webrtc::VideoCodec currentSendCodec; + if (_vcm.SendCodec(¤tSendCodec) == VCM_OK) + { + currentSendCodec.startBitrate = _vcm.Bitrate(); + } + + if (_vcm.RegisterExternalEncoder(NULL, plType) != VCM_OK) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "Could not deregister external encoder"); + return -1; + } + + // If the external encoder is the current send codec use vcm internal encoder + if (currentSendCodec.plType == plType) + { + WebRtc_UWord16 maxDataPayloadLength = _rtpRtcp.MaxDataPayloadLength(); + if (_vcm.RegisterSendCodec(¤tSendCodec, _numberOfCores, + maxDataPayloadLength) != VCM_OK) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, + _channelId), + "Could not use internal encoder"); + return -1; + } + } + return 0; +} + +// ---------------------------------------------------------------------------- +// SetEncoder +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEEncoder::SetEncoder(const webrtc::VideoCodec& videoCodec) +{ + WEBRTC_TRACE( webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: CodecType: %d, width: %u, height: %u, maxPayloadLength: %u", + __FUNCTION__, videoCodec.codecType, videoCodec.width, + videoCodec.height); + + // Multiply startBitrate by 1000 because RTP module changed in API. + if (_rtpRtcp.SetSendBitrate(videoCodec.startBitrate * 1000, + videoCodec.minBitrate, videoCodec.maxBitrate) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "Could not set RTP module bitrates"); + return -1; + } + + // Setting target width and height for VPM + if (_vpm.SetTargetResolution(videoCodec.width, videoCodec.height, videoCodec.maxFramerate) != VPM_OK) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "Could not set VPM target dimensions"); + return -1; + + } + + if (_rtpRtcp.RegisterSendPayload(videoCodec.plName, videoCodec.plType) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "Could register RTP module video payload"); + return -1; + } + + WebRtc_UWord16 maxDataPayloadLength = _rtpRtcp.MaxDataPayloadLength(); + + // update QM with MaxDataPayloadLength + _qmCallback->SetMaxPayloadLength(maxDataPayloadLength); + + if (_vcm.RegisterSendCodec(&videoCodec, _numberOfCores, + maxDataPayloadLength) != VCM_OK) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "Could not register send codec"); + return -1; + } + _dataCritsect.Enter(); + memcpy(&_sendCodec, &videoCodec, sizeof(_sendCodec)); // Copy current send codec + _dataCritsect.Leave(); + + // Set this module as sending right away, let the + // slave module in the channel start and stop sending... + if (_rtpRtcp.Sending() == false) + { + if (_rtpRtcp.SetSendingStatus(true) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, + _channelId), + "Could start RTP module sending"); + return -1; + } + } + return 0; +} + +// ---------------------------------------------------------------------------- +// GetSendCodec +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEEncoder::GetEncoder(webrtc::VideoCodec& videoCodec) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + if (_vcm.SendCodec(&videoCodec) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "Could not get VCM send codec"); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// GetCodecConfigParameters +// +// Only valid for H.264 and MPEG-4 +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEEncoder::GetCodecConfigParameters( + unsigned char configParameters[kConfigParameterSize], + unsigned char& configParametersSize) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + WebRtc_Word32 numParameters = + _vcm.CodecConfigParameters(configParameters, kConfigParameterSize); + if (numParameters <= 0) + { + configParametersSize = 0; + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "Could not get config parameters"); + return -1; + } + configParametersSize = (unsigned char) numParameters; + return 0; +} + +// ---------------------------------------------------------------------------- +// ScaleInputImage +// +// The input image will be scaled if the codec resolution differs from the +// image resolution of the input image, otherwise will the image be +// cropped/padded. Default: crop/pad. +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEEncoder::ScaleInputImage(bool enable) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s(enable %d)", __FUNCTION__, enable); + + VideoFrameResampling resamplingMode = kFastRescaling; + if (enable == true) + { + // Currently not supported. + //resamplingMode = kInterpolation; + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s not supported", + __FUNCTION__, enable); + return -1; + } + _vpm.SetInputFrameResampleMode(resamplingMode); + + return 0; +} + +//============================================================================= +// RTP settings +//============================================================================= + +// ---------------------------------------------------------------------------- +// GetRtpRtcpModule +// ---------------------------------------------------------------------------- + +RtpRtcp* ViEEncoder::SendRtpRtcpModule() +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + return &_rtpRtcp; +} + +//============================================================================= +// Data flow +//============================================================================= + + +// ---------------------------------------------------------------------------- +// DeliverFrame +// Implements ViEFrameCallback::DeliverFrame +// Receive videoFrame to be encoded from a provider (capture or file) +// ---------------------------------------------------------------------------- + +void ViEEncoder::DeliverFrame(int id, webrtc::VideoFrame& videoFrame, + int numCSRCs, + const WebRtc_UWord32 CSRC[kRtpCsrcSize]) +{ + WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: %llu", __FUNCTION__, videoFrame.TimeStamp()); + + { + CriticalSectionScoped cs(_dataCritsect); + if (_paused || _rtpRtcp.SendingMedia() == false) + { + // We've passed or we have no channels attached, don't encode + return; + } + if (_dropNextFrame) + { + // Drop this frame + WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, ViEId(_engineId, + _channelId), + "%s: Dropping frame %llu after a key fame", + __FUNCTION__, videoFrame.TimeStamp()); + _dropNextFrame = false; + return; + } + } + // Set the frame timestamp + const WebRtc_UWord32 timeStamp = 90 * (WebRtc_UWord32) videoFrame.RenderTimeMs(); + videoFrame.SetTimeStamp(timeStamp); + { + // Send to effect filter, if registered by user. + CriticalSectionScoped cs(_callbackCritsect); + if (_effectFilter) + { + _effectFilter->Transform(videoFrame.Length(), videoFrame.Buffer(), + videoFrame.TimeStamp(), + videoFrame.Width(), videoFrame.Height()); + } + } + // Record un-encoded frame. + _fileRecorder.RecordVideoFrame(videoFrame); + // Make sure the CSRC list is correct. + if (numCSRCs > 0) + { + WebRtc_UWord32 tempCSRC[kRtpCsrcSize]; + for (int i = 0; i < numCSRCs; i++) + { + if (CSRC[i] == 1) + { + tempCSRC[i] = _rtpRtcp.SSRC(); + } + else + { + tempCSRC[i] = CSRC[i]; + } + } + _rtpRtcp.SetCSRCs(tempCSRC, (WebRtc_UWord8) numCSRCs); + } + +#ifdef VIDEOCODEC_VP8 + if (_vcm.SendCodec() == webrtc::kVideoCodecVP8) + { + webrtc::CodecSpecificInfo codecSpecificInfo; + codecSpecificInfo.codecType = webrtc::kVideoCodecUnknown; + + if (_hasReceivedSLI || _hasReceivedRPSI) + { + webrtc::VideoCodec currentSendCodec; + _vcm.SendCodec(¤tSendCodec); + if (currentSendCodec.codecType == webrtc::kVideoCodecVP8) + { + codecSpecificInfo.codecType = webrtc::kVideoCodecVP8; + codecSpecificInfo.codecSpecific.VP8.hasReceivedRPSI = _hasReceivedRPSI; + codecSpecificInfo.codecSpecific.VP8.hasReceivedSLI = _hasReceivedSLI; + codecSpecificInfo.codecSpecific.VP8.pictureIdRPSI = _pictureIdRPSI; + codecSpecificInfo.codecSpecific.VP8.pictureIdSLI = _pictureIdSLI; + } + _hasReceivedSLI = false; + _hasReceivedRPSI = false; + } + // Pass frame via preprocessor + VideoFrame *decimatedFrame = NULL; + const int ret = _vpm.PreprocessFrame(&videoFrame, &decimatedFrame); + if (ret == 1) + { + // Drop this frame + return; + } + else if (ret != VPM_OK) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: Error preprocessing frame %u", __FUNCTION__, + videoFrame.TimeStamp()); + return; + } + + VideoContentMetrics* contentMetrics = NULL; + contentMetrics = _vpm.ContentMetrics(); + + if (_vcm.AddVideoFrame + (*decimatedFrame, contentMetrics, &codecSpecificInfo) != VCM_OK) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: Error encoding frame %u", __FUNCTION__, + videoFrame.TimeStamp()); + } + return; + } +#endif + // Pass frame via preprocessor + VideoFrame *decimatedFrame = NULL; + const int ret = _vpm.PreprocessFrame(&videoFrame, &decimatedFrame); + if (ret == 1) + { + // Drop this frame + return; + } + else if (ret != VPM_OK) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: Error preprocessing frame %u", __FUNCTION__, videoFrame.TimeStamp()); + return; + } + if (_vcm.AddVideoFrame(*decimatedFrame) != VCM_OK) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: Error encoding frame %u", + __FUNCTION__, videoFrame.TimeStamp()); + } +} +// ---------------------------------------------------------------------------- +// DeliverFrame +// Implements ViEFrameCallback::DelayChanged +// ---------------------------------------------------------------------------- +void ViEEncoder::DelayChanged(int id, int frameDelay) + +{ + WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: %u", __FUNCTION__, frameDelay); + + _rtpRtcp.SetCameraDelay(frameDelay); + _fileRecorder.SetFrameDelay(frameDelay); +} +// ---------------------------------------------------------------------------- +// GetPreferedFrameSettings +// Implements ViEFrameCallback::GetPreferedFrameSettings +// Fetch the widh, height and frame rate prefered by this encoder. +// ---------------------------------------------------------------------------- + +int ViEEncoder::GetPreferedFrameSettings(int &width, int &height, + int &frameRate) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + webrtc::VideoCodec videoCodec; + memset(&videoCodec, 0, sizeof(videoCodec)); + if (_vcm.SendCodec(&videoCodec) != VCM_OK) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "Could not get VCM send codec"); + return -1; + } + + width = videoCodec.width; + height = videoCodec.height; + frameRate = videoCodec.maxFramerate; + return 0; + +} +// ---------------------------------------------------------------------------- +// SendKeyFrame +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEEncoder::SendKeyFrame() +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + return _vcm.FrameTypeRequest(kVideoFrameKey); +} + +// ---------------------------------------------------------------------------- +// SendCodecStatistics +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEEncoder::SendCodecStatistics(WebRtc_UWord32& numKeyFrames, + WebRtc_UWord32& numDeltaFrames) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + webrtc::VCMFrameCount sentFrames; + if (_vcm.SentFrameCount(sentFrames) != VCM_OK) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not get sent frame information", __FUNCTION__); + return -1; + } + numKeyFrames = sentFrames.numKeyFrames; + numDeltaFrames = sentFrames.numDeltaFrames; + return 0; + +} + +//============================================================================= +// Loss protection +//============================================================================= + +// ---------------------------------------------------------------------------- +// UpdateProtectionMethod +// +// Updated protection method to VCM to get correct packetization sizes +// FEC has larger overhead than NACK -> set FEC if used +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEEncoder::UpdateProtectionMethod() +{ + // Get FEC status + bool fecEnabled = false; + WebRtc_UWord8 dummyPTypeRed = 0; + WebRtc_UWord8 dummyPTypeFEC = 0; + + WebRtc_Word32 error = _rtpRtcp.GenericFECStatus(fecEnabled, dummyPTypeRed, + dummyPTypeFEC); + if (error) + { + return -1; + } + + if (_fecEnabled != fecEnabled) + { + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: FEC status ", __FUNCTION__, fecEnabled); + + _vcm.SetVideoProtection(webrtc::kProtectionFEC, fecEnabled); + if (fecEnabled) + { + _vcm.RegisterProtectionCallback(this); + } else + { + _vcm.RegisterProtectionCallback(NULL); + } + // Need to reregister the send codec in order to set the new MTU + webrtc::VideoCodec codec; + if (_vcm.SendCodec(&codec) == 0) + { + WebRtc_UWord16 maxPayLoad = _rtpRtcp.MaxDataPayloadLength(); + codec.startBitrate = _vcm.Bitrate(); + if (_vcm.RegisterSendCodec(&codec, _numberOfCores, maxPayLoad) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, + _channelId), + "%s: Failed to update Sendcodec when enabling FEC", + __FUNCTION__, fecEnabled); + return -1; + } + } + _fecEnabled = fecEnabled; + } + + // Update NACK status + _vcm.SetVideoProtection(webrtc::kProtectionNack, _rtpRtcp.NACK() != kNackOff); + + return 0; +} + +//============================================================================= +// Implementation of VideoPacketizationCallback from VCM +//============================================================================= + +// ---------------------------------------------------------------------------- +// SendData +// ---------------------------------------------------------------------------- +WebRtc_Word32 ViEEncoder::SendData(const FrameType frameType, + const WebRtc_UWord8 payloadType, + const WebRtc_UWord32 timeStamp, + const WebRtc_UWord8* payloadData, + const WebRtc_UWord32 payloadSize, + const webrtc::RTPFragmentationHeader& fragmentationHeader) +{ + { + CriticalSectionScoped cs(_dataCritsect); + if (_paused) + { + // Paused, don't send this packet + return 0; + } + if (_channelsDroppingDeltaFrames && frameType == webrtc::kVideoFrameKey) + { + WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, ViEId(_engineId, + _channelId), + "%s: Sending key frame, drop next frame", __FUNCTION__); + _dropNextFrame = true; + } + } + // New encoded data, hand over to the rtp module + WebRtc_Word32 retVal = _rtpRtcp.SendOutgoingData(frameType, payloadType, + timeStamp, payloadData, + payloadSize, + &fragmentationHeader); + return retVal; +} + +//============================================================================= +// Implementation of VideoProtectionCallback from VCM +//============================================================================= + +// ---------------------------------------------------------------------------- +// ProtectionRequest +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEEncoder::ProtectionRequest(const WebRtc_UWord8 deltaFECRate, + const WebRtc_UWord8 keyFECRate, + const bool nack) +{ + WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: deltaFECRate: %u, keyFECRate: %u, nack: %d", __FUNCTION__, + deltaFECRate, keyFECRate, nack); + + if (_rtpRtcp.SetFECCodeRate(keyFECRate, deltaFECRate) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Could not update FEC code rate", __FUNCTION__); + } + return 0; +} + +//============================================================================= +// Implementation of VideoSendStatisticsCallback from VCM +//============================================================================= + +// ---------------------------------------------------------------------------- +// SendStatistics +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEEncoder::SendStatistics(const WebRtc_UWord32 bitRate, + const WebRtc_UWord32 frameRate) +{ + CriticalSectionScoped cs(_callbackCritsect); + if (_codecObserver) + { + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: bitrate %u, framerate %u", __FUNCTION__, bitRate, + frameRate); + _codecObserver->OutgoingRate(_channelId, frameRate, bitRate); + } + return 0; +} + +// ---------------------------------------------------------------------------- +// RegisterCodecObserver +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViEEncoder::RegisterCodecObserver(ViEEncoderObserver* observer) +{ + CriticalSectionScoped cs(_callbackCritsect); + if (observer) + { + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: observer added", __FUNCTION__); + if (_codecObserver) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, + _channelId), + "%s: observer already set.", __FUNCTION__); + return -1; + } + _codecObserver = observer; + } else + { + if (_codecObserver == NULL) + { + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, + _channelId), + "%s: observer does not exist.", __FUNCTION__); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: observer removed", __FUNCTION__); + _codecObserver = NULL; + } + return 0; +} + +//============================================================================= +// Implementation of RtcpFeedback +//============================================================================= + +void ViEEncoder::OnSLIReceived(const WebRtc_Word32 id, + const WebRtc_UWord8 pictureId) +{ + _pictureIdSLI = pictureId; + _hasReceivedSLI = true; +} + +void ViEEncoder::OnRPSIReceived(const WebRtc_Word32 id, + const WebRtc_UWord64 pictureId) +{ + _pictureIdRPSI = pictureId; + _hasReceivedRPSI = true; +} + +//============================================================================= +// Implementation of RtpVideoFeedback +//============================================================================= + +// ---------------------------------------------------------------------------- +// OnReceivedIntraFrameRequest +// ---------------------------------------------------------------------------- + +void ViEEncoder::OnReceivedIntraFrameRequest(const WebRtc_Word32 id, + const WebRtc_UWord8 message) +{ + // Key frame request from other side, signal to VCM + WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s", __FUNCTION__); + + if (_timeLastIntraRequestMs + kViEMinKeyRequestIntervalMs + > TickTime::MillisecondTimestamp()) + { + WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Not not encoding new intra due to timing", __FUNCTION__); + return; + } + // Default message == 0... + if (message == 0) + { + _vcm.FrameTypeRequest(kVideoFrameKey); + } else + { + _vcm.FrameTypeRequest((FrameType) message); + } + _timeLastIntraRequestMs = TickTime::MillisecondTimestamp(); + return; +} + +// ---------------------------------------------------------------------------- +// OnNetworkChanged +// ---------------------------------------------------------------------------- +void ViEEncoder::OnNetworkChanged(const WebRtc_Word32 id, + const WebRtc_UWord32 minBitrateBps, + const WebRtc_UWord32 maxBitrateBps, + const WebRtc_UWord8 fractionLost, + const WebRtc_UWord16 roundTripTimeMs, + const WebRtc_UWord16 bwEstimateKbitMin, + const WebRtc_UWord16 bwEstimateKbitMax) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s(minBitrateBps: %u, maxBitrateBps: %u,fractionLost: %u, rttMs: %u, bwEstMinKbit: %u, bwEstMaxKbit: %u", + __FUNCTION__, minBitrateBps, maxBitrateBps, fractionLost, + roundTripTimeMs, bwEstimateKbitMin, bwEstimateKbitMax); + _vcm.SetChannelParameters(minBitrateBps / 1000, fractionLost, roundTripTimeMs); + return; +} + +WebRtc_Word32 ViEEncoder::RegisterEffectFilter(ViEEffectFilter* effectFilter) +{ + CriticalSectionScoped cs(_callbackCritsect); + if (effectFilter == NULL) + { + if (_effectFilter == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, + _channelId), + "%s: no effect filter added", __FUNCTION__); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: deregister effect filter", __FUNCTION__); + } else + { + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + "%s: register effect", __FUNCTION__); + if (_effectFilter) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, + _channelId), + "%s: effect filter already added ", __FUNCTION__); + return -1; + } + } + _effectFilter = effectFilter; + return 0; +} + +ViEFileRecorder& ViEEncoder::GetOutgoingFileRecorder() +{ + return _fileRecorder; +} + +//============================================================================= +// Implementation of Video QM settings callback: +// Callback to be called from VCM to update VPM of frame rate and size +//============================================================================= + +ViEEncoder::QMTestVideoSettingsCallback::QMTestVideoSettingsCallback(): +_vpm(NULL), +_vcm(NULL) +{ + +} + +void ViEEncoder::QMTestVideoSettingsCallback:: + RegisterVPM(VideoProcessingModule *vpm) +{ + _vpm = vpm; +} + +void ViEEncoder::QMTestVideoSettingsCallback:: + RegisterVCM(VideoCodingModule *vcm) +{ + _vcm = vcm; +} + +WebRtc_Word32 ViEEncoder::QMTestVideoSettingsCallback::SetVideoQMSettings + (const WebRtc_UWord32 frameRate, + const WebRtc_UWord32 width, + const WebRtc_UWord32 height) +{ + + WebRtc_Word32 retVal = 0; + retVal = _vpm->SetTargetResolution(width, height, frameRate); + //Initialize codec with new values + if (!retVal) + { + // first get current settings + VideoCodec currentCodec; + _vcm->SendCodec(¤tCodec); + + WebRtc_UWord32 currentBitRate = _vcm->Bitrate(); + + // now set new values: + currentCodec.height = (WebRtc_UWord16)height; + currentCodec.width = (WebRtc_UWord16)width; + currentCodec.maxFramerate = (WebRtc_UWord8)frameRate; + currentCodec.startBitrate = currentBitRate; + + // re-register encoder + retVal = _vcm->RegisterSendCodec(¤tCodec,_numOfCores,_maxPayloadLength); + } + + return retVal; +} + +///////////////////// + + +} // namespace webrtc diff --git a/video_engine/main/source/vie_encoder.h b/video_engine/main/source/vie_encoder.h new file mode 100644 index 0000000000..42d297818a --- /dev/null +++ b/video_engine/main/source/vie_encoder.h @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_encoder.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_ENCODER_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_ENCODER_H_ + +#include "vie_defines.h" +#include "typedefs.h" +#include "vie_frame_provider_base.h" +#include "vie_file_recorder.h" +#include "rtp_rtcp_defines.h" +#include "video_coding_defines.h" +#include "video_processing.h" +#include "common_types.h" + +namespace webrtc { +class CriticalSectionWrapper; +class ProcessThread; +class RtpRtcp; +class ViEEffectFilter; +class VideoCodingModule; +class ViEEncoderObserver; + +class ViEEncoder: public ViEFrameCallback, // New frame delivery + public RtpVideoFeedback, // Feedback from RTP module + public RtcpFeedback, // RTP/RTCP Module + public VCMPacketizationCallback, // Callback from VCM + public VCMProtectionCallback, // Callback from VCM + public VCMSendStatisticsCallback // Callback from VCM +{ +public: + ViEEncoder(WebRtc_Word32 engineId, WebRtc_Word32 channelId, + WebRtc_UWord32 numberOfCores, + ProcessThread& moduleProcessThread); + ~ViEEncoder(); + + // Drops incoming packets + void Pause(); + void Restart(); + + WebRtc_Word32 DropDeltaAfterKey(bool enable); + + // Codec settings + WebRtc_UWord8 NumberOfCodecs(); + WebRtc_Word32 GetCodec(WebRtc_UWord8 listIndex, VideoCodec& videoCodec); + WebRtc_Word32 RegisterExternalEncoder(VideoEncoder* encoder, + WebRtc_UWord8 plType); + WebRtc_Word32 DeRegisterExternalEncoder(WebRtc_UWord8 plType); + WebRtc_Word32 SetEncoder(const VideoCodec& videoCodec); + WebRtc_Word32 GetEncoder(VideoCodec& videoCodec); + + WebRtc_Word32 GetCodecConfigParameters( + unsigned char configParameters[kConfigParameterSize], + unsigned char& configParametersSize); + + // Scale or crop/pad image + WebRtc_Word32 ScaleInputImage(bool enable); + + // RTP settings + RtpRtcp* SendRtpRtcpModule(); + + // Implementing ViEFrameCallback + virtual void DeliverFrame(int id, VideoFrame& videoFrame, int numCSRCs = 0, + const WebRtc_UWord32 CSRC[kRtpCsrcSize] = NULL); + virtual void DelayChanged(int id, int frameDelay); + virtual int GetPreferedFrameSettings(int &width, int &height, + int &frameRate); + + virtual void ProviderDestroyed(int id) { return; } + + WebRtc_Word32 EncodeFrame(VideoFrame& videoFrame); + WebRtc_Word32 SendKeyFrame(); + WebRtc_Word32 SendCodecStatistics(WebRtc_UWord32& numKeyFrames, + WebRtc_UWord32& numDeltaFrames); + // Loss protection + WebRtc_Word32 UpdateProtectionMethod(); + // Implements VCMPacketizationCallback + virtual WebRtc_Word32 SendData(const FrameType frameType, + const WebRtc_UWord8 payloadType, + const WebRtc_UWord32 timeStamp, + const WebRtc_UWord8* payloadData, + const WebRtc_UWord32 payloadSize, + const RTPFragmentationHeader& fragmentationHeader); + // Implements VideoProtectionCallback + virtual WebRtc_Word32 ProtectionRequest(const WebRtc_UWord8 deltaFECRate, + const WebRtc_UWord8 keyFECRate, + const bool nack); + // Implements VideoSendStatisticsCallback + virtual WebRtc_Word32 SendStatistics(const WebRtc_UWord32 bitRate, + const WebRtc_UWord32 frameRate); + WebRtc_Word32 RegisterCodecObserver(ViEEncoderObserver* observer); + // Implements RtcpFeedback + virtual void OnSLIReceived(const WebRtc_Word32 id, + const WebRtc_UWord8 pictureId); + virtual void OnRPSIReceived(const WebRtc_Word32 id, + const WebRtc_UWord64 pictureId); + + // Implements RtpVideoFeedback + virtual void OnReceivedIntraFrameRequest(const WebRtc_Word32 id, + const WebRtc_UWord8 message = 0); + virtual void OnNetworkChanged(const WebRtc_Word32 id, + const WebRtc_UWord32 minBitrateBps, + const WebRtc_UWord32 maxBitrateBps, + const WebRtc_UWord8 fractionLost, + const WebRtc_UWord16 roundTripTimeMs, + const WebRtc_UWord16 bwEstimateKbitMin, + const WebRtc_UWord16 bwEstimateKbitMax); + // Effect filter + WebRtc_Word32 RegisterEffectFilter(ViEEffectFilter* effectFilter); + //Recording + ViEFileRecorder& GetOutgoingFileRecorder(); + +private: + WebRtc_Word32 _engineId; + + class QMTestVideoSettingsCallback : public VCMQMSettingsCallback + { + public: + QMTestVideoSettingsCallback(); + // update VPM with QM (quality modes: frame size & frame rate) settings + WebRtc_Word32 SetVideoQMSettings(const WebRtc_UWord32 frameRate, + const WebRtc_UWord32 width, + const WebRtc_UWord32 height); + // register VPM and VCM + void RegisterVPM(VideoProcessingModule* vpm); + void RegisterVCM(VideoCodingModule* vcm); + void SetNumOfCores(WebRtc_Word32 numOfCores) + {_numOfCores = numOfCores;}; + void SetMaxPayloadLength(WebRtc_Word32 maxPayloadLength) + {_maxPayloadLength = maxPayloadLength;}; + private: + VideoProcessingModule* _vpm; + VideoCodingModule* _vcm; + WebRtc_Word32 _numOfCores; + WebRtc_Word32 _maxPayloadLength; + }; + + WebRtc_Word32 _channelId; + const WebRtc_UWord32 _numberOfCores; + + VideoCodingModule& _vcm; + VideoProcessingModule& _vpm; + RtpRtcp& _rtpRtcp; + CriticalSectionWrapper& _callbackCritsect; + CriticalSectionWrapper& _dataCritsect; + VideoCodec _sendCodec; + + bool _paused; + WebRtc_Word64 _timeLastIntraRequestMs; + WebRtc_Word32 _channelsDroppingDeltaFrames; + bool _dropNextFrame; + //Loss protection + bool _fecEnabled; + // Uses + ViEEncoderObserver* _codecObserver; + ViEEffectFilter* _effectFilter; + ProcessThread& _moduleProcessThread; + + bool _hasReceivedSLI; + WebRtc_UWord8 _pictureIdSLI; + bool _hasReceivedRPSI; + WebRtc_UWord64 _pictureIdRPSI; + + //Recording + ViEFileRecorder _fileRecorder; + + // Quality modes callback + QMTestVideoSettingsCallback* _qmCallback; + +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_ENCODER_H_ diff --git a/video_engine/main/source/vie_encryption_impl.cc b/video_engine/main/source/vie_encryption_impl.cc new file mode 100644 index 0000000000..6a96128527 --- /dev/null +++ b/video_engine/main/source/vie_encryption_impl.cc @@ -0,0 +1,543 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_encryption_impl.cc + */ + +#include "vie_encryption_impl.h" + +// Defines +#include "vie_defines.h" +#include "vie_errors.h" +#include "vie_channel.h" +#include "vie_channel_manager.h" + +#include "trace.h" +#include "vie_impl.h" + +#ifdef WEBRTC_SRTP +#include "SrtpModule.h" +#endif + +namespace webrtc +{ + +// ---------------------------------------------------------------------------- +// GetInterface +// ---------------------------------------------------------------------------- + +ViEEncryption* ViEEncryption::GetInterface(VideoEngine* videoEngine) +{ +#ifdef WEBRTC_VIDEO_ENGINE_ENCRYPTION_API + if (videoEngine == NULL) + { + return NULL; + } + VideoEngineImpl* vieImpl = reinterpret_cast (videoEngine); + ViEEncryptionImpl* vieEncryptionImpl = vieImpl; + (*vieEncryptionImpl)++; // Increase ref count + + return vieEncryptionImpl; +#else + return NULL; +#endif +} + +// ---------------------------------------------------------------------------- +// Release +// +// Releases the interface, i.e. reduces the reference counter. The number of +// remaining references is returned, -1 if released too many times. +// ---------------------------------------------------------------------------- + +int ViEEncryptionImpl::Release() +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, _instanceId, + "ViEEncryptionImpl::Release()"); + (*this)--; // Decrease ref count + + WebRtc_Word32 refCount = GetCount(); + if (refCount < 0) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, _instanceId, + "ViEEncryptionImpl release too many times"); + SetLastError(kViEAPIDoesNotExist); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, _instanceId, + "ViEEncryptionImpl reference count: %d", refCount); + return refCount; +} + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + +ViEEncryptionImpl::ViEEncryptionImpl() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, _instanceId, + "ViEEncryptionImpl::ViEEncryptionImpl() Ctor"); +} + +// ---------------------------------------------------------------------------- +// Destructor +// ---------------------------------------------------------------------------- + +ViEEncryptionImpl::~ViEEncryptionImpl() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, _instanceId, + "ViEEncryptionImpl::~ViEEncryptionImpl() Dtor"); +} + +// ============================================================================ +// SRTP +// ============================================================================ + +// ---------------------------------------------------------------------------- +// EnableSRTPSend +// +// ---------------------------------------------------------------------------- + +int ViEEncryptionImpl::EnableSRTPSend( + const int videoChannel, const CipherTypes cipherType, + const unsigned int cipherKeyLength, const AuthenticationTypes authType, + const unsigned int authKeyLength, const unsigned int authTagLength, + const SecurityLevels level, const unsigned char key[kViEMaxSrtpKeyLength], + const bool useForRTCP) +{ + + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, _instanceId, + "EnableSRTPSend(channel=%d, cipherType=%d, cipherKeyLength=%d, " + "authType=%d, authKeyLength=%d, authTagLength=%d, level=%d, " + "key=?, RTCP=%s", + videoChannel, cipherType, cipherKeyLength, authType, + authKeyLength, authTagLength, level, + useForRTCP ? "true" : "false"); +#ifdef WEBRTC_SRTP + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + + bool cipherAllZero = (kCipherNull == cipherType) && (0 == cipherKeyLength); + bool authAllZero = (kAuthNull == authType) && + (0 == authKeyLength) && + (0 == authTagLength); + + // 1. For no protection all cipher and auth must be zero + // 2. For encryption only all auth must be zero + // 3. For authentication only all cipher must be zero + if (((kNoProtection == level) && (!cipherAllZero || !authAllZero)) + || ((kEncryption == level) && !authAllZero) + || ((kAuthentication == level) && !cipherAllZero)) + + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, _instanceId, + " Invalid input argument"); + SetLastError(kViEEncryptionInvalidSrtpParameter); + return -1; + } + + // 16 <= cipherKeyLength <= 256 + if (((kEncryption == level) || (kEncryptionAndAuthentication == level)) + && ((cipherKeyLength < kViEMinSrtpEncryptLength) + || (cipherKeyLength > kViEMaxSrtpEncryptLength))) + + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, _instanceId, + " Invalid cipher key length"); + SetLastError(kViEEncryptionInvalidSrtpParameter); + return -1; + } + + // For HMAC_SHA1 auth: + // authKeyLength <= 20, authTagLength <= 20 + if (((kAuthentication == level) || (kEncryptionAndAuthentication == level)) + && (kAuthHmacSha1 == authType) + && ((authKeyLength > kViEMaxSrtpAuthSh1Length) + || (authTagLength > kViEMaxSrtpAuthSh1Length))) + + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, _instanceId, + " Invalid auth key or tag length"); + SetLastError(kViEEncryptionInvalidSrtpParameter); + return -1; + } + + // For NULL auth: + // authKeyLength <= 256, authTagLength <= 12 + if (((kAuthentication == level) || (kEncryptionAndAuthentication == level)) + && (kAuthNull == authType) + && ((authKeyLength > kViEMaxSrtpKeyAuthNullLength) + || (authTagLength > kViEMaxSrtpTagAuthNullLength))) + + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, _instanceId, + " Invalid auth key or tag length"); + SetLastError(kViEEncryptionInvalidSrtpParameter); + return -1; + } + if (!key) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, _instanceId, + " key NULL pointer"); + SetLastError(kViEEncryptionInvalidSrtpParameter); + return -1; + } + + const SrtpModule::CipherTypes cipher_type = + static_cast (cipherType); + const SrtpModule::AuthenticationTypes auth_type = + static_cast (authType); + const SrtpModule::SecurityLevels security_level = + static_cast (level); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (vieChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s: No channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViEEncryptionInvalidChannelId); + return -1; + } + + if (0 != vieChannel->EnableSRTPSend(cipher_type, cipherKeyLength, auth_type, + authKeyLength, authTagLength, + security_level, key, useForRTCP)) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, _instanceId, + "Failed to configure SRTP Encryption for sending"); + SetLastError(kViEEncryptionUnknownError); + return -1; + } + + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, _instanceId, + "SRTP Enabled for sending"); + return 0; +#else + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVoice, + ViEId(_instanceId, videoChannel), + " _SRTP is undefined => _lastError = %d", + LastErrorInternal()); + SetLastError(kViEEncryptionSrtpNotSupported); + return -1; +#endif +} + +// ---------------------------------------------------------------------------- +// DisableSRTPSend +// +// ---------------------------------------------------------------------------- + +int ViEEncryptionImpl::DisableSRTPSend(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "DisableSRTPSend(videoChannel=%d)", videoChannel); +#ifdef WEBRTC_SRTP + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (vieChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s: No channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViEEncryptionInvalidChannelId); + return -1; + } + + if (0 != vieChannel->DisableSRTPSend()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "Failed to Disable SRTP Encryption for sending"); + SetLastError(kViEEncryptionUnknownError); + return -1; + } + + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "SRTP Disabled for sending"); + return 0; +#else + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVoice, + ViEId(_instanceId, videoChannel), + " _SRTP is undefined => _lastError = %d", + LastErrorInternal()); + SetLastError(kViEEncryptionSrtpNotSupported); + return -1; +#endif + +} + +// ---------------------------------------------------------------------------- +// EnableSRTPReceive +// +// ---------------------------------------------------------------------------- + +int ViEEncryptionImpl::EnableSRTPReceive( + const int videoChannel, const CipherTypes cipherType, + const unsigned int cipherKeyLength, const AuthenticationTypes authType, + const unsigned int authKeyLength, const unsigned int authTagLength, + const SecurityLevels level, + const unsigned char key[kViEMaxSrtpKeyLength], const bool useForRTCP) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "EnableSRTPReceive(channel=%d, cipherType=%d, " + "cipherKeyLength=%d, authType=%d, authKeyLength=%d, " + "authTagLength=%d, level=%d, key=?, RTCP=%s)", + videoChannel, cipherType, cipherKeyLength, authType, + authKeyLength, authTagLength, level, + useForRTCP ? "true" : "false"); + +#ifdef WEBRTC_SRTP + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + + bool cipherAllZero = (kCipherNull == cipherType) && (0 == cipherKeyLength); + bool authAllZero = (kAuthNull == authType) + && (0 == authKeyLength) + && (0 == authTagLength); + + // 1. For no protection all cipher and auth must be zero + // 2. For encryption only all auth must be zero + // 3. For authentication only all cipher must be zero + if (((kNoProtection == level) && (!cipherAllZero || !authAllZero)) || + ((kEncryption == level) && !authAllZero) || + ((kAuthentication == level) && !cipherAllZero)) + + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + " Invalid input argument"); + SetLastError(kViEEncryptionInvalidSrtpParameter); + return -1; + } + + // 16 <= cipherKeyLength <= 256 + if (((kEncryption == level) || (kEncryptionAndAuthentication == level)) + && ((cipherKeyLength < kViEMinSrtpEncryptLength) + || (cipherKeyLength > kViEMaxSrtpEncryptLength))) + + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + " Invalid cipher key length"); + SetLastError(kViEEncryptionInvalidSrtpParameter); + return -1; + } + + // For HMAC_SHA1 auth: + // authKeyLength <= 20, authTagLength <= 20 + if (((kAuthentication == level) || (kEncryptionAndAuthentication == level)) + && (kAuthHmacSha1 == authType) + && ((authKeyLength > kViEMaxSrtpAuthSh1Length) + || (authTagLength > kViEMaxSrtpAuthSh1Length))) + + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + " Invalid auth key or tag length"); + SetLastError(kViEEncryptionInvalidSrtpParameter); + return -1; + } + + // For NULL auth: + // authKeyLength <= 256, authTagLength <= 12 + if (((kAuthentication == level) || (kEncryptionAndAuthentication == level)) + && (kAuthNull == authType) + && ((authKeyLength > kViEMaxSrtpKeyAuthNullLength) + || (authTagLength > kViEMaxSrtpTagAuthNullLength))) + + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + " Invalid auth key or tag length"); + SetLastError(kViEEncryptionInvalidSrtpParameter); + return -1; + } + + if (!key) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), " key NULL pointer"); + SetLastError(kViEEncryptionInvalidSrtpParameter); + return -1; + } + + const SrtpModule::CipherTypes cipher_type = + static_cast (cipherType); + const SrtpModule::AuthenticationTypes auth_type = + static_cast (authType); + const SrtpModule::SecurityLevels security_level = + static_cast (level); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (vieChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s: No channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViEEncryptionInvalidChannelId); + return -1; + } + + if (0 != vieChannel->EnableSRTPReceive(cipher_type, cipherKeyLength, + auth_type, authKeyLength, + authTagLength, security_level, key, + useForRTCP)) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "Failed to configure SRTP Encryption for receiving"); + SetLastError(kViEEncryptionUnknownError); + return -1; + } + + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "SRTP Enabled for receiving"); + return 0; + +#else + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVoice, + ViEId(_instanceId, videoChannel), + " _SRTP is undefined => _lastError = %d", + LastErrorInternal()); + SetLastError(kViEEncryptionSrtpNotSupported); + return -1; +#endif +} + +// ---------------------------------------------------------------------------- +// DisableSRTPReceive +// +// ---------------------------------------------------------------------------- + +int ViEEncryptionImpl::DisableSRTPReceive(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "DisableSRTPReceive(videoChannel=%d)", videoChannel); + +#ifdef WEBRTC_SRTP + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (vieChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s: No channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViEEncryptionInvalidChannelId); + return -1; + } + + if (0 != vieChannel->DisableSRTPReceive()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "Failed to Disable SRTP Encryption for receiving"); + SetLastError(kViEEncryptionUnknownError); + return -1; + } + + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "SRTP Disabled for receiving"); + return 0; +#else + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVoice, + ViEId(_instanceId, videoChannel), + " _SRTP is undefined => _lastError = %d", + LastErrorInternal()); + SetLastError(kViEEncryptionSrtpNotSupported); + return -1; +#endif +} + +// ============================================================================ +// External encryption +// ============================================================================ + +// ---------------------------------------------------------------------------- +// RegisterExternalEncryption +// +// ---------------------------------------------------------------------------- + +int ViEEncryptionImpl::RegisterExternalEncryption(const int videoChannel, + Encryption& encryption) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "RegisterExternalEncryption(videoChannel=%d)", videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (vieChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s: No channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViEEncryptionInvalidChannelId); + return -1; + } + if (vieChannel->RegisterExternalEncryption(&encryption) != 0) + { + SetLastError(kViEEncryptionUnknownError); + return -1; + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// DeregisterExternalEncryption +// +// ---------------------------------------------------------------------------- + +int ViEEncryptionImpl::DeregisterExternalEncryption(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "RegisterExternalEncryption(videoChannel=%d)", videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (vieChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s: No channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViEEncryptionInvalidChannelId); + return -1; + } + + if (vieChannel->DeRegisterExternalEncryption() != 0) + { + SetLastError(kViEEncryptionUnknownError); + return -1; + } + return 0; +} +} // namespace webrtc diff --git a/video_engine/main/source/vie_encryption_impl.h b/video_engine/main/source/vie_encryption_impl.h new file mode 100644 index 0000000000..2a306d0a60 --- /dev/null +++ b/video_engine/main/source/vie_encryption_impl.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_encryption_impl.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_ENCRYPTION_IMPL_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_ENCRYPTION_IMPL_H_ + +#include "vie_defines.h" + +#include "typedefs.h" +#include "vie_ref_count.h" +#include "vie_encryption.h" +#include "vie_shared_data.h" + +namespace webrtc +{ + +// ---------------------------------------------------------------------------- +// ViEEncryptionImpl +// ---------------------------------------------------------------------------- + +class ViEEncryptionImpl : public virtual ViESharedData, + public ViEEncryption, + public ViERefCount +{ +public: + virtual int Release(); + + // SRTP calls + virtual int EnableSRTPSend(const int videoChannel, + const CipherTypes cipherType, + const unsigned int cipherKeyLength, + const AuthenticationTypes authType, + const unsigned int authKeyLength, + const unsigned int authTagLength, + const SecurityLevels level, + const unsigned char key[kViEMaxSrtpKeyLength], + const bool useForRTCP); + + virtual int DisableSRTPSend(const int videoChannel); + + virtual int EnableSRTPReceive(const int videoChannel, + const CipherTypes cipherType, + const unsigned int cipherKeyLength, + const AuthenticationTypes authType, + const unsigned int authKeyLength, + const unsigned int authTagLength, + const SecurityLevels level, + const unsigned char key[kViEMaxSrtpKeyLength], + const bool useForRTCP); + + virtual int DisableSRTPReceive(const int videoChannel); + + // External encryption + virtual int RegisterExternalEncryption(const int videoChannel, + Encryption& encryption); + + virtual int DeregisterExternalEncryption(const int videoChannel); + +protected: + ViEEncryptionImpl(); + virtual ~ViEEncryptionImpl(); +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_ENCRYPTION_IMPL_H_ diff --git a/video_engine/main/source/vie_external_codec_impl.cc b/video_engine/main/source/vie_external_codec_impl.cc new file mode 100644 index 0000000000..beeb72c35e --- /dev/null +++ b/video_engine/main/source/vie_external_codec_impl.cc @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_external_codec_impl.cc + */ + +#include "engine_configurations.h" +#include "vie_external_codec_impl.h" +#include "vie_errors.h" +#include "trace.h" +#include "vie_impl.h" +#include "vie_channel.h" +#include "vie_encoder.h" +#include "vie_channel_manager.h" + +namespace webrtc +{ + +// ---------------------------------------------------------------------------- +// GetInterface +// ---------------------------------------------------------------------------- + +ViEExternalCodec* ViEExternalCodec::GetInterface(VideoEngine* videoEngine) +{ +#ifdef WEBRTC_VIDEO_ENGINE_EXTERNAL_CODEC_API + if (videoEngine == NULL) + { + return NULL; + } + VideoEngineImpl* vieImpl = reinterpret_cast (videoEngine); + ViEExternalCodecImpl* vieExternalCodecImpl = vieImpl; + (*vieExternalCodecImpl)++; // Increase ref count + + return vieExternalCodecImpl; +#else + return NULL; +#endif +} + +// ---------------------------------------------------------------------------- +// Release +// +// Releases the interface, i.e. reduces the reference counter. The number of +// remaining references is returned, -1 if released too many times. +// ---------------------------------------------------------------------------- + +int ViEExternalCodecImpl::Release() +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, _instanceId, + "ViEExternalCodec::Release()"); + (*this)--; // Decrease ref count + + WebRtc_Word32 refCount = GetCount(); + if (refCount < 0) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, _instanceId, + "ViEExternalCodec release too many times"); + SetLastError(kViEAPIDoesNotExist); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, _instanceId, + "ViEExternalCodec reference count: %d", refCount); + return refCount; +} + +// ---------------------------------------------------------------------------- +// RegisterExternalSendCodec +// ---------------------------------------------------------------------------- + +int ViEExternalCodecImpl::RegisterExternalSendCodec(const int videoChannel, + const unsigned char plType, + VideoEncoder* encoder) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s channel %d plType %d encoder 0x%x", __FUNCTION__, + videoChannel, plType, encoder); + + ViEChannelManagerScoped cs(_channelManager); + ViEEncoder* vieEncoder = cs.Encoder(videoChannel); + if (!vieEncoder) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Invalid argument videoChannel %u. Does it exist?", + __FUNCTION__, videoChannel); + SetLastError(kViECodecInvalidArgument); + return -1; + } + if (!encoder) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Invalid argument Encoder 0x%x.", __FUNCTION__, encoder); + SetLastError(kViECodecInvalidArgument); + return -1; + } + + if (vieEncoder->RegisterExternalEncoder(encoder, plType) != 0) + { + SetLastError(kViECodecUnknownError); + return -1; + } + return 0; +} + +int ViEExternalCodecImpl::DeRegisterExternalSendCodec( + const int videoChannel, const unsigned char plType) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s channel %d plType %d", __FUNCTION__, videoChannel, plType); + + ViEChannelManagerScoped cs(_channelManager); + ViEEncoder* vieEncoder = cs.Encoder(videoChannel); + if (!vieEncoder) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Invalid argument videoChannel %u. Does it exist?", + __FUNCTION__, videoChannel); + SetLastError(kViECodecInvalidArgument); + return -1; + } + + if (vieEncoder->DeRegisterExternalEncoder(plType) != 0) + { + SetLastError(kViECodecUnknownError); + return -1; + } + return 0; + +} + +int ViEExternalCodecImpl::RegisterExternalReceiveCodec( + const int videoChannel, const unsigned int plType, VideoDecoder* decoder, + bool decoderRender /*= false*/, int renderDelay /*= 0*/) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s channel %d plType %d decoder 0x%x, decoderRender %d, " + "renderDelay %d", __FUNCTION__, videoChannel, plType, decoder, + decoderRender, renderDelay); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (!vieChannel) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Invalid argument videoChannel %u. Does it exist?", + __FUNCTION__, videoChannel); + SetLastError(kViECodecInvalidArgument); + return -1; + } + if (!decoder) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Invalid argument decoder 0x%x.", __FUNCTION__, decoder); + SetLastError(kViECodecInvalidArgument); + return -1; + } + + if (vieChannel->RegisterExternalDecoder(plType, decoder, decoderRender, + renderDelay) != 0) + { + SetLastError(kViECodecUnknownError); + return -1; + } + return 0; +} + +int ViEExternalCodecImpl::DeRegisterExternalReceiveCodec( + const int videoChannel, const unsigned char plType) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s channel %d plType %u", __FUNCTION__, videoChannel, plType); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (!vieChannel) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Invalid argument videoChannel %u. Does it exist?", + __FUNCTION__, videoChannel); + SetLastError(kViECodecInvalidArgument); + return -1; + } + if (vieChannel->DeRegisterExternalDecoder(plType) != 0) + { + SetLastError(kViECodecUnknownError); + return -1; + } + return 0; +} +} // namespace webrtc diff --git a/video_engine/main/source/vie_external_codec_impl.h b/video_engine/main/source/vie_external_codec_impl.h new file mode 100644 index 0000000000..101585c2bf --- /dev/null +++ b/video_engine/main/source/vie_external_codec_impl.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_external_codec_impl.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_EXTERNAL_CODEC_IMPL_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_EXTERNAL_CODEC_IMPL_H_ + +#include "vie_external_codec.h" +#include "vie_ref_count.h" +#include "vie_shared_data.h" + +namespace webrtc +{ + +// ---------------------------------------------------------------------------- +// ViEExternalCodec +// ---------------------------------------------------------------------------- + +class ViEExternalCodecImpl : public virtual ViESharedData, + public ViEExternalCodec, + public ViERefCount +{ +public: + + virtual int Release(); + + virtual int RegisterExternalSendCodec(const int videoChannel, + const unsigned char plType, + VideoEncoder* encoder); + + virtual int DeRegisterExternalSendCodec(const int videoChannel, + const unsigned char plType); + + virtual int RegisterExternalReceiveCodec(const int videoChannel, + const unsigned int plType, + VideoDecoder* decoder, + bool decoderRender = false, + int renderDelay = 0); + + virtual int DeRegisterExternalReceiveCodec(const int videoChannel, + const unsigned char plType); +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_EXTERNAL_CODEC_IMPL_H_ diff --git a/video_engine/main/source/vie_file_image.cc b/video_engine/main/source/vie_file_image.cc new file mode 100644 index 0000000000..1ddb70aa65 --- /dev/null +++ b/video_engine/main/source/vie_file_image.cc @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_file_image.cc + */ + +#include +#include "vie_file_image.h" +#include "jpeg.h" +#include "trace.h" + +namespace webrtc { + +int ViEFileImage::ConvertJPEGToVideoFrame(int engineId, + const char* fileNameUTF8, + VideoFrame& videoFrame) +{ + // read jpeg file into temporary buffer + WebRtc_UWord8* imageBuffer; + WebRtc_UWord32 imageBufferSize; + FILE* imageFile = fopen(fileNameUTF8, "rb"); + if (NULL == imageFile) + { + // error reading file + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, engineId, + "%s could not open file %s", __FUNCTION__, fileNameUTF8); + return -1; + } + fseek(imageFile, 0, SEEK_END); + imageBufferSize = ftell(imageFile); + fseek(imageFile, 0, SEEK_SET); + imageBuffer = new WebRtc_UWord8[imageBufferSize + 1]; + if (imageBufferSize != fread(imageBuffer, sizeof(WebRtc_UWord8), + imageBufferSize, imageFile)) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, engineId, + "%s could not read file %s", __FUNCTION__, fileNameUTF8); + delete imageBuffer; + return -1; + } + fclose(imageFile); + + // if this is a jpeg file, decode it + JpegDecoder decoder; + + int ret = 0; + WebRtc_UWord8* imageDecodedBuffer = NULL; + WebRtc_UWord32 imageWidth, imageHeight = 0; + ret = decoder.Decode(imageBuffer, imageBufferSize, imageDecodedBuffer, + imageWidth, imageHeight); + + // done with this. + delete imageBuffer; + + if (-1 == ret) + { + // error decoding the file + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, engineId, + "%s could decode file %s from jpeg format", __FUNCTION__, + fileNameUTF8); + return -1; + } else if (-3 == ret) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, engineId, + "%s could not convert jpeg's data to i420 format", + __FUNCTION__, fileNameUTF8); + } + + WebRtc_UWord32 imageLength = (WebRtc_UWord32)(imageWidth * imageHeight + * 1.5); + if (-1 == videoFrame.Swap(imageDecodedBuffer, imageLength, imageLength)) + { + WEBRTC_TRACE( + webrtc::kTraceDebug, + webrtc::kTraceVideo, + engineId, + "%s could not copy frame imageDecodedBuffer to frame videoFrame ", + __FUNCTION__, fileNameUTF8); + return -1; + } + videoFrame.SetWidth(imageWidth); + videoFrame.SetHeight(imageHeight); + return 0; +} + +int ViEFileImage::ConvertPictureToVideoFrame(int engineId, + const ViEPicture& picture, + VideoFrame& videoFrame) +{ + WebRtc_UWord32 pictureLength = (WebRtc_UWord32)(picture.width + * picture.height * 1.5); + videoFrame.CopyFrame(pictureLength, picture.data); + videoFrame.SetWidth(picture.width); + videoFrame.SetHeight(picture.height); + videoFrame.SetLength(pictureLength); + + return 0; +} +} // namespace webrtc diff --git a/video_engine/main/source/vie_file_image.h b/video_engine/main/source/vie_file_image.h new file mode 100644 index 0000000000..be049d754d --- /dev/null +++ b/video_engine/main/source/vie_file_image.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_file_image.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_FILE_IMAGE_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_FILE_IMAGE_H_ + +#include "typedefs.h" +#include "vie_file.h" +#include "module_common_types.h" +namespace webrtc { +class ViEFileImage +{ +public: + static int ConvertJPEGToVideoFrame(int engineId, + const char* fileNameUTF8, + VideoFrame& videoFrame); + static int ConvertPictureToVideoFrame(int engineId, + const ViEPicture& picture, + VideoFrame& videoFrame); +}; +} // namespace webrtc + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_FILE_IMAGE_H_ diff --git a/video_engine/main/source/vie_file_impl.cc b/video_engine/main/source/vie_file_impl.cc new file mode 100644 index 0000000000..7488d06931 --- /dev/null +++ b/video_engine/main/source/vie_file_impl.cc @@ -0,0 +1,1285 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_file_impl.cc + */ + +#include "vie_file_impl.h" + +// Defines +#include "vie_defines.h" + +// Includes +#include "condition_variable_wrapper.h" +#include "critical_section_wrapper.h" +#include "jpeg.h" +#include "trace.h" +#include "vie_capturer.h" +#include "vie_channel.h" +#include "vie_channel_manager.h" +#include "vie_encoder.h" +#include "vie_errors.h" +#include "vie_file_image.h" +#include "vie_file_player.h" +#include "vie_file_recorder.h" +#include "vie_impl.h" +#include "vie_input_manager.h" +#include "vie_render_manager.h" + +namespace webrtc +{ + +// ---------------------------------------------------------------------------- +// GetInterface +// ---------------------------------------------------------------------------- + +ViEFile* ViEFile::GetInterface(VideoEngine* videoEngine) +{ +#ifdef WEBRTC_VIDEO_ENGINE_FILE_API + if (videoEngine == NULL) + { + return NULL; + } + VideoEngineImpl* vieImpl = reinterpret_cast (videoEngine); + ViEFileImpl* vieFileImpl = vieImpl; + (*vieFileImpl)++; // Increase ref count + + return vieFileImpl; +#else + return NULL; +#endif +} + +// ---------------------------------------------------------------------------- +// Release +// +// Releases the interface, i.e. reduces the reference counter. The number of +// remaining references is returned, -1 if released too many times. +// ---------------------------------------------------------------------------- + +int ViEFileImpl::Release() +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, _instanceId, + "ViEFile::Release()"); + (*this)--; // Decrease ref count + + WebRtc_Word32 refCount = GetCount(); + if (refCount < 0) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, _instanceId, + "ViEFile release too many times"); + SetLastError(kViEAPIDoesNotExist); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, _instanceId, + "ViEFile reference count: %d", refCount); + return refCount; +} + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + +ViEFileImpl::ViEFileImpl() + +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, _instanceId, + "ViEFileImpl::ViEFileImpl() Ctor"); + +} + +// ---------------------------------------------------------------------------- +// Destructor +// ---------------------------------------------------------------------------- + +ViEFileImpl::~ViEFileImpl() +{ + + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, _instanceId, + "ViEFileImpl::~ViEFileImpl() Dtor"); +} + +// ---------------------------------------------------------------------------- +// StartPlayFile +// ---------------------------------------------------------------------------- +// Play file +int ViEFileImpl::StartPlayFile(const char* fileNameUTF8, int& fileId, + const bool loop /*= false*/, + const webrtc::FileFormats fileFormat + /*= webrtc::kFileFormatAviFile*/) +{ + + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s", + __FUNCTION__); + + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + + VoiceEngine* voice = _channelManager.GetVoiceEngine(); + const WebRtc_Word32 result = _inputManager.CreateFilePlayer(fileNameUTF8, + loop, + fileFormat, + voice, fileId); + if (result != 0) + { + SetLastError(result); + return -1; + } + return 0; +} + +int ViEFileImpl::StopPlayFile(const int fileId) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(fileId: %d)", __FUNCTION__, fileId); + + { + ViEInputManagerScoped is(_inputManager); + ViEFilePlayer* ptrViEFilePlayer = is.FilePlayer(fileId); + if (ptrViEFilePlayer == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: File with id %d is not playing.", __FUNCTION__, + fileId); + SetLastError(kViEFileNotPlaying); + return -1; + } + } + + // Destroy the capture device + return _inputManager.DestroyFilePlayer(fileId); + +} + +int ViEFileImpl::RegisterObserver(int fileId, ViEFileObserver& observer) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(fileId: %d)", __FUNCTION__, fileId); + + ViEInputManagerScoped is(_inputManager); + ViEFilePlayer* ptrViEFilePlayer = is.FilePlayer(fileId); + if (ptrViEFilePlayer == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: File with id %d is not playing.", __FUNCTION__, + fileId); + SetLastError(kViEFileNotPlaying); + return -1; + } + if (ptrViEFilePlayer->IsObserverRegistered()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, fileId), + "%s: Observer already registered", __FUNCTION__); + SetLastError(kViEFileObserverAlreadyRegistered); + return -1; + } + if (ptrViEFilePlayer->RegisterObserver(observer) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, fileId), + "%s: Failed to register observer", __FUNCTION__, fileId); + SetLastError(kViEFileUnknownError); + return -1; + } + return 0; + +} + +int ViEFileImpl::DeregisterObserver(int fileId, ViEFileObserver& observer) +{ + + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(fileId: %d)", __FUNCTION__, fileId); + + ViEInputManagerScoped is(_inputManager); + ViEFilePlayer* ptrViEFilePlayer = is.FilePlayer(fileId); + if (ptrViEFilePlayer == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: File with id %d is not playing.", __FUNCTION__, + fileId); + SetLastError(kViEFileNotPlaying); + return -1; + } + if (!ptrViEFilePlayer->IsObserverRegistered()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, fileId), "%s: No Observer registered", + __FUNCTION__); + SetLastError(kViEFileObserverNotRegistered); + return -1; + } + if (ptrViEFilePlayer->DeRegisterObserver() != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, fileId), + "%s: Failed to deregister observer", __FUNCTION__, fileId); + SetLastError(kViEFileUnknownError); + return -1; + } + return 0; + +} + +int ViEFileImpl::SendFileOnChannel(const int fileId, const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(fileId: %d)", __FUNCTION__, fileId); + + ViEChannelManagerScoped cs(_channelManager); + ViEEncoder* ptrViEEncoder = cs.Encoder(videoChannel); + if (ptrViEEncoder == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViEFileInvalidChannelId); + return -1; + } + + ViEInputManagerScoped is(_inputManager); + if (is.FrameProvider(ptrViEEncoder) != NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d already connected to a capture device or " + "file.", __FUNCTION__, videoChannel); + SetLastError(kViEFileInputAlreadyConnected); + return -1; + } + + ViEFilePlayer* ptrViEFilePlayer = is.FilePlayer(fileId); + if (ptrViEFilePlayer == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: File with id %d is not playing.", __FUNCTION__, + fileId); + SetLastError(kViEFileNotPlaying); + return -1; + } + + if (ptrViEFilePlayer->RegisterFrameCallback(videoChannel, ptrViEEncoder) + != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: Failed to register frame callback.", __FUNCTION__, + fileId); + SetLastError(kViEFileUnknownError); + return -1; + } + return 0; +} + +int ViEFileImpl::StopSendFileOnChannel(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(videoChannel: %d)", __FUNCTION__, videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEEncoder* ptrViEEncoder = cs.Encoder(videoChannel); + if (ptrViEEncoder == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViEFileInvalidChannelId); + return -1; + } + + ViEInputManagerScoped is(_inputManager); + ViEFrameProviderBase* frameProvider = is.FrameProvider(ptrViEEncoder); + if (frameProvider == NULL + || frameProvider->Id() < kViEFileIdBase + || frameProvider->Id() > kViEFileIdMax) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: No file connected to Channel %d", __FUNCTION__, + videoChannel); + SetLastError(kViEFileNotConnected); + return -1; + } + if (frameProvider->DeregisterFrameCallback(ptrViEEncoder) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Failed to deregister file from channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViEFileUnknownError); + } + return 0; + +} + +int ViEFileImpl::StartPlayFileAsMicrophone(const int fileId, + const int audioChannel, + bool mixMicrophone /*= false*/, + float volumeScaling /*= 1*/) +{ + ViEInputManagerScoped is(_inputManager); + + ViEFilePlayer* ptrViEFilePlayer = is.FilePlayer(fileId); + if (ptrViEFilePlayer == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: File with id %d is not playing.", __FUNCTION__, + fileId); + SetLastError(kViEFileNotPlaying); + return -1; + } + if (ptrViEFilePlayer->SendAudioOnChannel(audioChannel, mixMicrophone, + volumeScaling) != 0) + { + SetLastError(kViEFileVoEFailure); + return -1; + } + return 0; + +} + +int ViEFileImpl::StopPlayFileAsMicrophone(const int fileId, + const int audioChannel) +{ + ViEInputManagerScoped is(_inputManager); + + ViEFilePlayer* ptrViEFilePlayer = is.FilePlayer(fileId); + if (ptrViEFilePlayer == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: File with id %d is not playing.", __FUNCTION__, + fileId); + SetLastError(kViEFileNotPlaying); + return -1; + } + + if (ptrViEFilePlayer->StopSendAudioOnChannel(audioChannel) != 0) + { + SetLastError(kViEFileVoEFailure); + return -1; + } + return 0; +} + +int ViEFileImpl::StartPlayAudioLocally(const int fileId, + const int audioChannel, + float volumeScaling /*=1*/) +{ + ViEInputManagerScoped is(_inputManager); + + ViEFilePlayer* ptrViEFilePlayer = is.FilePlayer(fileId); + if (ptrViEFilePlayer == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: File with id %d is not playing.", __FUNCTION__, + fileId); + SetLastError(kViEFileNotPlaying); + return -1; + } + if (ptrViEFilePlayer->PlayAudioLocally(audioChannel, volumeScaling) != 0) + { + SetLastError(kViEFileVoEFailure); + return -1; + } + return 0; +} + +int ViEFileImpl::StopPlayAudioLocally(const int fileId, const int audioChannel) +{ + ViEInputManagerScoped is(_inputManager); + + ViEFilePlayer* ptrViEFilePlayer = is.FilePlayer(fileId); + if (ptrViEFilePlayer == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: File with id %d is not playing.", __FUNCTION__, + fileId); + SetLastError(kViEFileNotPlaying); + return -1; + } + if (ptrViEFilePlayer->StopPlayAudioLocally(audioChannel) != 0) + { + SetLastError(kViEFileVoEFailure); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// StartRecordOutgoingVideo +// ---------------------------------------------------------------------------- +int ViEFileImpl::StartRecordOutgoingVideo(const int videoChannel, + const char* fileNameUTF8, + AudioSource audioSource, + const webrtc::CodecInst& audioCodec, + const VideoCodec& videoCodec, + const webrtc::FileFormats fileFormat + /*= webrtc::kFileFormatAviFile*/) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s videoChannel: %d)", + __FUNCTION__, videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEEncoder* ptrViEEncoder = cs.Encoder(videoChannel); + if (ptrViEEncoder == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViEFileInvalidChannelId); + return -1; + } + ViEFileRecorder& fileRecorder = ptrViEEncoder->GetOutgoingFileRecorder(); + if (fileRecorder.RecordingStarted()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Already recording outgoing video on channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViEFileAlreadyRecording); + return -1; + } + + WebRtc_Word32 veChannelId = -1; + VoiceEngine* vePtr = NULL; + if (audioSource != NO_AUDIO) + { + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + veChannelId = ptrViEChannel->VoiceChannel(); + vePtr = _channelManager.GetVoiceEngine(); + + if (!vePtr) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Can't access voice engine. Have SetVoiceEngine " + "been called?", __FUNCTION__); + SetLastError(kViEFileVoENotSet); + return -1; + } + } + if (fileRecorder.StartRecording(fileNameUTF8, videoCodec, audioSource, + veChannelId, audioCodec, vePtr, + fileFormat) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Failed to start recording. Check arguments.", + __FUNCTION__); + SetLastError(kViEFileUnknownError); + return -1; + } + + return 0; +} + +int ViEFileImpl::StopRecordOutgoingVideo(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s videoChannel: %d)", + __FUNCTION__, videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEEncoder* ptrViEEncoder = cs.Encoder(videoChannel); + if (ptrViEEncoder == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViEFileInvalidChannelId); + return -1; + } + ViEFileRecorder& fileRecorder = ptrViEEncoder->GetOutgoingFileRecorder(); + if (!fileRecorder.RecordingStarted()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d is not recording.", __FUNCTION__, + videoChannel); + SetLastError(kViEFileNotRecording); + return -1; + } + if (fileRecorder.StopRecording() != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Failed to stop recording of channel %d.", + __FUNCTION__, videoChannel); + SetLastError(kViEFileUnknownError); + return -1; + } + return 0; + +} +int ViEFileImpl::StopRecordIncomingVideo(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s videoChannel: %d)", + __FUNCTION__, videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViEFileInvalidChannelId); + return -1; + } + ViEFileRecorder& fileRecorder = ptrViEChannel->GetIncomingFileRecorder(); + if (!fileRecorder.RecordingStarted()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d is not recording.", __FUNCTION__, + videoChannel); + SetLastError(kViEFileNotRecording); + ptrViEChannel->ReleaseIncomingFileRecorder(); + + return -1; + } + if (fileRecorder.StopRecording() != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Failed to stop recording of channel %d.", + __FUNCTION__, videoChannel); + SetLastError(kViEFileUnknownError); + ptrViEChannel->ReleaseIncomingFileRecorder(); + return -1; + } + // Let the channel know we are no longer recording + ptrViEChannel->ReleaseIncomingFileRecorder(); + return 0; + +} + +int ViEFileImpl::StartRecordIncomingVideo(const int videoChannel, + const char* fileNameUTF8, + AudioSource audioSource, + const webrtc::CodecInst& audioCodec, + const VideoCodec& videoCodec, + const webrtc::FileFormats fileFormat + /*= webrtc::kFileFormatAviFile*/) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s videoChannel: %d)", + __FUNCTION__, videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViEFileInvalidChannelId); + return -1; + } + ViEFileRecorder& fileRecorder = ptrViEChannel->GetIncomingFileRecorder(); + if (fileRecorder.RecordingStarted()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Already recording outgoing video on channel %d", + __FUNCTION__, videoChannel); + SetLastError(kViEFileAlreadyRecording); + return -1; + } + + WebRtc_Word32 veChannelId = -1; + VoiceEngine* vePtr = NULL; + if (audioSource != NO_AUDIO) + { + veChannelId = ptrViEChannel->VoiceChannel(); + vePtr = _channelManager.GetVoiceEngine(); + + if (!vePtr) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Can't access voice engine. Have SetVoiceEngine " + "been called?", __FUNCTION__); + SetLastError(kViEFileVoENotSet); + return -1; + } + } + if (fileRecorder.StartRecording(fileNameUTF8, videoCodec, audioSource, + veChannelId, audioCodec, vePtr, fileFormat) + != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Failed to start recording. Check arguments.", + __FUNCTION__); + SetLastError(kViEFileUnknownError); + return -1; + } + return 0; +} + +// ============================================================================ +// File information +// ============================================================================ + +// ---------------------------------------------------------------------------- +// GetFileInformation +// +// +// ---------------------------------------------------------------------------- + +int ViEFileImpl::GetFileInformation(const char* fileName, + VideoCodec& videoCodec, + webrtc::CodecInst& audioCodec, + const webrtc::FileFormats fileFormat + /*= webrtc::kFileFormatAviFile*/) +{ + return ViEFilePlayer::GetFileInformation( + _instanceId, (WebRtc_Word8*) fileName, + videoCodec, audioCodec, fileFormat); +} + +// ============================================================================ +// Snapshot +// ============================================================================ +// ---------------------------------------------------------------------------- + +int ViEFileImpl::GetRenderSnapshot(const int videoChannel, + const char* fileNameUTF8) +{ + // gain access to the renderer for the specified channel and get it's + // current frame + ViERenderManagerScoped rs(_renderManager); + ViERenderer* ptrRender = rs.Renderer(videoChannel); + if (!ptrRender) + { + return -1; + } + + VideoFrame videoFrame; + if (-1 == ptrRender->GetLastRenderedFrame(videoChannel, videoFrame)) + { + return -1; + } + + const int JPEG_FORMAT = 0; + int format = JPEG_FORMAT; + + switch (format) + { + case JPEG_FORMAT: + { + // *** JPEGEncoder writes the jpeg file for you (no control + // over it) and does not return you the buffer + // *** Thusly, we are not going to be writing to the disk here + + JpegEncoder jpegEncoder; + + if (-1 == jpegEncoder.SetFileName(fileNameUTF8)) + { + // could not set filename for whatever reason + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, _instanceId, + "\tCould not open output file '%s' for writing!", + fileNameUTF8); + return -1; + } + + if (-1 == jpegEncoder.Encode(videoFrame.Buffer(), + videoFrame.Length(), + videoFrame.Width(), + videoFrame.Height())) + { + // could not encode i420->jpeg + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, _instanceId, + "\tCould not encode i420 -> jpeg file '%s' for " + "writing!", fileNameUTF8); + return -1; + } + + break; + } + default: + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceFile, _instanceId, + "\tUnsupported file format for %s", __FUNCTION__); + return -1; + break; + } + } + return 0; +} + +// ---------------------------------------------------------------------------- +// +// GetRenderSnapshot +// ---------------------------------------------------------------------------- + +int ViEFileImpl::GetRenderSnapshot(const int videoChannel, ViEPicture& picture) +{ + + // gain access to the renderer for the specified channel and get it's + // current frame + ViERenderManagerScoped rs(_renderManager); + ViERenderer* ptrRender = rs.Renderer(videoChannel); + if (!ptrRender) + { + return -1; + } + + VideoFrame videoFrame; + if (-1 == ptrRender->GetLastRenderedFrame(videoChannel, videoFrame)) + { + return -1; + } + + // copy from VideoFrame class to ViEPicture struct + int bufferLength = (int) (videoFrame.Width() * videoFrame.Height() * 1.5); + picture.data + = (WebRtc_UWord8*) malloc(bufferLength * sizeof(WebRtc_UWord8)); + memcpy(picture.data, videoFrame.Buffer(), bufferLength); + picture.size = bufferLength; + picture.width = videoFrame.Width(); + picture.height = videoFrame.Height(); + picture.type = kVideoI420; + + return 0; +} + +// ---------------------------------------------------------------------------- +// +// +// GetCaptureDeviceSnapshot +// ---------------------------------------------------------------------------- + +int ViEFileImpl::GetCaptureDeviceSnapshot(const int captureId, + const char* fileNameUTF8) +{ + ViEInputManagerScoped is(_inputManager); + ViECapturer* ptrCapture = is.Capture(captureId); + if (!ptrCapture) + { + return -1; + } + + VideoFrame videoFrame; + if (GetNextCapturedFrame(captureId, videoFrame) == -1) + { + // Failed to get a snapshot... + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, _instanceId, + "Could not gain acces to capture device %d video frame " + "%s:%d", captureId, __FUNCTION__); + return -1; + } + + const int JPEG_FORMAT = 0; + int format = JPEG_FORMAT; + + switch (format) + { + case JPEG_FORMAT: + { + // *** JPEGEncoder writes the jpeg file for you (no control + // over it) and does not return you the buffer + // *** Thusly, we are not going to be writing to the disk here + + JpegEncoder jpegEncoder; + + if (-1 == jpegEncoder.SetFileName(fileNameUTF8)) + { + // could not set filename for whatever reason + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, _instanceId, + "\tCould not open output file '%s' for writing!", + fileNameUTF8); + return -1; + } + + if (-1 == jpegEncoder.Encode(videoFrame.Buffer(), + videoFrame.Length(), + videoFrame.Width(), + videoFrame.Height())) + { + // could not encode i420->jpeg + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, _instanceId, + "\tCould not encode i420 -> jpeg file '%s' for " + "writing!", fileNameUTF8); + return -1; + } + + break; + } + default: + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceFile, _instanceId, + "\tUnsupported file format for %s", __FUNCTION__); + return -1; + break; + } + } + return 0; +} + +// ---------------------------------------------------------------------------- +// +// +// GetCaptureDeviceSnapshot +// ---------------------------------------------------------------------------- + +int ViEFileImpl::GetCaptureDeviceSnapshot(const int captureId, + ViEPicture& picture) +{ + VideoFrame videoFrame; + ViEInputManagerScoped is(_inputManager); + ViECapturer* ptrCapture = is.Capture(captureId); + if (!ptrCapture) + { + return -1; + } + + if (GetNextCapturedFrame(captureId, videoFrame) == -1) + { + // Failed to get a snapshot... + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, _instanceId, + "Could not gain acces to capture device %d video frame " + "%s:%d", captureId, __FUNCTION__); + return -1; + } + + // copy from VideoFrame class to ViEPicture struct + int bufferLength = (int) (videoFrame.Width() * videoFrame.Height() * 1.5); + picture.data + = (WebRtc_UWord8*) malloc(bufferLength * sizeof(WebRtc_UWord8)); + memcpy(picture.data, videoFrame.Buffer(), bufferLength); + picture.size = bufferLength; + picture.width = videoFrame.Width(); + picture.height = videoFrame.Height(); + picture.type = kVideoI420; + + return 0; +} + +// ---------------------------------------------------------------------------- +// +// +// FreePicture +// ---------------------------------------------------------------------------- + +int ViEFileImpl::FreePicture(ViEPicture& picture) +{ + if (picture.data) + free(picture.data); + + picture.data = NULL; + picture.size = 0; + picture.width = 0; + picture.height = 0; + picture.type = kVideoUnknown; + + return 0; +} + +// ============================================================================ +// Capture device images +// ============================================================================ + +// ---------------------------------------------------------------------------- +// +// +// SetCaptureDeviceImage +// ---------------------------------------------------------------------------- + +int ViEFileImpl::SetCaptureDeviceImage(const int captureId, + const char* fileNameUTF8) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, _instanceId, + "%s(captureId: %d)", __FUNCTION__, captureId); + + ViEInputManagerScoped is(_inputManager); + ViECapturer* ptrCapture = is.Capture(captureId); + if (!ptrCapture) + { + SetLastError(kViEFileInvalidCaptureId); + return -1; + } + + VideoFrame captureImage; + if (ViEFileImage::ConvertJPEGToVideoFrame( + ViEId(_instanceId, captureId), fileNameUTF8, captureImage) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, captureId), + "%s(captureId: %d) Failed to open file.", __FUNCTION__, + captureId); + SetLastError(kViEFileInvalidFile); + return -1; + } + if (ptrCapture->SetCaptureDeviceImage(captureImage)) + { + SetLastError(kViEFileSetCaptureImageError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// +// +// SetCaptureDeviceImage +// ---------------------------------------------------------------------------- + +int ViEFileImpl::SetCaptureDeviceImage(const int captureId, + const ViEPicture& picture) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, _instanceId, + "%s(captureId: %d)", __FUNCTION__, captureId); + + if (picture.type != kVideoI420) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, captureId), + "%s(captureId: %d) Not a valid picture type.", + __FUNCTION__, captureId); + SetLastError(kViEFileInvalidArgument); + return -1; + } + ViEInputManagerScoped is(_inputManager); + ViECapturer* ptrCapture = is.Capture(captureId); + if (!ptrCapture) + { + SetLastError(kViEFileSetCaptureImageError); + return -1; + } + + VideoFrame captureImage; + if (ViEFileImage::ConvertPictureToVideoFrame( + ViEId(_instanceId,captureId), picture, captureImage) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, captureId), + "%s(captureId: %d) Failed to use picture.", __FUNCTION__, + captureId); + SetLastError(kViEFileInvalidFile); + return -1; + } + if (ptrCapture->SetCaptureDeviceImage(captureImage)) + { + SetLastError(kViEFileInvalidCapture); + return -1; + } + return 0; +} + +// ============================================================================ +// Render images +// ============================================================================ + +// ---------------------------------------------------------------------------- +// +// +// SetRenderStartImage +// ---------------------------------------------------------------------------- + +int ViEFileImpl::SetRenderStartImage(const int videoChannel, + const char* fileNameUTF8) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(videoChannel: %d)", + __FUNCTION__, videoChannel); + + ViERenderManagerScoped rs(_renderManager); + ViERenderer* ptrRender = rs.Renderer(videoChannel); + if (!ptrRender) + { + SetLastError(kViEFileInvalidRenderId); + return -1; + } + + VideoFrame startImage; + if (ViEFileImage::ConvertJPEGToVideoFrame( + ViEId(_instanceId, videoChannel), fileNameUTF8, startImage) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(videoChannel: %d) Failed to open file.", __FUNCTION__, + videoChannel); + SetLastError(kViEFileInvalidFile); + return -1; + } + if (ptrRender->SetRenderStartImage(startImage) != 0) + { + SetLastError(kViEFileSetStartImageError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// +// +// SetRenderStartImage +// ---------------------------------------------------------------------------- + +int ViEFileImpl::SetRenderStartImage(const int videoChannel, + const ViEPicture& picture) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(videoChannel: %d)", + __FUNCTION__, videoChannel); + + if (picture.type != kVideoI420) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(videoChannel: %d) Not a valid picture type.", + __FUNCTION__, videoChannel); + SetLastError(kViEFileInvalidArgument); + return -1; + } + + ViERenderManagerScoped rs(_renderManager); + ViERenderer* ptrRender = rs.Renderer(videoChannel); + if (!ptrRender) + { + SetLastError(kViEFileInvalidRenderId); + return -1; + } + + VideoFrame startImage; + if (ViEFileImage::ConvertPictureToVideoFrame( + ViEId(_instanceId, videoChannel), picture, startImage) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(videoChannel: %d) Failed to use picture.", + __FUNCTION__, videoChannel); + SetLastError(kViEFileInvalidCapture); + return -1; + } + if (ptrRender->SetRenderStartImage(startImage) != 0) + { + SetLastError(kViEFileSetStartImageError); + return -1; + } + return 0; +} + +// ============================================================================ +// Timeout image +// ============================================================================ + +// ---------------------------------------------------------------------------- +// +// +// SetRenderTimeoutImage +// ---------------------------------------------------------------------------- + +int ViEFileImpl::SetRenderTimeoutImage(const int videoChannel, + const char* fileNameUTF8, + const unsigned int timeoutMs) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(videoChannel: %d)", + __FUNCTION__, videoChannel); + + ViERenderManagerScoped rs(_renderManager); + ViERenderer* ptrRender = rs.Renderer(videoChannel); + if (!ptrRender) + { + SetLastError(kViEFileInvalidRenderId); + return -1; + } + VideoFrame timeoutImage; + if (ViEFileImage::ConvertJPEGToVideoFrame( + ViEId(_instanceId,videoChannel), fileNameUTF8, timeoutImage) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(videoChannel: %d) Failed to open file.", __FUNCTION__, + videoChannel); + SetLastError(kViEFileInvalidFile); + return -1; + } + WebRtc_Word32 timeoutTime = timeoutMs; + if (timeoutMs < kViEMinRenderTimeoutTimeMs) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(videoChannel: %d) Invalid timeoutMs, using %d.", + __FUNCTION__, videoChannel, kViEMinRenderTimeoutTimeMs); + timeoutTime = kViEMinRenderTimeoutTimeMs; + } + if (timeoutMs > kViEMaxRenderTimeoutTimeMs) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(videoChannel: %d) Invalid timeoutMs, using %d.", + __FUNCTION__, videoChannel, kViEMaxRenderTimeoutTimeMs); + timeoutTime = kViEMaxRenderTimeoutTimeMs; + } + if (ptrRender->SetTimeoutImage(timeoutImage, timeoutTime) != 0) + { + SetLastError(kViEFileSetRenderTimeoutError); + return -1; + } + return 0; +} + +int ViEFileImpl::SetRenderTimeoutImage(const int videoChannel, + const ViEPicture& picture, + const unsigned int timeoutMs) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(videoChannel: %d)", + __FUNCTION__, videoChannel); + + if (picture.type != kVideoI420) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(videoChannel: %d) Not a valid picture type.", + __FUNCTION__, videoChannel); + SetLastError(kViEFileInvalidArgument); + return -1; + } + + ViERenderManagerScoped rs(_renderManager); + ViERenderer* ptrRender = rs.Renderer(videoChannel); + if (!ptrRender) + { + SetLastError(kViEFileSetRenderTimeoutError); + return -1; + } + VideoFrame timeoutImage; + if (ViEFileImage::ConvertPictureToVideoFrame( + ViEId(_instanceId, videoChannel), picture, timeoutImage) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(videoChannel: %d) Failed to use picture.", + __FUNCTION__, videoChannel); + SetLastError(kViEFileInvalidCapture); + return -1; + } + WebRtc_Word32 timeoutTime = timeoutMs; + if (timeoutMs < kViEMinRenderTimeoutTimeMs) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(videoChannel: %d) Invalid timeoutMs, using %d.", + __FUNCTION__, videoChannel, kViEMinRenderTimeoutTimeMs); + timeoutTime = kViEMinRenderTimeoutTimeMs; + } + if (timeoutMs > kViEMaxRenderTimeoutTimeMs) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(videoChannel: %d) Invalid timeoutMs, using %d.", + __FUNCTION__, videoChannel, kViEMaxRenderTimeoutTimeMs); + timeoutTime = kViEMaxRenderTimeoutTimeMs; + } + if (ptrRender->SetTimeoutImage(timeoutImage, timeoutTime) != 0) + { + SetLastError(kViEFileSetRenderTimeoutError); + return -1; + } + return 0; +} + +WebRtc_Word32 ViEFileImpl::GetNextCapturedFrame(WebRtc_Word32 captureId, + VideoFrame& videoFrame) +{ + ViEInputManagerScoped is(_inputManager); + ViECapturer* ptrCapture = is.Capture(captureId); + if (!ptrCapture) + { + return -1; + } + + ViECaptureSnapshot* snapShot = new ViECaptureSnapshot(); + ptrCapture->RegisterFrameCallback(-1, snapShot); + bool snapshotTaken = + snapShot->GetSnapshot(videoFrame, kViECaptureMaxSnapshotWaitTimeMs); + + // Check once again if it has been destroyed... + ptrCapture->DeregisterFrameCallback(snapShot); + delete snapShot; + snapShot = NULL; + + if (snapshotTaken) + { + return 0; + } + return -1; +} + +ViECaptureSnapshot::ViECaptureSnapshot() : + _crit(*CriticalSectionWrapper::CreateCriticalSection()), + _conditionVaraible(*ConditionVariableWrapper::CreateConditionVariable()), + _ptrVideoFrame(NULL) +{ +} + +ViECaptureSnapshot::~ViECaptureSnapshot() +{ + _crit.Enter(); + _crit.Leave(); + delete &_crit; + if (_ptrVideoFrame) + { + delete _ptrVideoFrame; + _ptrVideoFrame = NULL; + } +} + +bool ViECaptureSnapshot::GetSnapshot(VideoFrame& videoFrame, + unsigned int maxWaitTime) +{ + _crit.Enter(); + _ptrVideoFrame = new VideoFrame(); + if (_conditionVaraible.SleepCS(_crit, maxWaitTime)) + { + // Snapshot taken + videoFrame.SwapFrame(*_ptrVideoFrame); + delete _ptrVideoFrame; + _ptrVideoFrame = NULL; + _crit.Leave(); + return true; + } + return false; +} + +void ViECaptureSnapshot::DeliverFrame(int id, VideoFrame& videoFrame, + int numCSRCs, + const WebRtc_UWord32 CSRC[kRtpCsrcSize]) +{ + CriticalSectionScoped cs(_crit); + if (!_ptrVideoFrame) + { + return; + } + _ptrVideoFrame->SwapFrame(videoFrame); + _conditionVaraible.WakeAll(); + return; +} +} // namespace webrtc diff --git a/video_engine/main/source/vie_file_impl.h b/video_engine/main/source/vie_file_impl.h new file mode 100644 index 0000000000..6345499eff --- /dev/null +++ b/video_engine/main/source/vie_file_impl.h @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_file_impl.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_FILE_IMPL_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_FILE_IMPL_H_ + +#include "typedefs.h" +#include "vie_defines.h" +#include "vie_file.h" +#include "vie_frame_provider_base.h" +#include "vie_ref_count.h" +#include "vie_shared_data.h" + +namespace webrtc +{ +class ConditionVariableWrapper; + +// ---------------------------------------------------------------------------- +// ViECaptureSnapshot +// ---------------------------------------------------------------------------- + +class ViECaptureSnapshot: public ViEFrameCallback +{ +public: + ViECaptureSnapshot(); + ~ViECaptureSnapshot(); + + bool GetSnapshot(VideoFrame& videoFrame, unsigned int maxWaitTime); + + // From ViEFrameCallback + virtual void DeliverFrame(int id, VideoFrame& videoFrame, int numCSRCs = 0, + const WebRtc_UWord32 CSRC[kRtpCsrcSize] = NULL); + + virtual void DelayChanged(int id, int frameDelay) {} + + virtual int GetPreferedFrameSettings(int &width, int &height, + int &frameRate) + { + return -1; + } + + virtual void ProviderDestroyed(int id) {} + +private: + CriticalSectionWrapper& _crit; + ConditionVariableWrapper& _conditionVaraible; + VideoFrame* _ptrVideoFrame; +}; + +// ---------------------------------------------------------------------------- +// VideoFileImpl +// ---------------------------------------------------------------------------- + +class ViEFileImpl: public virtual ViESharedData, + public ViEFile, + public ViERefCount + +{ +public: + virtual int Release(); + + // Play file + virtual int StartPlayFile(const char* fileNameUTF8, int& fileId, + const bool loop = false, + const webrtc::FileFormats fileFormat = + webrtc::kFileFormatAviFile); + + virtual int StopPlayFile(const int fileId); + + virtual int RegisterObserver(int fileId, ViEFileObserver& observer); + + virtual int DeregisterObserver(int fileId, ViEFileObserver& observer); + + virtual int SendFileOnChannel(const int fileId, const int videoChannel); + + virtual int StopSendFileOnChannel(const int videoChannel); + + virtual int StartPlayFileAsMicrophone(const int fileId, + const int audioChannel, + bool mixMicrophone = false, + float volumeScaling = 1); + + virtual int StopPlayFileAsMicrophone(const int fileId, + const int audioChannel); + + virtual int StartPlayAudioLocally(const int fileId, const int audioChannel, + float volumeScaling = 1); + + virtual int StopPlayAudioLocally(const int fileId, const int audioChannel); + + virtual int StartRecordOutgoingVideo(const int videoChannel, + const char* fileNameUTF8, + AudioSource audioSource, + const webrtc::CodecInst& audioCodec, + const VideoCodec& videoCodec, + const webrtc::FileFormats fileFormat = + webrtc::kFileFormatAviFile); + + virtual int StartRecordIncomingVideo(const int videoChannel, + const char* fileNameUTF8, + AudioSource audioSource, + const webrtc::CodecInst& audioCodec, + const VideoCodec& videoCodec, + const webrtc::FileFormats fileFormat = + webrtc::kFileFormatAviFile); + + virtual int StopRecordOutgoingVideo(const int videoChannel); + + virtual int StopRecordIncomingVideo(const int videoChannel); + + // File information + virtual int GetFileInformation(const char* fileName, + VideoCodec& videoCodec, + webrtc::CodecInst& audioCodec, + const webrtc::FileFormats fileFormat = + webrtc::kFileFormatAviFile); + + // Snapshot + virtual int GetRenderSnapshot(const int videoChannel, + const char* fileNameUTF8); + + virtual int GetRenderSnapshot(const int videoChannel, ViEPicture& picture); + + virtual int FreePicture(ViEPicture& picture); + + virtual int GetCaptureDeviceSnapshot(const int captureId, + const char* fileNameUTF8); + + virtual int GetCaptureDeviceSnapshot(const int captureId, + ViEPicture& picture); + + // Capture device images + virtual int SetCaptureDeviceImage(const int captureId, + const char* fileNameUTF8); + + virtual int SetCaptureDeviceImage(const int captureId, + const ViEPicture& picture); + // Render images + virtual int SetRenderStartImage(const int videoChannel, + const char* fileNameUTF8); + + virtual int SetRenderStartImage(const int videoChannel, + const ViEPicture& picture); + + // Timeout image + virtual int SetRenderTimeoutImage(const int videoChannel, + const char* fileNameUTF8, + const unsigned int timeoutMs); + + virtual int SetRenderTimeoutImage(const int videoChannel, + const ViEPicture& picture, + const unsigned int timeoutMs); + +protected: + ViEFileImpl(); + virtual ~ViEFileImpl(); + +private: + WebRtc_Word32 GetNextCapturedFrame(WebRtc_Word32 captureId, + VideoFrame& videoFrame); + +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_FILE_IMPL_H_ diff --git a/video_engine/main/source/vie_file_player.cc b/video_engine/main/source/vie_file_player.cc new file mode 100644 index 0000000000..b933dd8d8e --- /dev/null +++ b/video_engine/main/source/vie_file_player.cc @@ -0,0 +1,572 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_file_player.cc + * + */ + +#include "critical_section_wrapper.h" +#include "trace.h" +#include "vie_file_player.h" +#include "tick_util.h" +#include "thread_wrapper.h" +#include "event_wrapper.h" +#include "vie_input_manager.h" +namespace webrtc { +ViEFilePlayer* ViEFilePlayer::CreateViEFilePlayer(int fileId, + int engineId, + const char* fileNameUTF8, + const bool loop, + const webrtc::FileFormats fileFormat, + ViEInputManager& inputManager, + VoiceEngine* vePtr) +{ + ViEFilePlayer* self = new ViEFilePlayer(fileId, engineId, inputManager); + if (!self || self->Init(fileNameUTF8, loop, fileFormat, vePtr) != 0) + { + delete self; + self = NULL; + } + return self; +} + +ViEFilePlayer::ViEFilePlayer(int Id, int engineId, + ViEInputManager& inputManager) + : ViEFrameProviderBase(Id, engineId), _playBackStarted(false), + _inputManager(inputManager), _ptrFeedBackCritSect(NULL), + _ptrAudioCritSect(NULL), _filePlayer(NULL), _audioStream(false), + _videoClients(0), _audioClients(0), _localAudioChannel(-1), _observer(NULL), + _veFileInterface(NULL), _veVideoSync(NULL), _ptrDecodeThread(NULL), + _ptrDecodeEvent(NULL), _decodedAudioLength(0), _audioChannelBuffers(), + _decodedVideo() +{ +} + +ViEFilePlayer::~ViEFilePlayer() +{ + StopPlay(); + delete _ptrDecodeEvent; + delete _ptrAudioCritSect; + delete _ptrFeedBackCritSect; +} + +int ViEFilePlayer::Init(const char* fileNameUTF8, const bool loop, + const webrtc::FileFormats fileFormat, + VoiceEngine* vePtr) +{ + + _ptrFeedBackCritSect = CriticalSectionWrapper::CreateCriticalSection(); + if (!_ptrFeedBackCritSect) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "ViEFilePlayer::StartPlay() failed to allocate critsect"); + return -1; + } + + _ptrAudioCritSect = CriticalSectionWrapper::CreateCriticalSection(); + if (!_ptrAudioCritSect) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "ViEFilePlayer::StartPlay() failed to allocate critsect"); + return -1; + } + + _ptrDecodeEvent = EventWrapper::Create(); + if (!_ptrDecodeEvent) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "ViEFilePlayer::StartPlay() failed to allocate event"); + return -1; + + } + if (strlen(fileNameUTF8) > FileWrapper::kMaxFileNameSize) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "ViEFilePlayer::StartPlay() To long filename"); + return -1; + } + strncpy(_fileName, fileNameUTF8, strlen(fileNameUTF8) + 1); + + _filePlayer = FilePlayer::CreateFilePlayer(ViEId(_engineId, _id), + fileFormat); + if (!_filePlayer) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "ViEFilePlayer::StartPlay() failed to create file player"); + return -1; + } + if (_filePlayer->RegisterModuleFileCallback(this) == -1) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "ViEFilePlayer::StartPlay() failed to RegisterModuleFileCallback"); + _filePlayer = NULL; + return -1; + } + _ptrDecodeThread = ThreadWrapper::CreateThread(FilePlayDecodeThreadFunction, + this, kHighestPriority, + "ViEFilePlayThread"); + if (!_ptrDecodeThread) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "ViEFilePlayer::StartPlay() failed to start decode thread."); + _filePlayer = NULL; + return -1; + + } + + // Always try to open with Audio since we don't know on what channels the audio should be played on. + WebRtc_Word32 error = _filePlayer->StartPlayingVideoFile(_fileName, loop, + false); + if (error) // Failed to open the file with audio. Try without + { + error = _filePlayer->StartPlayingVideoFile(_fileName, loop, true); + _audioStream = false; + if (error) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "ViEFilePlayer::StartPlay() failed to Start play video file"); + return -1; + } + + } else + { + _audioStream = true; + } + + if (_audioStream) // The file contain an audiostream + { + if (vePtr) // && localAudioChannel!=-1) // VeInterface have been provided and we want to play audio on local channel. + { + _veFileInterface = VoEFile::GetInterface(vePtr); + if (!_veFileInterface) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _id), + "ViEFilePlayer::StartPlay() failed to get VEFile interface"); + return -1; + } + _veVideoSync = VoEVideoSync::GetInterface(vePtr); + if (!_veVideoSync) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _id), + "ViEFilePlayer::StartPlay() failed to get " + "VoEVideoSync interface"); + return -1; + } + } + } + + _ptrDecodeEvent->StartTimer(true, 10); // Read audio /(or just video) every 10ms. + + return 0; +} +/* + //Implements ViEFrameProviderBase + // Starts the decode thread when someone cares. + */ +int ViEFilePlayer::FrameCallbackChanged() +{ + if (ViEFrameProviderBase::NumberOfRegistersFrameCallbacks() > _videoClients) + { + if (!_playBackStarted) + { + _playBackStarted = true; + unsigned int threadId; + if (_ptrDecodeThread->Start(threadId)) + { + WEBRTC_TRACE( + webrtc::kTraceStateInfo, + webrtc::kTraceVideo, + ViEId(_engineId, _id), + "ViEFilePlayer::FrameCallbackChanged() Started filedecode thread %u", + threadId); + } else + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _id), + "ViEFilePlayer::FrameCallbackChanged() Failed to start file decode thread."); + } + } else if (!_filePlayer->IsPlayingFile()) + { + if (_filePlayer->StartPlayingVideoFile(_fileName, false, + !_audioStream) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _id), + "ViEFilePlayer::FrameCallbackChanged(), Failed to restart the file player."); + + } + + } + } + _videoClients = ViEFrameProviderBase::NumberOfRegistersFrameCallbacks(); + return 0; + +} + +// File play decode function. +bool ViEFilePlayer::FilePlayDecodeThreadFunction(void* obj) +{ + return static_cast (obj)->FilePlayDecodeProcess(); +} +bool ViEFilePlayer::FilePlayDecodeProcess() +{ + + if (_ptrDecodeEvent->Wait(kThreadWaitTimeMs) == kEventSignaled) + { + if (_audioStream && _audioClients == 0) // If there is audio but no one cares- read the audio self + { + Read(NULL, 0); + } + if (_filePlayer->TimeUntilNextVideoFrame() < 10) // Less than 10ms to next videoframe + { + if (_filePlayer->GetVideoFromFile(_decodedVideo) != 0) + { + } + } + if (_decodedVideo.Length() > 0) + { + + if (_localAudioChannel != -1 && _veVideoSync) // We are playing audio locally + { + int audioDelay = 0; + if (_veVideoSync->GetPlayoutBufferSize(audioDelay) == 0) + { + _decodedVideo.SetRenderTime(_decodedVideo.RenderTimeMs() + + audioDelay); + } + } + DeliverFrame(_decodedVideo); + _decodedVideo.SetLength(0); + } + + } + return true; +} + +int ViEFilePlayer::StopPlay() //Only called from destructor. +{ + + bool threadStoped = false; + if (_ptrDecodeThread) + { + _ptrDecodeThread->SetNotAlive(); + if (_ptrDecodeThread->Stop()) + { + + delete _ptrDecodeThread; + } else + { + assert(!"ViEFilePlayer::StopPlay() Failed to stop decode thread"); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "ViEFilePlayer::StartPlay() Failed to stop file decode thread."); + } + } + + _ptrDecodeThread = NULL; + if (_ptrDecodeEvent) + { + _ptrDecodeEvent->StopTimer(); + } + + StopPlayAudio(); + + if (_veFileInterface) + { + _veFileInterface->Release(); + _veFileInterface = NULL; + } + if (_veVideoSync) + { + _veVideoSync->Release(); + _veVideoSync = NULL; + } + + if (_filePlayer) + { + _filePlayer->StopPlayingFile(); + FilePlayer::DestroyFilePlayer(_filePlayer); + _filePlayer = NULL; + } + + return 0; +} +int ViEFilePlayer::StopPlayAudio() +{ + // Stop sending audio + while (MapItem* audioItem = _audioChannelsSending.First()) + { + StopSendAudioOnChannel(audioItem->GetId()); + } + + // Stop local audio playback + if (_localAudioChannel != -1) + { + StopPlayAudioLocally(_localAudioChannel); + } + _localAudioChannel = -1; + while (_audioChannelBuffers.PopFront() != -1); + while (_audioChannelsSending.Erase(_audioChannelsSending.First()) != -1); + _audioClients = 0; + return 0; +} + +// From webrtc::InStream +int ViEFilePlayer::Read(void *buf, int len) +{ + CriticalSectionScoped lock(*_ptrAudioCritSect); // Protect from simultaneouse reading from multiple channels + if (NeedsAudioFromFile(buf)) + { + if (_filePlayer->Get10msAudioFromFile(_decodedAudio, + _decodedAudioLength, 16000) != 0) // we will run the VE in 16KHz + { + // No data + _decodedAudioLength = 0; + return 0; + } + _decodedAudioLength *= 2; // 2 bytes per sample + if (buf != 0) + { + _audioChannelBuffers.PushBack(buf); + } + } else + { + // No need for new audiobuffer from file. Ie the buffer read from file has not been played on this channel. + } + if (buf) + { + memcpy(buf, _decodedAudio, _decodedAudioLength); + } + return _decodedAudioLength; + +} +bool ViEFilePlayer::NeedsAudioFromFile(void* buf) +{ + bool needsNewAudio = false; + if (_audioChannelBuffers.GetSize() == 0) + { + return true; + } + + //Check if we the buf already have read the current audio. + for (ListItem* item = _audioChannelBuffers.First(); item != NULL; item + = _audioChannelBuffers.Next(item)) + { + if (item->GetItem() == buf) + { + needsNewAudio = true; + _audioChannelBuffers.Erase(item); + break; + } + } + return needsNewAudio; +} + +// From FileCallback +void ViEFilePlayer::PlayFileEnded(const WebRtc_Word32 id) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, id), + "%s: fileId %d", __FUNCTION__, _id); + + _filePlayer->StopPlayingFile(); + + CriticalSectionScoped lock(*_ptrFeedBackCritSect); + if (_observer) + { + _observer->PlayFileEnded(_id); + } +} + +bool ViEFilePlayer::IsObserverRegistered() +{ + CriticalSectionScoped lock(*_ptrFeedBackCritSect); + return _observer != NULL; + +} +int ViEFilePlayer::RegisterObserver(ViEFileObserver& observer) +{ + CriticalSectionScoped lock(*_ptrFeedBackCritSect); + if (_observer) + return -1; + _observer = &observer; + return 0; +} +int ViEFilePlayer::DeRegisterObserver() +{ + CriticalSectionScoped lock(*_ptrFeedBackCritSect); + _observer = NULL; + return 0; +} + +// ---------------------------------------------------------------------------- +// SendAudioOnChannel +// Order the voice engine to send the audio on a channel +// ---------------------------------------------------------------------------- +int ViEFilePlayer::SendAudioOnChannel(const int audioChannel, + bool mixMicrophone, float volumeScaling) +{ + + if (!_veFileInterface) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "%s No VEFile interface.", __FUNCTION__); + return -1; + } + if (_veFileInterface->StartPlayingFileAsMicrophone(audioChannel, + this, + mixMicrophone, + kFileFormatPcm16kHzFile, + volumeScaling) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "ViEFilePlayer::SendAudioOnChannel() VE_StartPlayingFileAsMicrophone failed. audioChannel %d, mixMicrophone %d, volumeScaling %.2f", + audioChannel, mixMicrophone, volumeScaling); + return -1; + } + _audioChannelsSending.Insert(audioChannel, NULL); + + CriticalSectionScoped lock(*_ptrAudioCritSect); + _audioClients++; // Increase the number of audioClients; + + return 0; +} + +// ---------------------------------------------------------------------------- +// StopSendAudioOnChannel +// Order the voice engine to stop send the audio on a channel +// ---------------------------------------------------------------------------- +int ViEFilePlayer::StopSendAudioOnChannel(const int audioChannel) +{ + int result = 0; + MapItem* audioItem = _audioChannelsSending.Find(audioChannel); + if (!audioItem) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "_s AudioChannel %d not sending", __FUNCTION__, audioChannel); + return -1; + } + result = _veFileInterface->StopPlayingFileAsMicrophone(audioChannel); + if (result != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "ViEFilePlayer::StopSendAudioOnChannel() VE_StopPlayingFileAsMicrophone failed. audioChannel %d", + audioChannel); + } + _audioChannelsSending.Erase(audioItem); + CriticalSectionScoped lock(*_ptrAudioCritSect); + _audioClients--; // Decrease the number of audioClients; + assert(_audioClients>=0); + return 0; + +} +int ViEFilePlayer::PlayAudioLocally(const int audioChannel, float volumeScaling) +{ + if (!_veFileInterface) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "%s No VEFile interface.", __FUNCTION__); + return -1; + } + if (_veFileInterface->StartPlayingFileLocally( + audioChannel, + this, + kFileFormatPcm16kHzFile, + volumeScaling) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "%s VE_StartPlayingFileAsMicrophone failed. audioChannel %d, mixMicrophone %d, volumeScaling %.2f", + __FUNCTION__, audioChannel, volumeScaling); + return -1; + } + + CriticalSectionScoped lock(*_ptrAudioCritSect); + _localAudioChannel = audioChannel; + _audioClients++; // Increase the number of audioClients; + + return 0; + +} + +int ViEFilePlayer::StopPlayAudioLocally(const int audioChannel) +{ + if (!_veFileInterface) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "%s No VEFile interface.", __FUNCTION__); + return -1; + } + if (_veFileInterface->StopPlayingFileLocally(audioChannel) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "%s VE_StopPlayingFileLocally failed. audioChannel %d.", + __FUNCTION__, audioChannel); + return -1; + } + + CriticalSectionScoped lock(*_ptrAudioCritSect); + _localAudioChannel = -1; + _audioClients--; // Decrease the number of audioClients; + + return 0; + +} + +//static +int ViEFilePlayer::GetFileInformation(int engineId, const char* fileName, + VideoCodec& videoCodec, + webrtc::CodecInst& audioCodec, + const webrtc::FileFormats fileFormat) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, engineId, "%s ", __FUNCTION__); + + FilePlayer* filePlayer = FilePlayer::CreateFilePlayer(engineId, fileFormat); + if (!filePlayer) + { + return -1; + } + int result = 0; + + bool videoOnly = false; + + memset(&videoCodec, 0, sizeof(videoCodec)); + memset(&audioCodec, 0, sizeof(audioCodec)); + + if (filePlayer->StartPlayingVideoFile(fileName, false, false) != 0) + { + videoOnly = true; + if (filePlayer->StartPlayingVideoFile(fileName, false, true) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, engineId, + "%s Failed to open file.", __FUNCTION__); + FilePlayer::DestroyFilePlayer(filePlayer); + return -1; + } + } + + if (!videoOnly && filePlayer->AudioCodec(audioCodec) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, engineId, + "%s Failed to get audio codec.", __FUNCTION__); + FilePlayer::DestroyFilePlayer(filePlayer); + return -1; + } + if (filePlayer->video_codec_info(videoCodec) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, engineId, + "%s Failed to get video codec.", __FUNCTION__); + FilePlayer::DestroyFilePlayer(filePlayer); + return -1; + } + FilePlayer::DestroyFilePlayer(filePlayer); + return 0; +} +} // namespace webrtc diff --git a/video_engine/main/source/vie_file_player.h b/video_engine/main/source/vie_file_player.h new file mode 100644 index 0000000000..cfe0f96ce6 --- /dev/null +++ b/video_engine/main/source/vie_file_player.h @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_file_player.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_FILE_PLAYER_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_FILE_PLAYER_H_ + +#include "typedefs.h" +#include "common_types.h" // webrtc::OutStream +#include "file_player.h" +#include "media_file_defines.h" +#include "vie_file.h" +#include "voe_file.h" +#include "voe_video_sync.h" +#include "list_wrapper.h" +#include "vie_frame_provider_base.h" +#include "file_wrapper.h" + +namespace webrtc +{ +class EventWrapper; +class ThreadWrapper; +class ViEInputManager; +class ViEFilePlayer: public ViEFrameProviderBase, + protected webrtc::FileCallback, + protected webrtc::InStream // for audio +{ +public: + static ViEFilePlayer *CreateViEFilePlayer(int fileId, int engineId, + const char* fileNameUTF8, + const bool loop, + const webrtc::FileFormats fileFormat, + ViEInputManager& inputManager, + VoiceEngine* vePtr); + + static int GetFileInformation(const int engineId, + const char* fileName, + webrtc::VideoCodec& videoCodec, + webrtc::CodecInst& audioCodec, + const webrtc::FileFormats fileFormat); + ~ViEFilePlayer(); + + bool IsObserverRegistered(); + int RegisterObserver(ViEFileObserver& observer); + int DeRegisterObserver(); + int SendAudioOnChannel(const int audioChannel, bool mixMicrophone, + float volumeScaling); + int StopSendAudioOnChannel(const int audioChannel); + int PlayAudioLocally(const int audioChannel, float volumeScaling); + int StopPlayAudioLocally(const int audioChannel); + + //Implement ViEFrameProviderBase + virtual int FrameCallbackChanged(); + +protected: + ViEFilePlayer(int Id, int engineId, ViEInputManager& inputManager); + int Init(const WebRtc_Word8* fileNameUTF8, const bool loop, + const webrtc::FileFormats fileFormat, VoiceEngine* vePtr); + int StopPlay(); + int StopPlayAudio(); + + // File play decode function. + static bool FilePlayDecodeThreadFunction(void* obj); + bool FilePlayDecodeProcess(); + bool NeedsAudioFromFile(void* buf); + + // From webrtc::InStream + virtual int Read(void *buf, int len); + virtual int Rewind() { return 0;} + + // From FileCallback + virtual void PlayNotification(const WebRtc_Word32 /*id*/, + const WebRtc_UWord32 /*notificationMs*/){} + virtual void RecordNotification(const WebRtc_Word32 id, + const WebRtc_UWord32 notificationMs){} + virtual void PlayFileEnded(const WebRtc_Word32 id); + virtual void RecordFileEnded(const WebRtc_Word32 id) { } + + +private: + enum { kThreadWaitTimeMs = 100 }; + + bool _playBackStarted; + ViEInputManager& _inputManager; + + CriticalSectionWrapper* _ptrFeedBackCritSect; + CriticalSectionWrapper* _ptrAudioCritSect; + + webrtc::FilePlayer* _filePlayer; + bool _audioStream; + + int _videoClients; // Number of active video clients + int _audioClients; //No of audio channels sending this audio. + int _localAudioChannel; //Local audio channel playing this video. Sync video against this. + + ViEFileObserver* _observer; + WebRtc_Word8 _fileName[FileWrapper::kMaxFileNameSize]; + + // VE Interface + VoEFile* _veFileInterface; + VoEVideoSync* _veVideoSync; + // Thread for decoding video (and audio if no audio clients connected) + ThreadWrapper* _ptrDecodeThread; + EventWrapper* _ptrDecodeEvent; + WebRtc_Word16 _decodedAudio[320]; + WebRtc_UWord32 _decodedAudioLength; + + ListWrapper _audioChannelBuffers; //trick - list containing VE buffer reading this file. Used if multiple audio channels are sending. + MapWrapper _audioChannelsSending; // AudioChannels sending audio from this file + VideoFrame _decodedVideo; // Frame receiving decoded video from file. +}; +} // namespace webrtc + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_FILE_PLAYER_H_ diff --git a/video_engine/main/source/vie_file_recorder.cc b/video_engine/main/source/vie_file_recorder.cc new file mode 100644 index 0000000000..547cc1449c --- /dev/null +++ b/video_engine/main/source/vie_file_recorder.cc @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_file_recorder.cc + * + */ +#include "vie_file_recorder.h" +#include "critical_section_wrapper.h" +#include "trace.h" +#include "tick_util.h" +#include "file_player.h" +#include "file_recorder.h" +#include "vie_defines.h" + +namespace webrtc { + +ViEFileRecorder::ViEFileRecorder(int instanceID) + : _ptrCritSec(CriticalSectionWrapper::CreateCriticalSection()), + _fileRecorder(NULL), _isFirstFrameRecorded(false), + _isOutStreamStarted(false), _instanceID(instanceID), _frameDelay(0), + _audioChannel(-1), _audioSource(NO_AUDIO), + _veFileInterface(NULL) +{ +} + +ViEFileRecorder::~ViEFileRecorder() +{ + StopRecording(); + delete _ptrCritSec; +} + +int ViEFileRecorder::StartRecording(const char* fileNameUTF8, + const VideoCodec& codecInst, + AudioSource audioSource, + int audioChannel, + const webrtc::CodecInst audioCodecInst, + VoiceEngine* vePtr, + const webrtc::FileFormats fileFormat) +{ + CriticalSectionScoped lock(*_ptrCritSec); + + if (_fileRecorder) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, _instanceID, + "ViEFileRecorder::StartRecording() failed, already recording."); + return -1; + } + _fileRecorder = FileRecorder::CreateFileRecorder(_instanceID, fileFormat); + if (!_fileRecorder) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, _instanceID, + "ViEFileRecorder::StartRecording() failed to create file recoder."); + return -1; + } + + int error = _fileRecorder->StartRecordingVideoFile(fileNameUTF8, + audioCodecInst, + codecInst, + AMRFileStorage, + audioSource == NO_AUDIO); + if (error) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, _instanceID, + "ViEFileRecorder::StartRecording() failed to StartRecordingVideoFile."); + FileRecorder::DestroyFileRecorder(_fileRecorder); + _fileRecorder = NULL; + return -1; + } + + _audioSource = audioSource; + if (vePtr && audioSource != NO_AUDIO) // VeInterface have been provided and we want to record audio + { + _veFileInterface = VoEFile::GetInterface(vePtr); + if (!_veFileInterface) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, _instanceID, + "ViEFileRecorder::StartRecording() failed to get VEFile interface"); + return -1; + } + // always drive VoE in L16 + CodecInst engineAudioCodecInst = { 96, // .pltype + "L16", // .plname + audioCodecInst.plfreq, // .plfreq + audioCodecInst.plfreq / 100, // .pacsize (10ms) + 1, // .channels + audioCodecInst.plfreq * 16 // .rate + }; + + switch (audioSource) + { + case MICROPHONE: + error + = _veFileInterface->StartRecordingMicrophone( + this, + &engineAudioCodecInst); + break; + case PLAYOUT: + error + = _veFileInterface->StartRecordingPlayout( + audioChannel, + this, + &engineAudioCodecInst); + break; + case NO_AUDIO: + break; + default: + assert(!"Unknown audioSource"); + } + if (error != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, _instanceID, + "ViEFileRecorder::StartRecording() failed to start recording audio"); + FileRecorder::DestroyFileRecorder(_fileRecorder); + _fileRecorder = NULL; + return -1; + } + _isOutStreamStarted = true; + _audioChannel = audioChannel; + } + + _isFirstFrameRecorded = false; + return 0; +} + +int ViEFileRecorder::StopRecording() +{ + + int error; + // Stop recording audio + // Note - we can not hold the _ptrCritSect while accessing VE functions. It might cause deadlock in Write + if (_veFileInterface) + { + switch (_audioSource) + { + case MICROPHONE: + error = _veFileInterface->StopRecordingMicrophone(); + break; + case PLAYOUT: + error = _veFileInterface->StopRecordingPlayout(_audioChannel); + break; + case NO_AUDIO: + break; + default: + assert(!"Unknown audioSource"); + } + if (error != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, _instanceID, + "ViEFileRecorder::StopRecording() failed to stop recording audio"); + } + } + CriticalSectionScoped lock(*_ptrCritSec); + if (_veFileInterface) + { + _veFileInterface->Release(); + _veFileInterface = NULL; + } + + if (_fileRecorder) + { + if (_fileRecorder->IsRecording()) + { + int error = _fileRecorder->StopRecording(); + if (error) + { + return -1; + } + } + FileRecorder::DestroyFileRecorder(_fileRecorder); + _fileRecorder = NULL; + } + _isFirstFrameRecorded = false; + _isOutStreamStarted = false; + return 0; +} + +void ViEFileRecorder::SetFrameDelay(int frameDelay) +{ + CriticalSectionScoped lock(*_ptrCritSec); + _frameDelay = frameDelay; +} + +bool ViEFileRecorder::RecordingStarted() +{ + CriticalSectionScoped lock(*_ptrCritSec); + return _fileRecorder && _fileRecorder->IsRecording(); +} + +bool ViEFileRecorder::FirstFrameRecorded() +{ + CriticalSectionScoped lock(*_ptrCritSec); + return _isFirstFrameRecorded; +} + +bool ViEFileRecorder::IsRecordingFileFormat(const webrtc::FileFormats fileFormat) +{ + CriticalSectionScoped lock(*_ptrCritSec); + return (_fileRecorder->RecordingFileFormat() == fileFormat) ? true : false; +} + +/******************************************************************************* + * void RecordVideoFrame() + * + * Records incoming decoded video frame to AVI-file. + * + */ +void ViEFileRecorder::RecordVideoFrame(const VideoFrame& videoFrame) +{ + CriticalSectionScoped lock(*_ptrCritSec); + + if (_fileRecorder && _fileRecorder->IsRecording()) + { + if (!IsRecordingFileFormat(webrtc::kFileFormatAviFile)) + { + return; + } + + //Compensate for frame delay in order to get audiosync when recording local video. + const WebRtc_UWord32 timeStamp = videoFrame.TimeStamp(); + const WebRtc_Word64 renderTimeStamp = videoFrame.RenderTimeMs(); + VideoFrame& unconstVideoFrame = + const_cast (videoFrame); + unconstVideoFrame.SetTimeStamp(timeStamp - 90 * _frameDelay); + unconstVideoFrame.SetRenderTime(renderTimeStamp - _frameDelay); + + _fileRecorder->RecordVideoToFile(unconstVideoFrame); + + unconstVideoFrame.SetRenderTime(renderTimeStamp); + unconstVideoFrame.SetTimeStamp(timeStamp); + } +} + +// --------------------- +// From OutStream +// --------------------- +// 10 ms block of PCM 16 +bool ViEFileRecorder::Write(const void* buf, int len) +{ + if (!_isOutStreamStarted) + return true; + + // always L16 from VoCE + if (len % (2 * 80)) // 2 bytes 80 samples + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, _audioChannel, + "Audio length not supported: %d.", len); + return true; + } + AudioFrame audioFrame; + WebRtc_UWord16 lengthInSamples = len / 2; + + audioFrame.UpdateFrame(_audioChannel, 0, (const WebRtc_Word16*) buf, + lengthInSamples, lengthInSamples * 100, + AudioFrame::kUndefined, + AudioFrame::kVadUnknown); + + CriticalSectionScoped lock(*_ptrCritSec); + + if (_fileRecorder && _fileRecorder->IsRecording()) + { + TickTime tickTime = TickTime::Now(); + _fileRecorder->RecordAudioToFile(audioFrame, &tickTime); + } + return true; // Always return true! +} + +int ViEFileRecorder::Rewind() +{ + // Not supported! + return -1; +} +} // namespace webrtc + diff --git a/video_engine/main/source/vie_file_recorder.h b/video_engine/main/source/vie_file_recorder.h new file mode 100644 index 0000000000..bae96f11a3 --- /dev/null +++ b/video_engine/main/source/vie_file_recorder.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_file_recorder.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_FILE_RECORDER_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_FILE_RECORDER_H_ + +#include "typedefs.h" +#include "file_recorder.h" +#include "vie_file.h" +#include "voe_file.h" + +namespace webrtc { +class CriticalSectionWrapper; + +class ViEFileRecorder: protected webrtc::OutStream // for audio +{ +public: + ViEFileRecorder(int channelId); + ~ViEFileRecorder(); + + int StartRecording(const char* fileNameUTF8, + const webrtc::VideoCodec& codecInst, + AudioSource audioSource, int audioChannel, + const webrtc::CodecInst audioCodecInst, + VoiceEngine* vePtr, + const webrtc::FileFormats fileFormat = webrtc::kFileFormatAviFile); + int StopRecording(); + void SetFrameDelay(int frameDelay); + bool RecordingStarted(); + void RecordVideoFrame(const VideoFrame& videoFrame); + +protected: + bool FirstFrameRecorded(); + bool IsRecordingFileFormat(const webrtc::FileFormats fileFormat); + // From webrtc::OutStream + bool Write(const void* buf, int len); + int Rewind(); + +private: + CriticalSectionWrapper* _ptrCritSec; + + FileRecorder* _fileRecorder; + bool _isFirstFrameRecorded; + bool _isOutStreamStarted; + int _instanceID; + int _frameDelay; + int _audioChannel; + AudioSource _audioSource; + VoEFile* _veFileInterface; +}; + +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_FILE_RECORDER_H_ diff --git a/video_engine/main/source/vie_frame_provider_base.cc b/video_engine/main/source/vie_frame_provider_base.cc new file mode 100644 index 0000000000..0fda87da9a --- /dev/null +++ b/video_engine/main/source/vie_frame_provider_base.cc @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "vie_frame_provider_base.h" +#include "critical_section_wrapper.h" +#include "tick_util.h" +#include "trace.h" +#include "vie_defines.h" + +namespace webrtc { + +ViEFrameProviderBase::ViEFrameProviderBase(int Id, int engineId): +_id(Id), +_engineId(engineId), +_frameCallbackMap(), +_providerCritSect(*CriticalSectionWrapper::CreateCriticalSection()), +_ptrExtraFrame(NULL), +_frameDelay(0) +{ +} + +ViEFrameProviderBase::~ViEFrameProviderBase() +{ + if(_frameCallbackMap.Size()>0) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, ViEId(_engineId,_id), "FramCallbacks still exist when Provider deleted %d",_frameCallbackMap.Size()); + } + for(MapItem* item=_frameCallbackMap.First();item!=NULL;item=_frameCallbackMap.Next(item)) + { + static_cast(item->GetItem())->ProviderDestroyed(_id); + } + + while(_frameCallbackMap.Erase(_frameCallbackMap.First()) == 0) + ; + + delete &_providerCritSect; + delete _ptrExtraFrame; +} + +int ViEFrameProviderBase::Id() +{ + return _id; +} + +void ViEFrameProviderBase::DeliverFrame(webrtc::VideoFrame& videoFrame,int numCSRCs, + const WebRtc_UWord32 CSRC[kRtpCsrcSize]) +{ +#ifdef _DEBUG + const TickTime startProcessTime=TickTime::Now(); +#endif + CriticalSectionScoped cs(_providerCritSect); + + // Deliver the frame to all registered callbacks + if (_frameCallbackMap.Size() > 0) + { + if(_frameCallbackMap.Size()==1) + { + ViEFrameCallback* frameObserver = static_cast(_frameCallbackMap.First()->GetItem()); + frameObserver->DeliverFrame(_id,videoFrame,numCSRCs,CSRC); + } + else + { + // Make a copy of the frame for all callbacks + for (MapItem* mapItem = _frameCallbackMap.First(); + mapItem != NULL; + mapItem = _frameCallbackMap.Next(mapItem)) + { + if (_ptrExtraFrame == NULL) + { + _ptrExtraFrame = new webrtc::VideoFrame(); + } + if (mapItem != NULL) + { + ViEFrameCallback* frameObserver = static_cast(mapItem->GetItem()); + if (frameObserver != NULL) + { + // We must copy the frame each time since the previous receiver might swap it... + _ptrExtraFrame->CopyFrame(videoFrame); + frameObserver->DeliverFrame(_id, *_ptrExtraFrame,numCSRCs,CSRC); + } + } + } + } + } + +#ifdef _DEBUG + const int processTime=(int) (TickTime::Now()-startProcessTime).Milliseconds(); + if(processTime>25) // Warn If the delivery time is too long. + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, ViEId(_engineId,_id), "%s Too long time: %ums",__FUNCTION__,processTime); + } +#endif +} + +void ViEFrameProviderBase::SetFrameDelay(int frameDelay) +{ + + CriticalSectionScoped cs(_providerCritSect); + _frameDelay=frameDelay; + + for (MapItem* mapItem = _frameCallbackMap.First(); + mapItem != NULL; + mapItem = _frameCallbackMap.Next(mapItem)) + { + ViEFrameCallback* frameObserver = static_cast(mapItem->GetItem()); + assert(frameObserver); + frameObserver->DelayChanged(_id,frameDelay); + } + +} + +int ViEFrameProviderBase::FrameDelay() +{ + return _frameDelay; +} + +int ViEFrameProviderBase::GetBestFormat(int& bestWidth, + int& bestHeight, + int& bestFrameRate) +{ + + int largestWidth = 0; + int largestHeight = 0; + int highestFrameRate = 0; + + CriticalSectionScoped cs(_providerCritSect); + + // Check if this one already exists... + for (MapItem* mapItem = _frameCallbackMap.First(); + mapItem != NULL; + mapItem = _frameCallbackMap.Next(mapItem)) + { + + + int preferedWidth=0; + int preferedHeight=0; + int preferedFrameRate=0; + + ViEFrameCallback* callbackObject = static_cast(mapItem->GetItem()); + assert(callbackObject); + if(callbackObject->GetPreferedFrameSettings(preferedWidth,preferedHeight,preferedFrameRate)==0) + { + if (preferedWidth > largestWidth) + { + largestWidth = preferedWidth; + } + if (preferedHeight > largestHeight) + { + largestHeight = preferedHeight; + } + if (preferedFrameRate > highestFrameRate) + { + highestFrameRate = preferedFrameRate; + } + } + } + + bestWidth = largestWidth; + bestHeight = largestHeight; + bestFrameRate = highestFrameRate; + + return 0; +} + +int ViEFrameProviderBase::RegisterFrameCallback(int observerId,ViEFrameCallback* callbackObject) +{ + if (callbackObject == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "%s: No argument", __FUNCTION__); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _id), + "%s(0x%p)", callbackObject); + + { + CriticalSectionScoped cs(_providerCritSect); + + // Check if this one already exists... + for (MapItem* mapItem = _frameCallbackMap.First(); + mapItem != NULL; + mapItem = _frameCallbackMap.Next(mapItem)) + { + const ViEFrameCallback* observer=static_cast (mapItem->GetItem()); + if (observer == callbackObject) + { + // This callback is already registered + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, ViEId(_engineId, _id), + "%s 0x%p already registered", __FUNCTION__, callbackObject); + + assert("!frameObserver already registered"); + return -1; + } + } + + if (_frameCallbackMap.Insert(observerId,callbackObject) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "%s: Could not add 0x%p to list", __FUNCTION__, callbackObject); + return -1; + } + } + // Report current capture delay + callbackObject->DelayChanged(_id,_frameDelay); + + FrameCallbackChanged(); // Notify implementer of this class that the callback list have changed + return 0; + + +} + + +// ---------------------------------------------------------------------------- +// DeregisterFrameCallback +// ---------------------------------------------------------------------------- + +int ViEFrameProviderBase::DeregisterFrameCallback(const ViEFrameCallback* callbackObject) +{ + if (callbackObject == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "%s: No argument", __FUNCTION__); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _id), + "%s(0x%p)", callbackObject); + + + { + CriticalSectionScoped cs(_providerCritSect); + bool itemFound=false; + + + // Try to find the callback in our list + for (MapItem* mapItem = _frameCallbackMap.First(); + mapItem != NULL; + mapItem = _frameCallbackMap.Next(mapItem)) + { + const ViEFrameCallback* observer=static_cast (mapItem->GetItem()); + if (observer == callbackObject) + { + // We found it, remove it! + _frameCallbackMap.Erase(mapItem); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _id), + "%s 0x%p deregistered", __FUNCTION__, callbackObject); + itemFound=true; + break; + } + } + if(!itemFound) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, ViEId(_engineId, _id), + "%s 0x%p not found", __FUNCTION__, callbackObject); + return -1; + } + } + + FrameCallbackChanged(); // Notify implementer of this class that the callback list have changed + return 0; +} + +// ---------------------------------------------------------------------------- +// IsFrameCallbackRegistered +// ---------------------------------------------------------------------------- + +bool ViEFrameProviderBase::IsFrameCallbackRegistered(const ViEFrameCallback* callbackObject) +{ + if (callbackObject == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id), + "%s: No argument", __FUNCTION__); + return false; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _id), + "%s(0x%p)", callbackObject); + + for (MapItem* mapItem = _frameCallbackMap.First(); + mapItem != NULL; + mapItem = _frameCallbackMap.Next(mapItem)) + { + if (callbackObject == mapItem->GetItem()) + { + // We found the callback + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _id), + "%s 0x%p is registered", __FUNCTION__, callbackObject); + return true; + } + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _id), + "%s 0x%p not registered", __FUNCTION__, callbackObject); + return false; +} + +// ---------------------------------------------------------------------------- +// NumberOfRegistersFrameCallbacks +// ---------------------------------------------------------------------------- + +int ViEFrameProviderBase::NumberOfRegistersFrameCallbacks() +{ + CriticalSectionScoped cs(_providerCritSect); + return _frameCallbackMap.Size(); +} +} // namespac webrtc diff --git a/video_engine/main/source/vie_frame_provider_base.h b/video_engine/main/source/vie_frame_provider_base.h new file mode 100644 index 0000000000..aa32a1f6c2 --- /dev/null +++ b/video_engine/main/source/vie_frame_provider_base.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_frame_provider_base.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_FRAME_PROVIDER_BASE_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_FRAME_PROVIDER_BASE_H_ + +// Defines +#include "typedefs.h" +#include "module_common_types.h" +#include "map_wrapper.h" + +namespace webrtc { +class CriticalSectionWrapper; +class VideoEncoder; + +class ViEFrameCallback +{ +public: + virtual void DeliverFrame(int id, VideoFrame& videoFrame, int numCSRCs = 0, + const WebRtc_UWord32 CSRC[kRtpCsrcSize] = NULL) = 0; + /* + * Delay has changed from the provider. + * frameDelay new capture delay in Ms. + */ + virtual void DelayChanged(int id, int frameDelay)=0; + + /* + Fetch the width, height and frame rate preferred by this observer. + return 0 on success, -1 otherwise. + */ + virtual int GetPreferedFrameSettings(int &width, int &height, + int &frameRate)=0; + + virtual void ProviderDestroyed(int id) = 0; + +protected: + virtual ~ViEFrameCallback() + { + } + ; +}; + +class ViEFrameProviderBase +{ +public: + ViEFrameProviderBase(int Id, int engineId); + virtual ~ViEFrameProviderBase(); + int Id(); + + // Register frame callbacks, i.e. a receiver of the captured frame. + virtual int RegisterFrameCallback(int observerId, + ViEFrameCallback* callbackObject); + virtual int + DeregisterFrameCallback(const ViEFrameCallback* callbackObject); + virtual bool + IsFrameCallbackRegistered(const ViEFrameCallback* callbackObject); + + int NumberOfRegistersFrameCallbacks(); + + // FrameCallbackChanged + // Inherited classes should check for new frameSettings and reconfigure output if possible. + // Return 0 on success, -1 otherwise. + virtual int FrameCallbackChanged() = 0; + +protected: + void DeliverFrame(VideoFrame& videoFrame, int numCSRCs = 0, + const WebRtc_UWord32 CSRC[kRtpCsrcSize] = NULL); + void SetFrameDelay(int frameDelay); + int FrameDelay(); + int GetBestFormat(int& bestWidth, int& bestHeight, int& bestFrameRate); + + int _id; + int _engineId; + +protected: + // Frame callbacks + MapWrapper _frameCallbackMap; + CriticalSectionWrapper& _providerCritSect; +private: + + VideoFrame* _ptrExtraFrame; + + //Members + int _frameDelay; + +}; + +} //namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_FRAME_PROVIDER_BASE_H_ diff --git a/video_engine/main/source/vie_image_process_impl.cc b/video_engine/main/source/vie_image_process_impl.cc new file mode 100644 index 0000000000..4908d0c657 --- /dev/null +++ b/video_engine/main/source/vie_image_process_impl.cc @@ -0,0 +1,396 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_image_process_impl.cpp + */ +#include "vie_image_process_impl.h" + +// Defines +#include "vie_defines.h" + +#include "trace.h" +#include "vie_errors.h" +#include "vie_impl.h" +#include "vie_channel.h" +#include "vie_channel_manager.h" +#include "vie_encoder.h" +#include "vie_input_manager.h" +#include "vie_capturer.h" + +namespace webrtc +{ + +// ---------------------------------------------------------------------------- +// GetInterface +// ---------------------------------------------------------------------------- + +ViEImageProcess* ViEImageProcess::GetInterface(VideoEngine* videoEngine) +{ +#ifdef WEBRTC_VIDEO_ENGINE_IMAGE_PROCESS_API + if (videoEngine == NULL) + { + return NULL; + } + VideoEngineImpl* vieImpl = reinterpret_cast (videoEngine); + ViEImageProcessImpl* vieImageProcessImpl = vieImpl; + (*vieImageProcessImpl)++; // Increase ref count + + return vieImageProcessImpl; +#else + return NULL; +#endif +} + +// ---------------------------------------------------------------------------- +// Release +// +// Releases the interface, i.e. reduces the reference counter. The number of +// remaining references is returned, -1 if released too many times. +// ---------------------------------------------------------------------------- + +int ViEImageProcessImpl::Release() +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, _instanceId, + "ViEImageProcess::Release()"); + (*this)--; // Decrease ref count + + WebRtc_Word32 refCount = GetCount(); + if (refCount < 0) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, _instanceId, + "ViEImageProcess release too many times"); + SetLastError(kViEAPIDoesNotExist); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, _instanceId, + "ViEImageProcess reference count: %d", refCount); + return refCount; +} + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + +ViEImageProcessImpl::ViEImageProcessImpl() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, _instanceId, + "ViEImageProcessImpl::ViEImageProcessImpl() Ctor"); +} + +// ---------------------------------------------------------------------------- +// Destructor +// ---------------------------------------------------------------------------- + +ViEImageProcessImpl::~ViEImageProcessImpl() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, _instanceId, + "ViEImageProcessImpl::~ViEImageProcessImpl() Dtor"); +} + +// ============================================================================ +// Effect filter +// ============================================================================ + +// ---------------------------------------------------------------------------- +// RegisterCaptureEffectFilter +// +// Registers an effect filter for a capture device +// ---------------------------------------------------------------------------- + +int ViEImageProcessImpl::RegisterCaptureEffectFilter( + const int captureId, ViEEffectFilter& captureFilter) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(captureId: %d)", __FUNCTION__, captureId); + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + + ViEInputManagerScoped is(_inputManager); + ViECapturer* vieCapture = is.Capture(captureId); + if (vieCapture == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: Capture device %d doesn't exist", __FUNCTION__, + captureId); + SetLastError(kViEImageProcessInvalidCaptureId); + return -1; + } + + if (vieCapture->RegisterEffectFilter(&captureFilter) != 0) + { + SetLastError(kViEImageProcessFilterExists); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// DeregisterCaptureEffectFilter +// +// Deregisters a previously set fffect filter +// ---------------------------------------------------------------------------- + +int ViEImageProcessImpl::DeregisterCaptureEffectFilter(const int captureId) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(captureId: %d)", __FUNCTION__, captureId); + + ViEInputManagerScoped is(_inputManager); + ViECapturer* vieCapture = is.Capture(captureId); + if (vieCapture == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: Capture device %d doesn't exist", __FUNCTION__, + captureId); + SetLastError(kViEImageProcessInvalidCaptureId); + return -1; + } + if (vieCapture->RegisterEffectFilter(NULL) != 0) + { + SetLastError(kViEImageProcessFilterDoesNotExist); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// RegisterSendEffectFilter +// +// Registers an effect filter for a channel +// ---------------------------------------------------------------------------- + +int ViEImageProcessImpl::RegisterSendEffectFilter(const int videoChannel, + ViEEffectFilter& sendFilter) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(videoChannel: %d)", __FUNCTION__, videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEEncoder* vieEncoder = cs.Encoder(videoChannel); + if (vieEncoder == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: Channel %d doesn't exist", __FUNCTION__, videoChannel); + SetLastError(kViEImageProcessInvalidChannelId); + return -1; + } + + if (vieEncoder->RegisterEffectFilter(&sendFilter) != 0) + { + SetLastError(kViEImageProcessFilterExists); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// DeregisterSendEffectFilter +// +// Deregisters a previously set effect filter +// ---------------------------------------------------------------------------- + +int ViEImageProcessImpl::DeregisterSendEffectFilter(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(videoChannel: %d)", __FUNCTION__, videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEEncoder* vieEncoder = cs.Encoder(videoChannel); + if (vieEncoder == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: Channel %d doesn't exist", __FUNCTION__, videoChannel); + SetLastError(kViEImageProcessInvalidChannelId); + return -1; + } + if (vieEncoder->RegisterEffectFilter(NULL) != 0) + { + SetLastError(kViEImageProcessFilterDoesNotExist); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// RegisterRenderEffectFilter +// +// Registers an effect filter for an incoming decoded stream +// ---------------------------------------------------------------------------- + +int ViEImageProcessImpl::RegisterRenderEffectFilter( + const int videoChannel, ViEEffectFilter& renderFilter) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(videoChannel: %d)", __FUNCTION__, videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (vieChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: Channel %d doesn't exist", __FUNCTION__, videoChannel); + SetLastError(kViEImageProcessInvalidChannelId); + return -1; + } + + if (vieChannel->RegisterEffectFilter(&renderFilter) != 0) + { + SetLastError(kViEImageProcessFilterExists); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// DeregisterRenderEffectFilter +// +// ---------------------------------------------------------------------------- + +int ViEImageProcessImpl::DeregisterRenderEffectFilter(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(videoChannel: %d)", __FUNCTION__, videoChannel); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (vieChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: Channel %d doesn't exist", __FUNCTION__, videoChannel); + SetLastError(kViEImageProcessInvalidChannelId); + return -1; + } + + if (vieChannel->RegisterEffectFilter(NULL) != 0) + { + SetLastError(kViEImageProcessFilterDoesNotExist); + return -1; + } + return 0; +} + +// ============================================================================ +// Image enhancement +// ============================================================================ + +// ---------------------------------------------------------------------------- +// EnableDeflickering +// +// Enables/disables deflickering of the captured image. +// ---------------------------------------------------------------------------- + +int ViEImageProcessImpl::EnableDeflickering(const int captureId, + const bool enable) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(captureId: %d, enable: %d)", __FUNCTION__, captureId, enable); + + ViEInputManagerScoped is(_inputManager); + ViECapturer* vieCapture = is.Capture(captureId); + if (vieCapture == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: Capture device %d doesn't exist", __FUNCTION__, + captureId); + SetLastError(kViEImageProcessInvalidChannelId); + return -1; + } + + if (vieCapture->EnableDeflickering(enable) != 0) + { + if (enable) + SetLastError(kViEImageProcessAlreadyEnabled); + else + { + SetLastError(kViEImageProcessAlreadyDisabled); + } + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// EnableDenoising +// +// Enables/disables denoising of the captured image. +// ---------------------------------------------------------------------------- + +int ViEImageProcessImpl::EnableDenoising(const int captureId, const bool enable) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(captureId: %d, enable: %d)", __FUNCTION__, captureId, enable); + + ViEInputManagerScoped is(_inputManager); + ViECapturer* vieCapture = is.Capture(captureId); + if (vieCapture == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: Capture device %d doesn't exist", __FUNCTION__, + captureId); + SetLastError(kViEImageProcessInvalidCaptureId); + return -1; + } + + if (vieCapture->EnableDenoising(enable) != 0) + { + if (enable) + SetLastError(kViEImageProcessAlreadyEnabled); + else + { + SetLastError(kViEImageProcessAlreadyDisabled); + } + return -1; + } + return 0; + +} + +// ---------------------------------------------------------------------------- +// EnableColorEnhancement +// +// Enables coloe enhancement for decoded images +// ---------------------------------------------------------------------------- + +int ViEImageProcessImpl::EnableColorEnhancement(const int videoChannel, + const bool enable) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(videoChannel: %d, enable: %d)", __FUNCTION__, videoChannel, + enable); + + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* vieChannel = cs.Channel(videoChannel); + if (vieChannel == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: Channel %d doesn't exist", __FUNCTION__, videoChannel); + SetLastError(kViEImageProcessInvalidChannelId); + return -1; + } + if (vieChannel->EnableColorEnhancement(enable) != 0) + { + if (enable) + SetLastError(kViEImageProcessAlreadyEnabled); + else + { + SetLastError(kViEImageProcessAlreadyDisabled); + } + return -1; + } + return 0; +} +} // namespace webrtc diff --git a/video_engine/main/source/vie_image_process_impl.h b/video_engine/main/source/vie_image_process_impl.h new file mode 100644 index 0000000000..d7494da4ad --- /dev/null +++ b/video_engine/main/source/vie_image_process_impl.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_image_process_impl.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_IMAGE_PROCESS_IMPL_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_IMAGE_PROCESS_IMPL_H_ + +#include "typedefs.h" +#include "vie_ref_count.h" +#include "vie_image_process.h" +#include "vie_shared_data.h" + +namespace webrtc +{ + +// ---------------------------------------------------------------------------- +// ViEImageProcessImpl +// ---------------------------------------------------------------------------- + +class ViEImageProcessImpl: public virtual ViESharedData, + public ViEImageProcess, + public ViERefCount +{ +public: + virtual int Release(); + + // Effect filter + virtual int RegisterCaptureEffectFilter(const int captureId, + ViEEffectFilter& captureFilter); + + virtual int DeregisterCaptureEffectFilter(const int captureId); + + virtual int RegisterSendEffectFilter(const int videoChannel, + ViEEffectFilter& sendFilter); + + virtual int DeregisterSendEffectFilter(const int videoChannel); + + virtual int RegisterRenderEffectFilter(const int videoChannel, + ViEEffectFilter& renderFilter); + + virtual int DeregisterRenderEffectFilter(const int videoChannel); + + // Image enhancement + virtual int EnableDeflickering(const int captureId, const bool enable); + + virtual int EnableDenoising(const int captureId, const bool enable); + + virtual int EnableColorEnhancement(const int videoChannel, + const bool enable); + +protected: + ViEImageProcessImpl(); + virtual ~ViEImageProcessImpl(); +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_IMAGE_PROCESS_IMPL_H_ diff --git a/video_engine/main/source/vie_impl.cc b/video_engine/main/source/vie_impl.cc new file mode 100644 index 0000000000..2f7af5b713 --- /dev/null +++ b/video_engine/main/source/vie_impl.cc @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_impl.cc + */ + +#include "vie_impl.h" +#include "trace.h" + +#if (defined(_WIN32) || defined(_WIN64)) +#include // For LoadLibrary +#include // For _T +#endif + +#ifdef ANDROID +#include "video_capture.h" +#include "video_render.h" +#endif + +// Global counter to get an id for each new ViE instance +static WebRtc_Word32 gViEActiveInstanceCounter = 0; + +namespace webrtc +{ + +// ------------------------------------------------------------------------- +// GetVideoEngine (C-function) +// +// extern "C" ensures that GetProcAddress() can find the function address +// ------------------------------------------------------------------------- + +extern "C" +{ +VideoEngine* GetVideoEngine(); + +VideoEngine* GetVideoEngine() +{ + VideoEngineImpl* self = new VideoEngineImpl(); + if (self == NULL) + { + return NULL; + } + gViEActiveInstanceCounter++; + VideoEngine* vie = reinterpret_cast (self); + return vie; +} +} + +// ------------------------------------------------------------------------- +// Create +// ------------------------------------------------------------------------- + +VideoEngine* VideoEngine::Create() +{ +#if (defined(_WIN32) || defined(_WIN64)) + // Load a debug dll, if there is one... + HMODULE hmod_ = LoadLibrary(TEXT("VideoEngineTestingDLL.dll")); + if (hmod_) + { + typedef VideoEngine* (*PFNGetVideoEngineLib)(void); + PFNGetVideoEngineLib pfn = + (PFNGetVideoEngineLib)GetProcAddress(hmod_,"GetVideoEngine"); + if (pfn) + { + VideoEngine* self = pfn(); + return self; + } + else + { + assert(!"Failed to open test dll VideoEngineTestingDLL.dll"); + return NULL; + } + } +#endif + + return GetVideoEngine(); +} + +// ------------------------------------------------------------------------- +// Delete +// +// Deletes the VideoEngineImpl instance if all reference counters are +// down to zero. +// ------------------------------------------------------------------------- + +bool VideoEngine::Delete(VideoEngine*& videoEngine) +{ + if (videoEngine == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "VideoEngine::Delete - No argument"); + return false; + } + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "VideoEngine::Delete( vie = 0x%p)", videoEngine); + + VideoEngineImpl* vieImpl = reinterpret_cast (videoEngine); + + // Check all reference counters + ViEBaseImpl* vieBase = vieImpl; + if (vieBase->GetCount() > 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "ViEBase ref count: %d", vieBase->GetCount()); + return false; + } +#ifdef WEBRTC_VIDEO_ENGINE_CAPTURE_API + ViECaptureImpl* vieCapture = vieImpl; + if (vieCapture->GetCount() > 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "ViECapture ref count: %d", vieCapture->GetCount()); + return false; + } +#endif +#ifdef WEBRTC_VIDEO_ENGINE_CODEC_API + ViECodecImpl* vieCodec = vieImpl; + if (vieCodec->GetCount() > 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "ViECodec ref count: %d", vieCodec->GetCount()); + return false; + } +#endif +#ifdef WEBRTC_VIDEO_ENGINE_ENCRYPTION_API + ViEEncryptionImpl* vieEncryption = vieImpl; + if (vieEncryption->GetCount() > 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "ViEEncryption ref count: %d", vieEncryption->GetCount()); + return false; + } +#endif +#ifdef WEBRTC_VIDEO_ENGINE_EXTERNAL_CODEC_API + ViEExternalCodecImpl* vieExternalCodec = vieImpl; + if (vieExternalCodec->GetCount() > 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "ViEEncryption ref count: %d", vieEncryption->GetCount()); + return false; + } +#endif +#ifdef WEBRTC_VIDEO_ENGINE_FILE_API + ViEFileImpl* vieFile = vieImpl; + if (vieFile->GetCount() > 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "ViEFile ref count: %d", vieFile->GetCount()); + return false; + } +#endif +#ifdef WEBRTC_VIDEO_ENGINE_IMAGE_PROCESS_API + ViEImageProcessImpl* vieImageProcess = vieImpl; + if (vieImageProcess->GetCount() > 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "ViEImageProcess ref count: %d", vieImageProcess->GetCount()); + return false; + } +#endif +#ifdef WEBRTC_VIDEO_ENGINE_NETWORK_API + ViENetworkImpl* vieNetwork = vieImpl; + if (vieNetwork->GetCount() > 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "ViENetwork ref count: %d", vieNetwork->GetCount()); + return false; + } +#endif +#ifdef WEBRTC_VIDEO_ENGINE_RENDER_API + ViERenderImpl* vieRender = vieImpl; + if (vieRender->GetCount() > 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "ViERender ref count: %d", vieRender->GetCount()); + return false; + } +#endif +#ifdef WEBRTC_VIDEO_ENGINE_RTP_RTCP_API + ViERTP_RTCPImpl* vieRtpRtcp = vieImpl; + if (vieRtpRtcp->GetCount() > 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "ViERTP_RTCP ref count: %d", vieRtpRtcp->GetCount()); + return false; + } +#endif + + // Delete VieImpl + delete vieImpl; + vieImpl = NULL; + videoEngine = NULL; + + // Decrease the number of instances + gViEActiveInstanceCounter--; + + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "%s: instance deleted. Remaining instances: %d", __FUNCTION__, + gViEActiveInstanceCounter); + + return true; +} + +// ------------------------------------------------------------------------- +// [static] SetTraceFile +// ------------------------------------------------------------------------- + +int VideoEngine::SetTraceFile(const char* fileNameUTF8, + const bool addFileCounter) +{ + if (fileNameUTF8 == NULL) + { + return -1; + } + if (Trace::SetTraceFile(fileNameUTF8, addFileCounter) == -1) + { + return -1; + } + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "SetTraceFileName(fileNameUTF8 = %s, addFileCounter = %d", + fileNameUTF8, addFileCounter); + return 0; +} + +// ------------------------------------------------------------------------- +// [static] SetTraceFilter +// ------------------------------------------------------------------------- + +int VideoEngine::SetTraceFilter(const unsigned int filter) +{ + WebRtc_UWord32 oldFilter = 0; + Trace::LevelFilter(oldFilter); + + if (filter == webrtc::kTraceNone && oldFilter != webrtc::kTraceNone) + { + // Do the logging before turning it off + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "SetTraceFilter(filter = 0x%x)", filter); + } + + WebRtc_Word32 error = Trace::SetLevelFilter(filter); + + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "SetTraceFilter(filter = 0x%x)", filter); + if (error != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "SetTraceFilter error: %d", error); + return -1; + } + + return 0; +} + +// ------------------------------------------------------------------------- +// [static] SetTraceFilter +// ------------------------------------------------------------------------- + +int VideoEngine::SetTraceCallback(webrtc::TraceCallback* callback) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "SetTraceCallback(webrtc::TraceCallback = 0x%p)", callback); + return Trace::SetTraceCallback(callback); +} + +// ------------------------------------------------------------------------- +// [static] SetAndroidObjects +// ------------------------------------------------------------------------- + +int VideoEngine::SetAndroidObjects(void* javaVM, void* javaContext) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "SetAndroidObjects()"); + +#ifdef ANDROID + if (VideoCaptureModule::SetAndroidObjects(javaVM,javaContext) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "Could not set capture module Android objects"); + return -1; + } + if (VideoRender::SetAndroidObjects(javaVM) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, gViEActiveInstanceCounter, + "Could not set render module Android objects"); + return -1; + } + return 0; +#else + return -1; +#endif +} +} // namespace webrtc diff --git a/video_engine/main/source/vie_impl.h b/video_engine/main/source/vie_impl.h new file mode 100644 index 0000000000..bea2065c67 --- /dev/null +++ b/video_engine/main/source/vie_impl.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_impl.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_IMPL_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_IMPL_H_ + +#include "engine_configurations.h" +#include "vie_defines.h" + +// Include all sub API:s + +#include "vie_base_impl.h" + +#ifdef WEBRTC_VIDEO_ENGINE_CAPTURE_API +#include "vie_capture_impl.h" +#endif +#ifdef WEBRTC_VIDEO_ENGINE_CODEC_API +#include "vie_codec_impl.h" +#endif +#ifdef WEBRTC_VIDEO_ENGINE_ENCRYPTION_API +#include "vie_encryption_impl.h" +#endif +#ifdef WEBRTC_VIDEO_ENGINE_FILE_API +#include "vie_file_impl.h" +#endif +#ifdef WEBRTC_VIDEO_ENGINE_IMAGE_PROCESS_API +#include "vie_image_process_impl.h" +#endif +#ifdef WEBRTC_VIDEO_ENGINE_NETWORK_API +#include "vie_network_impl.h" +#endif +#ifdef WEBRTC_VIDEO_ENGINE_RENDER_API +#include "vie_render_impl.h" +#endif +#ifdef WEBRTC_VIDEO_ENGINE_RTP_RTCP_API +#include "vie_rtp_rtcp_impl.h" +#endif +#ifdef WEBRTC_VIDEO_ENGINE_EXTERNAL_CODEC_API +#include "vie_external_codec_impl.h" +#endif + +namespace webrtc +{ + +class VideoEngineImpl: public ViEBaseImpl +#ifdef WEBRTC_VIDEO_ENGINE_CODEC_API + , public ViECodecImpl +#endif +#ifdef WEBRTC_VIDEO_ENGINE_CAPTURE_API + , public ViECaptureImpl +#endif +#ifdef WEBRTC_VIDEO_ENGINE_ENCRYPTION_API + , public ViEEncryptionImpl +#endif +#ifdef WEBRTC_VIDEO_ENGINE_FILE_API + , public ViEFileImpl +#endif +#ifdef WEBRTC_VIDEO_ENGINE_IMAGE_PROCESS_API + , public ViEImageProcessImpl +#endif +#ifdef WEBRTC_VIDEO_ENGINE_NETWORK_API + , public ViENetworkImpl +#endif +#ifdef WEBRTC_VIDEO_ENGINE_RENDER_API + , public ViERenderImpl +#endif +#ifdef WEBRTC_VIDEO_ENGINE_RTP_RTCP_API + , public ViERTP_RTCPImpl +#endif +#ifdef WEBRTC_VIDEO_ENGINE_EXTERNAL_CODEC_API +,public ViEExternalCodecImpl +#endif +{ +public: + VideoEngineImpl() {}; + virtual ~VideoEngineImpl() {}; +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_IMPL_H_ diff --git a/video_engine/main/source/vie_input_manager.cc b/video_engine/main/source/vie_input_manager.cc new file mode 100644 index 0000000000..3f7a2607a4 --- /dev/null +++ b/video_engine/main/source/vie_input_manager.cc @@ -0,0 +1,817 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_input_manager.cc + */ + +#include "vie_input_manager.h" +#include "vie_defines.h" + +#include "common_types.h" +#include "critical_section_wrapper.h" +#include "video_capture.h" +#include "video_capture.h" +#include "video_coding.h" +#include "video_coding_defines.h" +#include "rw_lock_wrapper.h" +#include "trace.h" +#include "vie_capturer.h" +#include "vie_file_player.h" +#include "vie_errors.h" + +#include + +namespace webrtc { + +//============================================================================= +// ViEInputManager +//============================================================================= + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + +ViEInputManager::ViEInputManager(const int engineId) + : _engineId(engineId), + _mapCritsect(*CriticalSectionWrapper::CreateCriticalSection()), + _ptrCaptureDeviceInfo(NULL), _vieFrameProviderMap(), + _freeCaptureDeviceId(), _moduleProcessThread(NULL) +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, ViEId(_engineId), "%s", + __FUNCTION__); + + for (int idx = 0; idx < kViEMaxCaptureDevices; idx++) + { + _freeCaptureDeviceId[idx] = true; + } +#ifdef WEBRTC_VIDEO_EXTERNAL_CAPTURE_AND_RENDER + _ptrCaptureDeviceInfo=NULL; +#else + _ptrCaptureDeviceInfo = VideoCaptureModule::CreateDeviceInfo( + ViEModuleId(_engineId)); +#endif + for (int idx = 0; idx < kViEMaxFilePlayers; idx++) + { + _freeFileId[idx] = true; + } + +} + +// ---------------------------------------------------------------------------- +// Destructor +// ---------------------------------------------------------------------------- + +ViEInputManager::~ViEInputManager() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, ViEId(_engineId), "%s", + __FUNCTION__); + while (_vieFrameProviderMap.Size() != 0) + { + MapItem* item = _vieFrameProviderMap.First(); + assert(item); + ViEFrameProviderBase* frameProvider = static_cast + (item->GetItem()); + _vieFrameProviderMap.Erase(item); + delete frameProvider; + } + + delete &_mapCritsect; + if (_ptrCaptureDeviceInfo) + { + VideoCaptureModule::DestroyDeviceInfo( _ptrCaptureDeviceInfo); + _ptrCaptureDeviceInfo = NULL; + } +} + +// ---------------------------------------------------------------------------- +// SetModuleProcessThread +// Initialize the thread context used by none time critical tasks in capture modules. +// ---------------------------------------------------------------------------- +void ViEInputManager::SetModuleProcessThread(ProcessThread& moduleProcessThread) +{ + assert(!_moduleProcessThread); + _moduleProcessThread = &moduleProcessThread; +} +// ---------------------------------------------------------------------------- +// NumberOfCaptureDevices +// +// Returns the number of available capture devices +// ---------------------------------------------------------------------------- + +// Capture device information +int ViEInputManager::NumberOfCaptureDevices() +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), "%s", + __FUNCTION__); + assert(_ptrCaptureDeviceInfo); + return _ptrCaptureDeviceInfo->NumberOfDevices(); +} + +// ---------------------------------------------------------------------------- +// GetDeviceName +// ---------------------------------------------------------------------------- + +int ViEInputManager::GetDeviceName(WebRtc_UWord32 deviceNumber, + WebRtc_UWord8* deviceNameUTF8, + WebRtc_UWord32 deviceNameLength, + WebRtc_UWord8* deviceUniqueIdUTF8, + WebRtc_UWord32 deviceUniqueIdUTF8Length) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), + "%s(deviceNumber: %d)", __FUNCTION__, deviceNumber); + assert(_ptrCaptureDeviceInfo); + return _ptrCaptureDeviceInfo->GetDeviceName(deviceNumber, deviceNameUTF8, + deviceNameLength, + deviceUniqueIdUTF8, + deviceUniqueIdUTF8Length); +} + +// ---------------------------------------------------------------------------- +// NumberOfCaptureCapabilities +// +// Returns the number of capture capabilities for the specified capture device +// ---------------------------------------------------------------------------- + +int ViEInputManager::NumberOfCaptureCapabilities( + const WebRtc_UWord8* deviceUniqueIdUTF8) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), "%s", + __FUNCTION__); + assert(_ptrCaptureDeviceInfo); + return _ptrCaptureDeviceInfo->NumberOfCapabilities(deviceUniqueIdUTF8); +} + +// ---------------------------------------------------------------------------- +// GetCaptureCapability +// ---------------------------------------------------------------------------- + +int ViEInputManager::GetCaptureCapability(const WebRtc_UWord8* deviceUniqueIdUTF8, + const WebRtc_UWord32 deviceCapabilityNumber, + CaptureCapability& capability) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), + "%s(deviceUniqueIdUTF8: %s, deviceCapabilityNumber: %d)", + __FUNCTION__, deviceUniqueIdUTF8, deviceCapabilityNumber); + assert(_ptrCaptureDeviceInfo); + VideoCaptureCapability moduleCapability; + int result = _ptrCaptureDeviceInfo->GetCapability(deviceUniqueIdUTF8, + deviceCapabilityNumber, + moduleCapability); + // Copy from module type to public type + capability.expectedCaptureDelay = moduleCapability.expectedCaptureDelay; + capability.height = moduleCapability.height; + capability.width = moduleCapability.width; + capability.interlaced = moduleCapability.interlaced; + capability.rawType = moduleCapability.rawType; + capability.codecType = moduleCapability.codecType; + capability.maxFPS = moduleCapability.maxFPS; + return result; +} + +int ViEInputManager::GetOrientation(const WebRtc_UWord8* deviceUniqueIdUTF8, + RotateCapturedFrame &orientation) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), + "%s(deviceUniqueIdUTF8: %s,)", __FUNCTION__, deviceUniqueIdUTF8); + assert(_ptrCaptureDeviceInfo); + VideoCaptureRotation moduleOrientation; + int result = _ptrCaptureDeviceInfo->GetOrientation(deviceUniqueIdUTF8, + moduleOrientation); + // Copy from module type to public type + switch (moduleOrientation) + { + case kCameraRotate0: + orientation = RotateCapturedFrame_0; + break; + case kCameraRotate90: + orientation = RotateCapturedFrame_90; + break; + case kCameraRotate180: + orientation = RotateCapturedFrame_180; + break; + case kCameraRotate270: + orientation = RotateCapturedFrame_270; + break; + default: + assert(!"Unknown enum"); + } + return result; +} + +//------------------------------------------------------------------------------ +// +// DisplayCaptureSettingsDialogBox +// Show OS specific Capture settings. +// Return 0 on success. +//------------------------------------------------------------------------------ +int ViEInputManager::DisplayCaptureSettingsDialogBox( + const WebRtc_UWord8* deviceUniqueIdUTF8, + const WebRtc_UWord8* dialogTitleUTF8, + void* parentWindow, + WebRtc_UWord32 positionX, + WebRtc_UWord32 positionY) +{ + assert(_ptrCaptureDeviceInfo); + return _ptrCaptureDeviceInfo->DisplayCaptureSettingsDialogBox( + deviceUniqueIdUTF8, + dialogTitleUTF8, + parentWindow, + positionX, + positionY); +} +// ---------------------------------------------------------------------------- +// CreateCaptureDevice +// +// Creates a capture module for the specified capture device and assigns +// a capture device id for the device +// ---------------------------------------------------------------------------- + +int ViEInputManager::CreateCaptureDevice(const WebRtc_UWord8* deviceUniqueIdUTF8, + const WebRtc_UWord32 deviceUniqueIdUTF8Length, + int& captureId) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), + "%s(deviceUniqueId: %s)", __FUNCTION__, deviceUniqueIdUTF8); +#ifdef WEBRTC_VIDEO_EXTERNAL_CAPTURE_AND_RENDER + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s(deviceUniqueId: Only external capture modules can be used.) " + , __FUNCTION__); + return -1; +#endif + CriticalSectionScoped cs(_mapCritsect); + + // Make sure the device is not already allocated + for (MapItem* item = _vieFrameProviderMap.First(); item != NULL; + item = _vieFrameProviderMap.Next(item)) + { + if (item->GetId() >= kViECaptureIdBase && + item->GetId() <= kViECaptureIdMax) // Make sure it is a capture device + { + ViECapturer* vieCapture = static_cast (item->GetItem()); + assert(vieCapture); + if (strncmp((char*) vieCapture->CurrentDeviceName(), + (char*) deviceUniqueIdUTF8, + strlen((char*) vieCapture->CurrentDeviceName())) == 0) + { + return kViECaptureDeviceAlreadyAllocated; + } + } + } + + // Make sure the device name is valid + bool foundDevice = false; + for (WebRtc_UWord32 deviceIndex = 0; + deviceIndex < _ptrCaptureDeviceInfo->NumberOfDevices(); ++deviceIndex) + { + if (deviceUniqueIdUTF8Length >kVideoCaptureUniqueNameLength) + { + // user's string length is longer than the max + return -1; + } + + WebRtc_UWord8 foundName[kVideoCaptureDeviceNameLength] = ""; + WebRtc_UWord8 foundUniqueName[kVideoCaptureUniqueNameLength] = ""; + _ptrCaptureDeviceInfo->GetDeviceName(deviceIndex, foundName, + kVideoCaptureDeviceNameLength, + foundUniqueName, + kVideoCaptureUniqueNameLength); + + if (strncmp((char*) deviceUniqueIdUTF8, (char*) foundUniqueName, + strlen((char*) deviceUniqueIdUTF8)) == 0) + { + WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideo, ViEId(_engineId), + "%s:%d Capture device was found by unique ID: %s. Returning", + __FUNCTION__, __LINE__, deviceUniqueIdUTF8); + foundDevice = true; + break; + } + } + if (!foundDevice) + { + WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideo, ViEId(_engineId), + "%s:%d Capture device NOT found by unique ID: %s. Returning", + __FUNCTION__, __LINE__, deviceUniqueIdUTF8); + return kViECaptureDeviceDoesnNotExist; + } + + int newcaptureId = 0; + if (GetFreeCaptureId(newcaptureId) == false) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s: Maximum supported number of capture devices already in use", + __FUNCTION__); + return kViECaptureDeviceMaxNoDevicesAllocated; + } + ViECapturer* vieCapture =ViECapturer::CreateViECapture(newcaptureId, + _engineId, + deviceUniqueIdUTF8, + deviceUniqueIdUTF8Length, + *_moduleProcessThread); + if (vieCapture == NULL) + { + ReturnCaptureId(newcaptureId); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s: Could not create capture module for %s", __FUNCTION__, + deviceUniqueIdUTF8); + return kViECaptureDeviceUnknownError; + } + + if (_vieFrameProviderMap.Insert(newcaptureId, vieCapture) != 0) + { + ReturnCaptureId(newcaptureId); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s: Could not insert capture module for %s", __FUNCTION__, + deviceUniqueIdUTF8); + return kViECaptureDeviceUnknownError; + } + captureId = newcaptureId; + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), + "%s(deviceUniqueId: %s, captureId: %d)", __FUNCTION__, + deviceUniqueIdUTF8, captureId); + + return 0; +} + +int ViEInputManager::CreateCaptureDevice(VideoCaptureModule& captureModule, + int& captureId) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), "%s", __FUNCTION__); + + CriticalSectionScoped cs(_mapCritsect); + + int newcaptureId = 0; + if (GetFreeCaptureId(newcaptureId) == false) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s: Maximum supported number of capture devices already in use", + __FUNCTION__); + return kViECaptureDeviceMaxNoDevicesAllocated; + } + + ViECapturer* vieCapture = ViECapturer::CreateViECapture(newcaptureId, + _engineId, + captureModule, + *_moduleProcessThread); + if (vieCapture == NULL) + { + ReturnCaptureId(newcaptureId); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s: Could attach capture module.", __FUNCTION__); + return kViECaptureDeviceUnknownError; + } + if (_vieFrameProviderMap.Insert(newcaptureId, vieCapture) != 0) + { + ReturnCaptureId(newcaptureId); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s: Could not insert capture module", __FUNCTION__); + return kViECaptureDeviceUnknownError; + } + + captureId = newcaptureId; + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), + "%s, captureId: %d", __FUNCTION__, captureId); + + return 0; + +} + +// ---------------------------------------------------------------------------- +// DestroyCaptureDevice +// +// Releases the capture device with specified id +// ---------------------------------------------------------------------------- + +int ViEInputManager::DestroyCaptureDevice(const int captureId) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), + "%s(captureId: %d)", __FUNCTION__, captureId); + + ViECapturer* vieCapture = NULL; + { + // We need exclusive access to the object to delete it + ViEManagerWriteScoped wl(*this); // Take this write lock first since the read lock is taken before _mapCritsect + CriticalSectionScoped cs(_mapCritsect); + + vieCapture = ViECapturePtr(captureId); + if (vieCapture == NULL) + { + // No capture deveice with that id + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s(captureId: %d) - No such capture device id", + __FUNCTION__, captureId); + return -1; + } + WebRtc_UWord32 numCallbacks = vieCapture->NumberOfRegistersFrameCallbacks(); + if (numCallbacks > 0) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, ViEId(_engineId), + "%s(captureId: %d) - %u registered callbacks when destroying capture device", + __FUNCTION__, captureId, numCallbacks); + } + _vieFrameProviderMap.Erase(captureId); + ReturnCaptureId(captureId); + } // Leave cs before deleting the capture object. This is because deleting the object might cause deletions of renderers so we prefer to not have a lock at that time. + delete vieCapture; + return 0; +} +// ---------------------------------------------------------------------------- +// CreateExternalCaptureDevice +// +// Creates a capture module to be used with external captureing. +// ---------------------------------------------------------------------------- +int ViEInputManager::CreateExternalCaptureDevice(ViEExternalCapture*& externalCapture, + int& captureId) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), "%s", + __FUNCTION__); +#ifdef WEBRTC_VIDEO_EXTERNAL_CAPTURE_AND_RENDER + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s(deviceUniqueId: Only external capture modules can be used.) " + , __FUNCTION__); + return -1; +#endif + CriticalSectionScoped cs(_mapCritsect); + + int newcaptureId = 0; + if (GetFreeCaptureId(newcaptureId) == false) + { + WEBRTC_TRACE( webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s: Maximum supported number of capture devices already in use", + __FUNCTION__); + return kViECaptureDeviceMaxNoDevicesAllocated; + } + + ViECapturer* vieCapture = ViECapturer::CreateViECapture(newcaptureId, + _engineId, NULL, 0, + *_moduleProcessThread); + if (vieCapture == NULL) + { + ReturnCaptureId(newcaptureId); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s: Could not create capture module for external capture.", + __FUNCTION__); + return kViECaptureDeviceUnknownError; + } + + if (_vieFrameProviderMap.Insert(newcaptureId, vieCapture) != 0) + { + ReturnCaptureId(newcaptureId); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s: Could not insert capture module for external capture.", + __FUNCTION__); + return kViECaptureDeviceUnknownError; + } + captureId = newcaptureId; + externalCapture = vieCapture; + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), + "%s, captureId: %d)", __FUNCTION__, captureId); + return 0; +} + +int ViEInputManager::CreateFilePlayer(const WebRtc_Word8* fileNameUTF8, + const bool loop, + const webrtc::FileFormats fileFormat, + VoiceEngine* vePtr, int& fileId) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), + "%s(deviceUniqueId: %s)", __FUNCTION__, fileNameUTF8); + + CriticalSectionScoped cs(_mapCritsect); + + int newFileId = 0; + if (GetFreeFileId(newFileId) == false) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s: Maximum supported number of file players already in use", + __FUNCTION__); + return kViEFileMaxNoOfFilesOpened; + } + + ViEFilePlayer* vieFilePlayer = ViEFilePlayer::CreateViEFilePlayer( + newFileId, _engineId, fileNameUTF8, + loop, fileFormat, *this, vePtr); + if (vieFilePlayer == NULL) + { + ReturnFileId(newFileId); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s: Could not open file %s for playback", __FUNCTION__, + fileNameUTF8); + return kViEFileUnknownError; + } + + if (_vieFrameProviderMap.Insert(newFileId, vieFilePlayer) != 0) + { + ReturnCaptureId(newFileId); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s: Could not insert file player for %s", __FUNCTION__, + fileNameUTF8); + delete vieFilePlayer; + return kViEFileUnknownError; + } + + fileId = newFileId; + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), + "%s(filename: %s, fileId: %d)", __FUNCTION__, fileNameUTF8, + newFileId); + return 0; +} + +int ViEInputManager::DestroyFilePlayer(int fileId) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), + "%s(fileId: %d)", __FUNCTION__, fileId); + + ViEFilePlayer* vieFilePlayer = NULL; + { + // We need exclusive access to the object to delete it + ViEManagerWriteScoped wl(*this); // Take this write lock first since the read lock is taken before _mapCritsect + + CriticalSectionScoped cs(_mapCritsect); + + vieFilePlayer = ViEFilePlayerPtr(fileId); + if (vieFilePlayer == NULL) + { + // No capture deveice with that id + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s(fileId: %d) - No such file player", + __FUNCTION__, fileId); + + return -1; + } + int numCallbacks = + vieFilePlayer->NumberOfRegistersFrameCallbacks(); + if (numCallbacks > 0) + { + WEBRTC_TRACE( webrtc::kTraceWarning, webrtc::kTraceVideo, ViEId(_engineId), + "%s(fileId: %d) - %u registered callbacks when destroying file player", + __FUNCTION__, fileId, numCallbacks); + } + _vieFrameProviderMap.Erase(fileId); + ReturnFileId(fileId); + } // Leave cs before deleting the file object. This is because deleting the object might cause deletions of renderers so we prefer to not have a lock at that time. + delete vieFilePlayer; + return 0; +} + +// ============================================================================ +// Private methods +// ============================================================================ + +// ---------------------------------------------------------------------------- +// GetFreeCaptureId +// +// Gets and allocates a free capture device id. Assumed protected by caller +// ---------------------------------------------------------------------------- + +// Private, asummed protected +bool ViEInputManager::GetFreeCaptureId(int& freecaptureId) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), "%s", + __FUNCTION__); + + for (int id = 0; id < kViEMaxCaptureDevices; id++) + { + if (_freeCaptureDeviceId[id]) + { + // We found a free capture device id + _freeCaptureDeviceId[id] = false; + freecaptureId = id + kViECaptureIdBase; + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), + "%s: new id: %d", __FUNCTION__, freecaptureId); + return true; + } + } + return false; +} + +// ---------------------------------------------------------------------------- +// ReturnCaptureId +// +// Frees a capture id assigned in GetFreeCaptureId +// ---------------------------------------------------------------------------- + +void ViEInputManager::ReturnCaptureId(int captureId) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), "%s(%d)", + __FUNCTION__, captureId); + + CriticalSectionScoped cs(_mapCritsect); + if (captureId >= kViECaptureIdBase && + captureId < kViEMaxCaptureDevices + kViECaptureIdBase) + { + _freeCaptureDeviceId[captureId - kViECaptureIdBase] = true; + } + + return; +} + +// ---------------------------------------------------------------------------- +// GetFreeFileId +// +// Gets and allocates a free file id. Assumed protected by caller +// ---------------------------------------------------------------------------- + +// Private, asumed protected +bool ViEInputManager::GetFreeFileId(int& freeFileId) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), "%s", + __FUNCTION__); + + for (int id = 0; id < kViEMaxFilePlayers; id++) + { + if (_freeFileId[id]) + { + // We found a free capture device id + _freeFileId[id] = false; + freeFileId = id + kViEFileIdBase; + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), + "%s: new id: %d", __FUNCTION__, freeFileId); + return true; + } + } + return false; +} +// ---------------------------------------------------------------------------- +// ReturnFileId +// +// Frees a file id assigned in GetFreeFileId +// ---------------------------------------------------------------------------- +void ViEInputManager::ReturnFileId(int fileId) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), "%s(%d)", + __FUNCTION__, fileId); + + CriticalSectionScoped cs(_mapCritsect); + if (fileId >= kViEFileIdBase && + fileId < kViEMaxFilePlayers + kViEFileIdBase) + { + _freeFileId[fileId - kViEFileIdBase] = true; + } + return; +} + +// ============================================================================ +// Methods used by ViECaptureScoped + +// ---------------------------------------------------------------------------- +// ViECapturePtr +// +// Gets the ViECapturer for the capture device id +// ---------------------------------------------------------------------------- + +ViECapturer* ViEInputManager::ViECapturePtr(int captureId) const +{ + if (!(captureId >= kViECaptureIdBase && + captureId <= kViECaptureIdBase + kViEMaxCaptureDevices)) + return NULL; + + CriticalSectionScoped cs(_mapCritsect); + MapItem* mapItem = _vieFrameProviderMap.Find(captureId); + if (mapItem == NULL) + { + // No ViEEncoder for this channel... + return NULL; + } + ViECapturer* vieCapture = static_cast (mapItem->GetItem()); + return vieCapture; +} + +// ---------------------------------------------------------------------------- +// ViEFrameProvider +// +// Gets the ViEFrameProvider for this capture observer. +// ---------------------------------------------------------------------------- + +ViEFrameProviderBase* ViEInputManager::ViEFrameProvider( + const ViEFrameCallback* captureObserver) const +{ + + assert(captureObserver); + CriticalSectionScoped cs(_mapCritsect); + + for (MapItem* providerItem = _vieFrameProviderMap.First(); providerItem + != NULL; providerItem = _vieFrameProviderMap.Next(providerItem)) + { + ViEFrameProviderBase* vieFrameProvider = static_cast + (providerItem->GetItem()); + assert(vieFrameProvider != NULL); + + if (vieFrameProvider->IsFrameCallbackRegistered(captureObserver)) + { + // We found it + return vieFrameProvider; + } + } + // No capture device set for this channel + return NULL; +} + +// ---------------------------------------------------------------------------- +// ViEFrameProvider +// +// Gets the ViEFrameProvider for this capture observer. +// ---------------------------------------------------------------------------- + +ViEFrameProviderBase* ViEInputManager::ViEFrameProvider(int providerId) const +{ + CriticalSectionScoped cs(_mapCritsect); + MapItem* mapItem = _vieFrameProviderMap.Find(providerId); + if (mapItem == NULL) + { + return NULL; + } + + ViEFrameProviderBase* vieFrameProvider = static_cast + (mapItem->GetItem()); + return vieFrameProvider; +} + +// ---------------------------------------------------------------------------- +// GetViECaptures +// +// Gets the the entire map with GetViECaptures +// ---------------------------------------------------------------------------- +void ViEInputManager::GetViECaptures(MapWrapper& vieCaptureMap) +{ + CriticalSectionScoped cs(_mapCritsect); + + if (_vieFrameProviderMap.Size() == 0) + { + // No ViECaptures + return; + } + // Add all items to the map + for (MapItem* item = _vieFrameProviderMap.First(); + item != NULL; + item = _vieFrameProviderMap.Next(item)) + { + vieCaptureMap.Insert(item->GetId(), item->GetItem()); + } + return; +} + +// ---------------------------------------------------------------------------- +// ViEFilePlayerPtr +// +// Gets the ViEFilePlayer for this fileId +// ---------------------------------------------------------------------------- + +ViEFilePlayer* ViEInputManager::ViEFilePlayerPtr(int fileId) const +{ + if (fileId < kViEFileIdBase || fileId > kViEFileIdMax) + return NULL; + + CriticalSectionScoped cs(_mapCritsect); + MapItem* mapItem = _vieFrameProviderMap.Find(fileId); + if (mapItem == NULL) + { + // No ViEFilePlayer for this fileId... + return NULL; + } + ViEFilePlayer* vieFilePlayer = + static_cast (mapItem->GetItem()); + return vieFilePlayer; +} + +// ---------------------------------------------------------------------------- +// ViEInputManagerScoped +// +// Provides protected access to ViEInputManater +// ---------------------------------------------------------------------------- +ViEInputManagerScoped::ViEInputManagerScoped( + const ViEInputManager& vieInputManager) + : ViEManagerScopedBase(vieInputManager) +{ +} +ViECapturer* ViEInputManagerScoped::Capture(int captureId) const +{ + return static_cast + (_vieManager)->ViECapturePtr(captureId); +} +ViEFrameProviderBase* ViEInputManagerScoped::FrameProvider( + const ViEFrameCallback* captureObserver) const +{ + return static_cast + (_vieManager)->ViEFrameProvider(captureObserver); +} +ViEFrameProviderBase* ViEInputManagerScoped::FrameProvider(int providerId) const +{ + return static_cast + (_vieManager)->ViEFrameProvider( providerId); +} + +ViEFilePlayer* ViEInputManagerScoped::FilePlayer(int fileId) const +{ + return static_cast + (_vieManager)->ViEFilePlayerPtr(fileId); +} + +} // namespace webrtc diff --git a/video_engine/main/source/vie_input_manager.h b/video_engine/main/source/vie_input_manager.h new file mode 100644 index 0000000000..ec4a81a48c --- /dev/null +++ b/video_engine/main/source/vie_input_manager.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_input_manager.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_INPUT_MANAGER_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_INPUT_MANAGER_H_ + +#include "vie_defines.h" +#include "typedefs.h" +#include "map_wrapper.h" +#include "video_capture.h" +#include "vie_manager_base.h" +#include "vie_frame_provider_base.h" +#include "vie_capture.h" + +class ViEExternalCapture; + +namespace webrtc { +class CriticalSectionWrapper; +class ProcessThread; +class RWLockWrapper; +class ViECapturer; +class ViEFilePlayer; +class VoiceEngine; + +class ViEInputManager: private ViEManagerBase +{ + friend class ViEInputManagerScoped; +public: + ViEInputManager(int engineId); + ~ViEInputManager(); + + void SetModuleProcessThread(ProcessThread& moduleProcessThread); + + // Capture device information + int NumberOfCaptureDevices(); + int GetDeviceName(WebRtc_UWord32 deviceNumber, + WebRtc_UWord8* deviceNameUTF8, + WebRtc_UWord32 deviceNameLength, + WebRtc_UWord8* deviceUniqueIdUTF8, + WebRtc_UWord32 deviceUniqueIdUTF8Length); + int NumberOfCaptureCapabilities(const WebRtc_UWord8* deviceUniqueIdUTF8); + int GetCaptureCapability(const WebRtc_UWord8* deviceUniqueIdUTF8, + const WebRtc_UWord32 deviceCapabilityNumber, + CaptureCapability& capability); + int DisplayCaptureSettingsDialogBox(const WebRtc_UWord8* deviceUniqueIdUTF8, + const WebRtc_UWord8* dialogTitleUTF8, + void* parentWindow, + WebRtc_UWord32 positionX, + WebRtc_UWord32 positionY); + int GetOrientation(const WebRtc_UWord8* deviceUniqueIdUTF8, + RotateCapturedFrame &orientation); + + // Create/delete Capture device settings + // Return zero on success. A ViEError on failure. + int CreateCaptureDevice(const WebRtc_UWord8* deviceUniqueIdUTF8, + const WebRtc_UWord32 deviceUniqueIdUTF8Length, + int& captureId); + int CreateCaptureDevice(VideoCaptureModule& captureModule, + int& captureId); + int CreateExternalCaptureDevice(ViEExternalCapture*& externalCapture, + int& captureId); + int DestroyCaptureDevice(int captureId); + + int CreateFilePlayer(const WebRtc_Word8* fileNameUTF8, const bool loop, + const webrtc::FileFormats fileFormat, VoiceEngine* vePtr, + int& fileId); + int DestroyFilePlayer(int fileId); + +private: + bool GetFreeCaptureId(int& freecaptureId); + void ReturnCaptureId(int captureId); + bool GetFreeFileId(int& freeFileId); + void ReturnFileId(int fileId); + + ViEFrameProviderBase* ViEFrameProvider(const ViEFrameCallback* + captureObserver) const; + ViEFrameProviderBase* ViEFrameProvider(int providerId) const; + + ViECapturer* ViECapturePtr(int captureId) const; + void GetViECaptures(MapWrapper& vieCaptureMap); + + ViEFilePlayer* ViEFilePlayerPtr(int fileId) const; + + // Members + int _engineId; + CriticalSectionWrapper& _mapCritsect; + MapWrapper _vieFrameProviderMap; + + // Capture devices + VideoCaptureModule::DeviceInfo* _ptrCaptureDeviceInfo; + int _freeCaptureDeviceId[kViEMaxCaptureDevices]; + //File Players + int _freeFileId[kViEMaxFilePlayers]; + //uses + ProcessThread* _moduleProcessThread; +}; + +class ViEInputManagerScoped: private ViEManagerScopedBase +{ +public: + ViEInputManagerScoped(const ViEInputManager& vieInputManager); + + ViECapturer* Capture(int captureId) const; + ViEFilePlayer* FilePlayer(int fileId) const; + ViEFrameProviderBase* FrameProvider(int providerId) const; + ViEFrameProviderBase* FrameProvider(const ViEFrameCallback* + captureObserver) const; +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_INPUT_MANAGER_H_ diff --git a/video_engine/main/source/vie_manager_base.cc b/video_engine/main/source/vie_manager_base.cc new file mode 100644 index 0000000000..4d36a16c53 --- /dev/null +++ b/video_engine/main/source/vie_manager_base.cc @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "vie_manager_base.h" +#include "rw_lock_wrapper.h" +#include "assert.h" + +namespace webrtc { + +ViEManagerBase::ViEManagerBase() : + _instanceRWLock(*RWLockWrapper::CreateRWLock()) +{ +} +ViEManagerBase::~ViEManagerBase() +{ + delete &_instanceRWLock; +} + +// ---------------------------------------------------------------------------- +// ReadLockManager +// +// Lock count increase. Used by ViEManagerScopedBase +// ---------------------------------------------------------------------------- +void ViEManagerBase::ReadLockManager() const +{ + _instanceRWLock.AcquireLockShared(); +} + +// ---------------------------------------------------------------------------- +// ReleaseLockManager +// +// Releases the lock count. +// ---------------------------------------------------------------------------- +void ViEManagerBase::ReleaseLockManager() const +{ + _instanceRWLock.ReleaseLockShared(); +} + +// ---------------------------------------------------------------------------- +// WriteLockManager +// +// Lock count increase. Used by ViEManagerWriteScoped +// ---------------------------------------------------------------------------- +void ViEManagerBase::WriteLockManager() +{ + _instanceRWLock.AcquireLockExclusive(); +} + +// ---------------------------------------------------------------------------- +// ReleaseLockManager +// +// Releases the lock count. +// ---------------------------------------------------------------------------- +void ViEManagerBase::ReleaseWriteLockManager() +{ + _instanceRWLock.ReleaseLockExclusive(); +} + +// ---------------------------------------------------------------------------- +// ViEManagerScopedBase +// +// ---------------------------------------------------------------------------- +ViEManagerScopedBase::ViEManagerScopedBase(const ViEManagerBase& ViEManagerBase) : + _vieManager(&ViEManagerBase), _refCount(0) +{ + _vieManager->ReadLockManager(); +} + +ViEManagerScopedBase::~ViEManagerScopedBase() +{ + assert(_refCount==0); + _vieManager->ReleaseLockManager(); +} + +// ---------------------------------------------------------------------------- +/// +// ViEManagerWriteScoped +// +// ---------------------------------------------------------------------------- +ViEManagerWriteScoped::ViEManagerWriteScoped(ViEManagerBase& vieManager) : + _vieManager(&vieManager) +{ + _vieManager->WriteLockManager(); +} + +ViEManagerWriteScoped::~ViEManagerWriteScoped() +{ + _vieManager->ReleaseWriteLockManager(); +} + +// ---------------------------------------------------------------------------- +// ViEManagedItemScopedBase +// +// ---------------------------------------------------------------------------- +ViEManagedItemScopedBase::ViEManagedItemScopedBase( + ViEManagerScopedBase& vieScopedManager) : + _vieScopedManager(vieScopedManager) +{ + _vieScopedManager._refCount++; +} + +ViEManagedItemScopedBase::~ViEManagedItemScopedBase() +{ + _vieScopedManager._refCount--; +} +} // namespace webrtc diff --git a/video_engine/main/source/vie_manager_base.h b/video_engine/main/source/vie_manager_base.h new file mode 100644 index 0000000000..c70f99861b --- /dev/null +++ b/video_engine/main/source/vie_manager_base.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_MANAGER_BASE_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_MANAGER_BASE_H_ + +namespace webrtc { +class RWLockWrapper; + +class ViEManagerBase +{ + friend class ViEManagerScopedBase; + friend class ViEManagedItemScopedBase; + friend class ViEManagerWriteScoped; +public: + ViEManagerBase(void); + ~ViEManagerBase(void); +private: + void WriteLockManager(); + void ReleaseWriteLockManager(); + void ReadLockManager() const; + void ReleaseLockManager() const; + RWLockWrapper& _instanceRWLock; +}; + +class ViEManagerWriteScoped +{ +public: + ViEManagerWriteScoped(ViEManagerBase& vieManager); + ~ViEManagerWriteScoped(); +private: + ViEManagerBase* _vieManager; +}; + +class ViEManagerScopedBase +{ + friend class ViEManagedItemScopedBase; +public: + ViEManagerScopedBase(const ViEManagerBase& vieManager); + ~ViEManagerScopedBase(); +protected: + const ViEManagerBase* _vieManager; +private: + int _refCount; +}; + +class ViEManagedItemScopedBase +{ +public: + ViEManagedItemScopedBase(ViEManagerScopedBase& vieScopedManager); + ~ViEManagedItemScopedBase(); +protected: + ViEManagerScopedBase& _vieScopedManager; +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_MANAGER_BASE_H_ diff --git a/video_engine/main/source/vie_network_impl.cc b/video_engine/main/source/vie_network_impl.cc new file mode 100644 index 0000000000..97be08a6bb --- /dev/null +++ b/video_engine/main/source/vie_network_impl.cc @@ -0,0 +1,1126 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_network_impl.cpp + */ + +#include "vie_network_impl.h" + +// Defines +#include "engine_configurations.h" +#include "vie_defines.h" + +// WebRTC include +#include "vie_errors.h" +#include "trace.h" +#include "vie_impl.h" +#include "vie_channel.h" +#include "vie_channel_manager.h" +#include "vie_encoder.h" + +// System include +#include + +// Conditional system include +#if (defined(_WIN32) || defined(_WIN64)) +#include +#endif + +namespace webrtc +{ + +// ---------------------------------------------------------------------------- +// GetInterface +// ---------------------------------------------------------------------------- + +ViENetwork* ViENetwork::GetInterface(VideoEngine* videoEngine) +{ +#ifdef WEBRTC_VIDEO_ENGINE_NETWORK_API + if (videoEngine == NULL) + { + return NULL; + } + VideoEngineImpl* vieImpl = reinterpret_cast (videoEngine); + ViENetworkImpl* vieNetworkImpl = vieImpl; + (*vieNetworkImpl)++; // Increase ref count + + return vieNetworkImpl; +#else + return NULL; +#endif +} + +// ---------------------------------------------------------------------------- +// Release +// +// Releases the interface, i.e. reduces the reference counter. The number of +// remaining references is returned, -1 if released too many times. +// ---------------------------------------------------------------------------- + +int ViENetworkImpl::Release() +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, _instanceId, + "ViENetwork::Release()"); + (*this)--; // Decrease ref count + + WebRtc_Word32 refCount = GetCount(); + if (refCount < 0) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, _instanceId, + "ViENetwork release too many times"); + SetLastError(kViEAPIDoesNotExist); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, _instanceId, + "ViENetwork reference count: %d", refCount); + return refCount; +} + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + +ViENetworkImpl::ViENetworkImpl() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, _instanceId, + "ViENetworkImpl::ViENetworkImpl() Ctor"); +} + +// ---------------------------------------------------------------------------- +// Destructor +// ---------------------------------------------------------------------------- + +ViENetworkImpl::~ViENetworkImpl() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, _instanceId, + "ViENetworkImpl::~ViENetworkImpl() Dtor"); +} + +// ============================================================================ +// Receive functions +// ============================================================================ + +// ---------------------------------------------------------------------------- +// SetLocalReceiver +// +// Initializes the receive socket +// ---------------------------------------------------------------------------- +int ViENetworkImpl::SetLocalReceiver(const int videoChannel, + const unsigned short rtpPort, + const unsigned short rtcpPort, + const char* ipAddress) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(channel: %d, rtpPort: %u, rtcpPort: %u, ipAddress: %s)", + __FUNCTION__, videoChannel, rtpPort, rtcpPort, ipAddress); + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId,videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + + if (ptrViEChannel->Receiving()) + { + SetLastError(kViENetworkAlreadyReceiving); + return -1; + } + if (ptrViEChannel->SetLocalReceiver(rtpPort, rtcpPort, ipAddress) != 0) + { + SetLastError(kViENetworkUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// GetLocalReceiver +// +// Gets settings for an initialized receive socket +// ---------------------------------------------------------------------------- +int ViENetworkImpl::GetLocalReceiver(const int videoChannel, + unsigned short& rtpPort, + unsigned short& rtcpPort, char* ipAddress) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + if (ptrViEChannel->GetLocalReceiver(rtpPort, rtcpPort, ipAddress) != 0) + { + SetLastError(kViENetworkLocalReceiverNotSet); + return -1; + } + return 0; +} + +// ============================================================================ +// Send functions +// ============================================================================ + +// ---------------------------------------------------------------------------- +// SetSendDestination +// +// Initializes the send socket +// ---------------------------------------------------------------------------- +int ViENetworkImpl::SetSendDestination(const int videoChannel, + const char* ipAddress, + const unsigned short rtpPort, + const unsigned short rtcpPort, + const unsigned short sourceRtpPort, + const unsigned short sourceRtcpPort) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(channel: %d, ipAddress: x, rtpPort: %u, rtcpPort: %u, " + "sourceRtpPort: %u, sourceRtcpPort: %u)", + __FUNCTION__, rtpPort, rtcpPort, sourceRtpPort, + sourceRtcpPort); + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s Channel doesn't exist", __FUNCTION__); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + if (ptrViEChannel->Sending()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s Channel already sending.", __FUNCTION__); + SetLastError(kViENetworkAlreadySending); + return -1; + } + if (ptrViEChannel->SetSendDestination(ipAddress, rtpPort, rtcpPort, + sourceRtpPort, sourceRtcpPort) != 0) + { + SetLastError(kViENetworkUnknownError); + return -1; + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// GetSendDestination +// +// Gets settings for an initialized send socket +// ---------------------------------------------------------------------------- +int ViENetworkImpl::GetSendDestination(const int videoChannel, char* ipAddress, + unsigned short& rtpPort, + unsigned short& rtcpPort, + unsigned short& sourceRtpPort, + unsigned short& sourceRtcpPort) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + if (ptrViEChannel->GetSendDestination(ipAddress, rtpPort, rtcpPort, + sourceRtpPort, sourceRtcpPort) != 0) + { + SetLastError(kViENetworkDestinationNotSet); + return -1; + } + return 0; +} + +// ============================================================================ +// External transport +// ============================================================================ + +// ---------------------------------------------------------------------------- +// RegisterSendTransport +// +// Registers the customer implemented send transport +// ---------------------------------------------------------------------------- +int ViENetworkImpl::RegisterSendTransport(const int videoChannel, + Transport& transport) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s Channel doesn't exist", __FUNCTION__); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + if (ptrViEChannel->Sending()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s Channel already sending.", __FUNCTION__); + SetLastError(kViENetworkAlreadySending); + return -1; + } + if (ptrViEChannel->RegisterSendTransport(transport) != 0) + { + SetLastError(kViENetworkUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// DeregisterSendTransport +// +// Deregisters the send transport implementation from +// RegisterSendTransport +// ---------------------------------------------------------------------------- +int ViENetworkImpl::DeregisterSendTransport(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s Channel doesn't exist", __FUNCTION__); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + if (ptrViEChannel->Sending()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s Channel already sending", __FUNCTION__); + SetLastError(kViENetworkAlreadySending); + return -1; + } + if (ptrViEChannel->DeregisterSendTransport() != 0) + { + SetLastError(kViENetworkUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// ReceivedRTPPacket +// +// Function for inserting a RTP packet received by a customer transport +// ---------------------------------------------------------------------------- +int ViENetworkImpl::ReceivedRTPPacket(const int videoChannel, const void* data, + const int length) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(channel: %d, data: -, length: %d)", __FUNCTION__, + videoChannel, length); + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + return ptrViEChannel->ReceivedRTPPacket(data, length); +} + +// ---------------------------------------------------------------------------- +// ReceivedRTCPPacket +// +// Function for inserting a RTCP packet received by a customer transport +// ---------------------------------------------------------------------------- +int ViENetworkImpl::ReceivedRTCPPacket(const int videoChannel, + const void* data, const int length) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(channel: %d, data: -, length: %d)", __FUNCTION__, + videoChannel, length); + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + return ptrViEChannel->ReceivedRTCPPacket(data, length); +} + +// ============================================================================ +// Get info +// ============================================================================ + +// ---------------------------------------------------------------------------- +// GetSourceInfo +// +// Retreives informatino about the remote side sockets +// ---------------------------------------------------------------------------- +int ViENetworkImpl::GetSourceInfo(const int videoChannel, + unsigned short& rtpPort, + unsigned short& rtcpPort, char* ipAddress, + unsigned int ipAddressLength) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + if (ptrViEChannel->GetSourceInfo(rtpPort, rtcpPort, ipAddress, + ipAddressLength) != 0) + { + SetLastError(kViENetworkUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// GetLocalIP +// +// Gets the local ip address +// ---------------------------------------------------------------------------- +int ViENetworkImpl::GetLocalIP(char ipAddress[64], bool ipv6) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s( ipAddress, ipV6: %d)", __FUNCTION__, ipv6); + +#ifndef WEBRTC_EXTERNAL_TRANSPORT + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + + if (ipAddress == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: No argument", __FUNCTION__); + SetLastError(kViENetworkInvalidArgument); + return -1; + } + + WebRtc_UWord8 numSocketThreads = 1; + UdpTransport* ptrSocketTransport = + UdpTransport::Create( + ViEModuleId(_instanceId,-1),numSocketThreads); + + if (ptrSocketTransport == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: Could not create socket module", __FUNCTION__); + SetLastError(kViENetworkUnknownError); + return -1; + } + + WebRtc_Word8 localIpAddress[64]; + if (ipv6) + { + WebRtc_UWord8 localIP[16]; + if (ptrSocketTransport->LocalHostAddressIPV6(localIP) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: Could not get local IP", __FUNCTION__); + SetLastError(kViENetworkUnknownError); + return -1; + } + // Convert 128-bit address to character string (a:b:c:d:e:f:g:h) + sprintf(localIpAddress, + "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x", + localIP[0], localIP[1], localIP[2], localIP[3], localIP[4], + localIP[5], localIP[6], localIP[7], localIP[8], localIP[9], + localIP[10], localIP[11], localIP[12], localIP[13], + localIP[14], localIP[15]); + } + else + { + WebRtc_UWord32 localIP = 0; + if (ptrSocketTransport->LocalHostAddress(localIP) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: Could not get local IP", __FUNCTION__); + SetLastError(kViENetworkUnknownError); + return -1; + } + // Convert 32-bit address to character string (x.y.z.w) + sprintf(localIpAddress, "%d.%d.%d.%d", (int) ((localIP >> 24) & 0x0ff), + (int) ((localIP >> 16) & 0x0ff), + (int) ((localIP >> 8) & 0x0ff), (int) (localIP & 0x0ff)); + } + strcpy(ipAddress, localIpAddress); + UdpTransport::Destroy( + ptrSocketTransport); + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: local ip = %s", __FUNCTION__, localIpAddress); + + return 0; +#else + WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: not available for external transport", __FUNCTION__); + + return -1; +#endif +} + +// ============================================================================ +// IPv6 +// ============================================================================ + +// ---------------------------------------------------------------------------- +// EnableIPv6 +// +// Enables IPv6 +// ---------------------------------------------------------------------------- +int ViENetworkImpl::EnableIPv6(int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + if (ptrViEChannel->EnableIPv6() != 0) + { + SetLastError(kViENetworkUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// IsIPv6Enabled +// +// Returns true if IPv6 is enabled, false otherwise. +// ---------------------------------------------------------------------------- +bool ViENetworkImpl::IsIPv6Enabled(int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return false; + } + return ptrViEChannel->IsIPv6Enabled(); +} + +// ============================================================================ +// Source IP address and port filter +// ============================================================================ + +// ---------------------------------------------------------------------------- +// SetSourceFilter +// +// Sets filter source port and IP address. Packets from all other sources +// will be discarded. +// ---------------------------------------------------------------------------- +int ViENetworkImpl::SetSourceFilter(const int videoChannel, + const unsigned short rtpPort, + const unsigned short rtcpPort, + const char* ipAddress) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(channel: %d, rtpPort: %u, rtcpPort: %u, ipAddress: %s)", + __FUNCTION__, videoChannel, rtpPort, rtcpPort, ipAddress); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + if (ptrViEChannel->SetSourceFilter(rtpPort, rtcpPort, ipAddress) != 0) + { + SetLastError(kViENetworkUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// GetSourceFilter +// +// Gets vaules set by SetSourceFilter +// ---------------------------------------------------------------------------- +int ViENetworkImpl::GetSourceFilter(const int videoChannel, + unsigned short& rtpPort, + unsigned short& rtcpPort, char* ipAddress) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + if (ptrViEChannel->GetSourceFilter(rtpPort, rtcpPort, ipAddress) != 0) + { + SetLastError(kViENetworkUnknownError); + return -1; + } + return 0; +} + +// ============================================================================ +// ToS +// ============================================================================ + +// ---------------------------------------------------------------------------- +// SetToS +// +// Sets values for ToS +// ---------------------------------------------------------------------------- +int ViENetworkImpl::SetSendToS(const int videoChannel, const int DSCP, + const bool useSetSockOpt = false) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(channel: %d, DSCP: %d, useSetSockOpt: %d)", __FUNCTION__, + videoChannel, DSCP, useSetSockOpt); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + +#if defined(WEBRTC_LINUX) || defined(WEBRTC_MAC) + WEBRTC_TRACE(kTraceInfo, kTraceVideo, ViEId(_instanceId, videoChannel), + " force useSetSockopt=true since there is no alternative" + " implementation"); + if (ptrViEChannel->SetToS(DSCP, true) != 0) +#else + if (ptrViEChannel->SetToS(DSCP, useSetSockOpt) != 0) +#endif + { + SetLastError(kViENetworkUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// GetToS +// +// Gets values set by SetToS +// ---------------------------------------------------------------------------- +int ViENetworkImpl::GetSendToS(const int videoChannel, int& DSCP, + bool& useSetSockOpt) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + if (ptrViEChannel->GetToS((WebRtc_Word32&) DSCP, useSetSockOpt) != 0) + { + SetLastError(kViENetworkUnknownError); + return -1; + } + return 0; +} + +// ============================================================================ +// GQoS +// ============================================================================ + +// ---------------------------------------------------------------------------- +// SetSendGQoS +// +// Sets settings for GQoS. Must be called after SetSendCodec to get correct +// bitrate setting. +// ---------------------------------------------------------------------------- +int ViENetworkImpl::SetSendGQoS(const int videoChannel, const bool enable, + const int serviceType, const int overrideDSCP) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(channel: %d, enable: %d, serviceType: %d, " + "overrideDSCP: %d)", + __FUNCTION__, videoChannel, enable, serviceType, overrideDSCP); + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + +#if (defined(_WIN32) || defined(_WIN64)) + // Sanity check. We might crash if testing and relying on an OS socket error + if (enable + && (serviceType != SERVICETYPE_BESTEFFORT) + && (serviceType != SERVICETYPE_CONTROLLEDLOAD) + && (serviceType != SERVICETYPE_GUARANTEED) && + (serviceType != SERVICETYPE_QUALITATIVE)) + { + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: service type %d not supported", __FUNCTION__, + videoChannel, serviceType); + SetLastError(kViENetworkServiceTypeNotSupported); + return -1; + } + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + ViEEncoder* ptrVieEncoder = cs.Encoder(videoChannel); + if (ptrVieEncoder == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + VideoCodec videoCodec; + if (ptrVieEncoder->GetEncoder(videoCodec) != 0) + { + // Could not get + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Could not get max bitrate for the channel", + __FUNCTION__); + SetLastError(kViENetworkSendCodecNotSet); + return -1; + } + if(ptrViEChannel->SetSendGQoS(enable, serviceType, videoCodec.maxBitrate, + overrideDSCP)!=0) + { + SetLastError(kViENetworkUnknownError); + return -1; + } + return 0; + +#else + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s: Not supported", + __FUNCTION__); + SetLastError(kViENetworkNotSupported); + return -1; +#endif +} + +// ---------------------------------------------------------------------------- +// GetSendGQoS +// +// Gets the settigns set by SetSendGQoS +// ---------------------------------------------------------------------------- +int ViENetworkImpl::GetSendGQoS(const int videoChannel, bool& enabled, + int& serviceType, int& overrideDSCP) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + if (ptrViEChannel->GetSendGQoS(enabled, (WebRtc_Word32&) serviceType, + (WebRtc_Word32&) overrideDSCP) != 0) + { + SetLastError(kViENetworkUnknownError); + return -1; + } + return 0; + +} + +// ============================================================================ +// Network settings +// ============================================================================ + +// ---------------------------------------------------------------------------- +// SetMTU +// ---------------------------------------------------------------------------- +int ViENetworkImpl::SetMTU(int videoChannel, unsigned int mtu) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d, mtu: %u)", + __FUNCTION__, videoChannel, mtu); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + if (ptrViEChannel->SetMTU(mtu) != 0) + { + SetLastError(kViENetworkUnknownError); + return -1; + } + return 0; + +} + +// ============================================================================ +// Packet timout notification +// ============================================================================ + +// ---------------------------------------------------------------------------- +// SetPacketTimeoutNotification +// +// Sets the time for packet timout notifications +// ---------------------------------------------------------------------------- +int ViENetworkImpl::SetPacketTimeoutNotification(const int videoChannel, + bool enable, + int timeoutSeconds) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(channel: %d, enable: %d, timeoutSeconds: %u)", + __FUNCTION__, videoChannel, enable, timeoutSeconds); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + if (ptrViEChannel->SetPacketTimeoutNotification(enable, timeoutSeconds) + != 0) + { + SetLastError(kViENetworkUnknownError); + return -1; + } + return 0; +} + +// ============================================================================ +// Periodic dead-or-alive reports +// ============================================================================ + +// ---------------------------------------------------------------------------- +// RegisterObserver +// ---------------------------------------------------------------------------- +int ViENetworkImpl::RegisterObserver(const int videoChannel, + ViENetworkObserver& observer) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + if (ptrViEChannel->RegisterNetworkObserver(&observer) != 0) + { + SetLastError(kViENetworkObserverAlreadyRegistered); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// DeregisterObserver +// ---------------------------------------------------------------------------- +int ViENetworkImpl::DeregisterObserver(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + if (!ptrViEChannel->NetworkObserverRegistered()) + { + SetLastError(kViENetworkObserverNotRegistered); + return -1; + } + return ptrViEChannel->RegisterNetworkObserver(NULL); +} + +// ---------------------------------------------------------------------------- +// SetPeriodicDeadOrAliveStatus +// +// Enables/disables the dead-or-alive callback +// ---------------------------------------------------------------------------- +int ViENetworkImpl::SetPeriodicDeadOrAliveStatus(const int videoChannel, + bool enable, + unsigned int sampleTimeSeconds) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(channel: %d, enable: %d, sampleTimeSeconds: %ul)", + __FUNCTION__, videoChannel, enable, sampleTimeSeconds); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + if (!ptrViEChannel->NetworkObserverRegistered()) + { + SetLastError(kViENetworkObserverNotRegistered); + return -1; + } + + if (ptrViEChannel->SetPeriodicDeadOrAliveStatus(enable, sampleTimeSeconds) + != 0) + { + SetLastError(kViENetworkUnknownError); + return -1; + } + return 0; +} + +// ============================================================================ +// Send packet using the User Datagram Protocol (UDP) +// ============================================================================ + +// ---------------------------------------------------------------------------- +// SendUDPPacket +// +// Send an extra UDP packet +// ---------------------------------------------------------------------------- +int ViENetworkImpl::SendUDPPacket(const int videoChannel, const void* data, + const unsigned int length, + int& transmittedBytes, + bool useRtcpSocket = false) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(channel: %d, data: -, length: %d, transmitterBytes: -, " + "useRtcpSocket: %d)", __FUNCTION__, videoChannel, length, + useRtcpSocket); + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "Channel doesn't exist"); + SetLastError(kViENetworkInvalidChannelId); + return -1; + } + if (ptrViEChannel->SendUDPPacket((const WebRtc_Word8*) data, length, + (WebRtc_Word32&) transmittedBytes, + useRtcpSocket) < 0) + { + SetLastError(kViENetworkUnknownError); + return -1; + } + return 0; +} +} // namespace webrtc diff --git a/video_engine/main/source/vie_network_impl.h b/video_engine/main/source/vie_network_impl.h new file mode 100644 index 0000000000..41c1634ad8 --- /dev/null +++ b/video_engine/main/source/vie_network_impl.h @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_network_impl.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_NETWORK_IMPL_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_NETWORK_IMPL_H_ + +#include "typedefs.h" +#include "vie_defines.h" +#include "vie_network.h" +#include "vie_ref_count.h" +#include "vie_shared_data.h" + +namespace webrtc +{ + +// ---------------------------------------------------------------------------- +// ViENetworkImpl +// ---------------------------------------------------------------------------- + +class ViENetworkImpl : public virtual ViESharedData, + public ViENetwork, + public ViERefCount +{ +public: + virtual int Release(); + + // Receive functions + virtual int SetLocalReceiver(const int videoChannel, + const unsigned short rtpPort, + const unsigned short rtcpPort, + const char* ipAddress); + + virtual int GetLocalReceiver(const int videoChannel, + unsigned short& rtpPort, + unsigned short& rtcpPort, char* ipAddress); + + // Send functions + virtual int SetSendDestination(const int videoChannel, + const char* ipAddress, + const unsigned short rtpPort, + const unsigned short rtcpPort, + const unsigned short sourceRtpPort, + const unsigned short sourceRtcpPort); + + virtual int GetSendDestination(const int videoChannel, char* ipAddress, + unsigned short& rtpPort, + unsigned short& rtcpPort, + unsigned short& sourceRtpPort, + unsigned short& sourceRtcpPort); + + // External transport + virtual int RegisterSendTransport(const int videoChannel, + Transport& transport); + + virtual int DeregisterSendTransport(const int videoChannel); + + virtual int ReceivedRTPPacket(const int videoChannel, const void* data, + const int length); + + virtual int ReceivedRTCPPacket(const int videoChannel, const void* data, + const int length); + + // Get info + virtual int GetSourceInfo(const int videoChannel, unsigned short& rtpPort, + unsigned short& rtcpPort, char* ipAddress, + unsigned int ipAddressLength); + + virtual int GetLocalIP(char ipAddress[64], bool ipv6); + + // IPv6 + virtual int EnableIPv6(int videoChannel); + + virtual bool IsIPv6Enabled(int videoChannel); + + // Source IP address and port filter + virtual int SetSourceFilter(const int videoChannel, + const unsigned short rtpPort, + const unsigned short rtcpPort, + const char* ipAddress); + + virtual int GetSourceFilter(const int videoChannel, + unsigned short& rtpPort, + unsigned short& rtcpPort, char* ipAddress); + + // ToS + virtual int SetSendToS(const int videoChannel, const int DSCP, + const bool useSetSockOpt); + + virtual int GetSendToS(const int videoChannel, int& DSCP, + bool& useSetSockOpt); + + // GQoS + virtual int SetSendGQoS(const int videoChannel, const bool enable, + const int serviceType, const int overrideDSCP); + + virtual int GetSendGQoS(const int videoChannel, bool& enabled, + int& serviceType, int& overrideDSCP); + + // Network settings + virtual int SetMTU(int videoChannel, unsigned int mtu); + + // Packet timout notification + virtual int SetPacketTimeoutNotification(const int videoChannel, + bool enable, int timeoutSeconds); + + // Periodic dead-or-alive reports + virtual int RegisterObserver(const int videoChannel, + ViENetworkObserver& observer); + + virtual int DeregisterObserver(const int videoChannel); + + virtual int + SetPeriodicDeadOrAliveStatus(const int videoChannel, const bool enable, + const unsigned int sampleTimeSeconds); + + // Send extra packet using the User Datagram Protocol (UDP) + virtual int SendUDPPacket(const int videoChannel, const void* data, + const unsigned int length, int& transmittedBytes, + bool useRtcpSocket); + +protected: + ViENetworkImpl(); + virtual ~ViENetworkImpl(); +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_NETWORK_IMPL_H_ diff --git a/video_engine/main/source/vie_performance_monitor.cc b/video_engine/main/source/vie_performance_monitor.cc new file mode 100644 index 0000000000..eb1483e03f --- /dev/null +++ b/video_engine/main/source/vie_performance_monitor.cc @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_performance_monitor.cc + */ + +#include "vie_performance_monitor.h" + +#include "cpu_wrapper.h" +#include "critical_section_wrapper.h" +#include "event_wrapper.h" +#include "thread_wrapper.h" +#include "tick_util.h" +#include "trace.h" +#include "vie_base.h" +#include "vie_defines.h" + +namespace webrtc { + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + +ViEPerformanceMonitor::ViEPerformanceMonitor(int engineId) + : _engineId(engineId), + _pointerCritsect(*CriticalSectionWrapper::CreateCriticalSection()), + _ptrViEMonitorThread(NULL), + _monitorkEvent(*EventWrapper::Create()), + _averageApplicationCPU(kViECpuStartValue), + _averageSystemCPU(kViECpuStartValue), _cpu(NULL), _vieBaseObserver(NULL) +{ + _cpu = CpuWrapper::CreateCpu(); + if (_cpu) + { + _cpu->CpuUsage(); // to initialize + } else + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s: Could not create CpuWrapper", __FUNCTION__); + } +} + +ViEPerformanceMonitor::~ViEPerformanceMonitor() +{ + Terminate(); + delete &_pointerCritsect; + delete &_monitorkEvent; + if (_cpu) + { + delete _cpu; + _cpu = NULL; + } +} + +int ViEPerformanceMonitor::Init() +{ + if (_cpu == NULL) + { + // Performance monitoring not supported + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), + "%s: Not supported", __FUNCTION__); + return 0; + } + + CriticalSectionScoped cs(_pointerCritsect); + if (_ptrViEMonitorThread == NULL) + { + _monitorkEvent.StartTimer(true, kViEMonitorPeriodMs); + _ptrViEMonitorThread + = ThreadWrapper::CreateThread(ViEMonitorThreadFunction, this, + kNormalPriority, + "ViEPerformanceMonitor"); + unsigned tId = 0; + if (_ptrViEMonitorThread->Start(tId)) + { + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), + "%s: Performance monitor thread started %u", + __FUNCTION__, tId); + } else + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s: Could not start performance monitor", __FUNCTION__); + _monitorkEvent.StopTimer(); + return -1; + } + } + return 0; +} + +int ViEPerformanceMonitor::Terminate() +{ + { + _pointerCritsect.Enter(); + _vieBaseObserver = NULL; + _pointerCritsect.Leave(); + + _monitorkEvent.StopTimer(); + if (_ptrViEMonitorThread) + { + ThreadWrapper* tmpThread = _ptrViEMonitorThread; + _ptrViEMonitorThread = NULL; + _monitorkEvent.Set(); + if (tmpThread->Stop()) + { + delete tmpThread; + tmpThread = NULL; + } + } + } + return 0; +} + +int ViEPerformanceMonitor::RegisterViEBaseObserver( + ViEBaseObserver* vieBaseObserver) +{ + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId), "%s", + __FUNCTION__); + CriticalSectionScoped cs(_pointerCritsect); + if (vieBaseObserver) + { + if (_vieBaseObserver) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "%s: Observer already started", __FUNCTION__); + return -1; + } + _vieBaseObserver = vieBaseObserver; + } else + { + _vieBaseObserver = NULL; + } + return 0; +} +bool ViEPerformanceMonitor::ViEBaseObserverRegistered() +{ + CriticalSectionScoped cs(_pointerCritsect); + return _vieBaseObserver != NULL; +} + +int ViEPerformanceMonitor::GetAverageApplicationCPU(int& applicationCPU) +{ + // Not supported + return -1; +} + +int ViEPerformanceMonitor::GetAverageSystemCPU(int& systemCPU) +{ + if (_cpu) + { + return _cpu->CpuUsage(); + } + return -1; +} + +bool ViEPerformanceMonitor::ViEMonitorThreadFunction(void* obj) +{ + return static_cast (obj)->ViEMonitorProcess(); +} + +bool ViEPerformanceMonitor::ViEMonitorProcess() +{ + // Periodically triggered with time KViEMonitorPeriodMs + _monitorkEvent.Wait(kViEMonitorPeriodMs); + { + if (_ptrViEMonitorThread == NULL) + { + // Thread removed, exit + return false; + } + if (_cpu) + { + int cpuLoad = _cpu->CpuUsage(); + if (cpuLoad > 75) + { + _pointerCritsect.Enter(); + if (_vieBaseObserver) + { + _vieBaseObserver->PerformanceAlarm(cpuLoad); + } + _pointerCritsect.Leave(); + } + } + } + return true; +} +} //namespace webrtc diff --git a/video_engine/main/source/vie_performance_monitor.h b/video_engine/main/source/vie_performance_monitor.h new file mode 100644 index 0000000000..6db1367ecd --- /dev/null +++ b/video_engine/main/source/vie_performance_monitor.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_performance_monitor.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_PERFORMANCE_MONITOR_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_PERFORMANCE_MONITOR_H_ + +// Defines +#include "vie_defines.h" +#include "typedefs.h" + +namespace webrtc +{ +class CriticalSectionWrapper; +class CpuWrapper; +class EventWrapper; +class ThreadWrapper; +class ViEBaseObserver; + +class ViEPerformanceMonitor +{ +public: + ViEPerformanceMonitor(int engineId); + ~ViEPerformanceMonitor(); + + int Init(); + int Terminate(); + int RegisterViEBaseObserver(ViEBaseObserver* vieBaseObserver); + bool ViEBaseObserverRegistered(); + + // ViEBase + int GetAverageApplicationCPU(int& applicationCPU); + int GetAverageSystemCPU(int& systemCPU); + +protected: + static bool ViEMonitorThreadFunction(void* obj); + bool ViEMonitorProcess(); + +private: + enum { kViEMonitorPeriodMs = 975 }; + enum { kViECpuStartValue = 75 }; + + const int _engineId; + CriticalSectionWrapper& _pointerCritsect; + ThreadWrapper* _ptrViEMonitorThread; + EventWrapper& _monitorkEvent; + int _averageApplicationCPU; + int _averageSystemCPU; + CpuWrapper* _cpu; + ViEBaseObserver* _vieBaseObserver; +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_PERFORMANCE_MONITOR_H_ diff --git a/video_engine/main/source/vie_receiver.cc b/video_engine/main/source/vie_receiver.cc new file mode 100644 index 0000000000..2ec47fac2b --- /dev/null +++ b/video_engine/main/source/vie_receiver.cc @@ -0,0 +1,536 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * ViEChannel.cpp + */ + +#include "vie_receiver.h" + +#include "critical_section_wrapper.h" +#include "rtp_rtcp.h" +#ifdef WEBRTC_SRTP +#include "SrtpModule.h" +#endif +#include "video_coding.h" +#include "rtp_dump.h" +#include "trace.h" + +namespace webrtc { + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + +ViEReceiver::ViEReceiver(int engineId, int channelId, + RtpRtcp& moduleRtpRtcp, + VideoCodingModule& moduleVcm) + : _receiveCritsect(*CriticalSectionWrapper::CreateCriticalSection()), + _engineId(engineId), _channelId(channelId), _rtpRtcp(moduleRtpRtcp), + _vcm(moduleVcm), +#ifdef WEBRTC_SRTP + _ptrSrtp(NULL), + _ptrSrtcp(NULL), + _ptrSrtpBuffer(NULL), + _ptrSrtcpBuffer(NULL), +#endif + _ptrExternalDecryption(NULL), _ptrDecryptionBuffer(NULL), + _rtpDump(NULL), _receiving(false) +{ + _rtpRtcp.RegisterIncomingVideoCallback(this); +} + +// ---------------------------------------------------------------------------- +// Destructor +// ---------------------------------------------------------------------------- + +ViEReceiver::~ViEReceiver() +{ + delete &_receiveCritsect; +#ifdef WEBRTC_SRTP + if (_ptrSrtpBuffer) + { + delete [] _ptrSrtpBuffer; + _ptrSrtpBuffer = NULL; + } + if (_ptrSrtcpBuffer) + { + delete [] _ptrSrtcpBuffer; + _ptrSrtcpBuffer = NULL; + } +#endif + if (_ptrDecryptionBuffer) + { + delete[] _ptrDecryptionBuffer; + _ptrDecryptionBuffer = NULL; + } + if (_rtpDump) + { + _rtpDump->Stop(); + RtpDump::DestroyRtpDump(_rtpDump); + _rtpDump = NULL; + } +} + +// ============================================================================ +// Decryption +// ============================================================================ + +// ---------------------------------------------------------------------------- +// RegisterExternalDecryption +// ---------------------------------------------------------------------------- + +int ViEReceiver::RegisterExternalDecryption(Encryption* decryption) +{ + CriticalSectionScoped cs(_receiveCritsect); + if (_ptrExternalDecryption) + { + return -1; + } + _ptrDecryptionBuffer = new WebRtc_UWord8[kViEMaxMtu]; + if (_ptrDecryptionBuffer == NULL) + { + return -1; + } + _ptrExternalDecryption = decryption; + return 0; +} + +// ---------------------------------------------------------------------------- +// DeregisterExternalDecryption +// ---------------------------------------------------------------------------- + +int ViEReceiver::DeregisterExternalDecryption() +{ + CriticalSectionScoped cs(_receiveCritsect); + if (_ptrExternalDecryption == NULL) + { + return -1; + } + _ptrExternalDecryption = NULL; + return 0; +} + +#ifdef WEBRTC_SRTP +// ---------------------------------------------------------------------------- +// RegisterSRTPModule +// ---------------------------------------------------------------------------- + +int ViEReceiver::RegisterSRTPModule(SrtpModule* srtpModule) +{ + CriticalSectionScoped cs(_receiveCritsect); + if (_ptrSrtp || + srtpModule == NULL) + { + return -1; + } + _ptrSrtpBuffer = new WebRtc_UWord8[kViEMaxMtu]; + if (_ptrSrtpBuffer == NULL) + { + return -1; + } + _ptrSrtp = srtpModule; + + return 0; +} + +// ---------------------------------------------------------------------------- +// DeregisterSRTPModule +// ---------------------------------------------------------------------------- + +int ViEReceiver::DeregisterSRTPModule() +{ + CriticalSectionScoped cs(_receiveCritsect); + if (_ptrSrtp == NULL) + { + return -1; + } + if (_ptrSrtpBuffer) + { + delete [] _ptrSrtpBuffer; + _ptrSrtpBuffer = NULL; + } + _ptrSrtp = NULL; + return 0; +} + +// ---------------------------------------------------------------------------- +// RegisterSRTCPModule +// ---------------------------------------------------------------------------- + +int ViEReceiver::RegisterSRTCPModule(SrtpModule* srtcpModule) +{ + CriticalSectionScoped cs(_receiveCritsect); + if (_ptrSrtcp || + srtcpModule == NULL) + { + return -1; + } + _ptrSrtcpBuffer = new WebRtc_UWord8[kViEMaxMtu]; + if (_ptrSrtcpBuffer == NULL) + { + return -1; + } + _ptrSrtcp = srtcpModule; + + return 0; +} + +// ---------------------------------------------------------------------------- +// DeregisterSRTPCModule +// ---------------------------------------------------------------------------- + +int ViEReceiver::DeregisterSRTCPModule() +{ + CriticalSectionScoped cs(_receiveCritsect); + if (_ptrSrtcp == NULL) + { + return -1; + } + if (_ptrSrtcpBuffer) + { + delete [] _ptrSrtcpBuffer; + _ptrSrtcpBuffer = NULL; + } + _ptrSrtcp = NULL; + return 0; +} + +#endif + +// ---------------------------------------------------------------------------- +// IncomingRTPPacket +// +// Receives RTP packets from SocketTransport +// ---------------------------------------------------------------------------- + +void ViEReceiver::IncomingRTPPacket(const WebRtc_Word8* incomingRtpPacket, + const WebRtc_Word32 incomingRtpPacketLength, + const WebRtc_Word8* fromIP, + const WebRtc_UWord16 fromPort) +{ + InsertRTPPacket(incomingRtpPacket, incomingRtpPacketLength); + return; +} + +// ---------------------------------------------------------------------------- +// IncomingRTCPPacket +// +// Receives RTCP packets from SocketTransport +// ---------------------------------------------------------------------------- + +void ViEReceiver::IncomingRTCPPacket(const WebRtc_Word8* incomingRtcpPacket, + const WebRtc_Word32 incomingRtcpPacketLength, + const WebRtc_Word8* fromIP, + const WebRtc_UWord16 fromPort) +{ + InsertRTCPPacket(incomingRtcpPacket, incomingRtcpPacketLength); + return; +} + +// ---------------------------------------------------------------------------- +// ReceivedRTPPacket +// +// Receives RTP packets from external transport +// ---------------------------------------------------------------------------- + +int ViEReceiver::ReceivedRTPPacket(const void* rtpPacket, int rtpPacketLength) +{ + if (!_receiving) + { + return -1; + } + return InsertRTPPacket((const WebRtc_Word8*) rtpPacket, rtpPacketLength); +} + +// ---------------------------------------------------------------------------- +// ReceivedRTCPPacket +// +// Receives RTCP packets from external transport +// ---------------------------------------------------------------------------- + +int ViEReceiver::ReceivedRTCPPacket(const void* rtcpPacket, + int rtcpPacketLength) +{ + if (!_receiving) + { + return -1; + } + return InsertRTCPPacket((const WebRtc_Word8*) rtcpPacket, rtcpPacketLength); +} + +// ---------------------------------------------------------------------------- +// OnReceivedPayloadData +// +// From RtpData, callback for data from RTP module +// ---------------------------------------------------------------------------- +WebRtc_Word32 ViEReceiver::OnReceivedPayloadData(const WebRtc_UWord8* payloadData, + const WebRtc_UWord16 payloadSize, + const WebRtcRTPHeader* rtpHeader) +{ + if (rtpHeader == NULL) + { + return 0; + } + if (rtpHeader->frameType == webrtc::kFrameEmpty) + { + // Don't care about empty rtp packets, we might + // get this e.g. when using FEC + return 0; + } + if (_vcm.IncomingPacket(payloadData, payloadSize, *rtpHeader) != 0) + { + // Check this... + return -1; + } + return 0; +} + +// ============================================================================ +// Private methods +// ============================================================================ + +// ---------------------------------------------------------------------------- +// InsertRTPPacket +// ---------------------------------------------------------------------------- + +int ViEReceiver::InsertRTPPacket(const WebRtc_Word8* rtpPacket, + int rtpPacketLength) +{ + WebRtc_UWord8* receivedPacket = (WebRtc_UWord8*) (rtpPacket); + int receivedPacketLength = rtpPacketLength; + + { + CriticalSectionScoped cs(_receiveCritsect); + + if (_ptrExternalDecryption) + { + int decryptedLength = 0; + _ptrExternalDecryption->decrypt(_channelId, receivedPacket, + _ptrDecryptionBuffer, + (int) receivedPacketLength, + (int*) &decryptedLength); + if (decryptedLength <= 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, + _channelId), + "RTP decryption failed"); + return -1; + } else if (decryptedLength > kViEMaxMtu) + { + WEBRTC_TRACE(webrtc::kTraceCritical, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + " %d bytes is allocated as RTP decrytption output => memory is now corrupted", + kViEMaxMtu); + return -1; + } + receivedPacket = _ptrDecryptionBuffer; + receivedPacketLength = decryptedLength; + } +#ifdef WEBRTC_SRTP + if (_ptrSrtp) + { + int decryptedLength = 0; + _ptrSrtp->decrypt(_channelId, receivedPacket, _ptrSrtpBuffer, receivedPacketLength, &decryptedLength); + if (decryptedLength <= 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), "RTP decryption failed"); + return -1; + } + else if (decryptedLength > kViEMaxMtu) + { + WEBRTC_TRACE(webrtc::kTraceCritical, webrtc::kTraceVideo,ViEId(_engineId, _channelId), " %d bytes is allocated as RTP decrytption output => memory is now corrupted", kViEMaxMtu); + return -1; + } + receivedPacket = _ptrSrtpBuffer; + receivedPacketLength = decryptedLength; + } +#endif + if (_rtpDump) + { + _rtpDump->DumpPacket(receivedPacket, + (WebRtc_UWord16) receivedPacketLength); + } + } + return _rtpRtcp.IncomingPacket(receivedPacket, receivedPacketLength); +} + +// ---------------------------------------------------------------------------- +// InsertRTCPPacket +// ---------------------------------------------------------------------------- + +int ViEReceiver::InsertRTCPPacket(const WebRtc_Word8* rtcpPacket, + int rtcpPacketLength) +{ + WebRtc_UWord8* receivedPacket = (WebRtc_UWord8*) rtcpPacket; + int receivedPacketLength = rtcpPacketLength; + { + CriticalSectionScoped cs(_receiveCritsect); + + if (_ptrExternalDecryption) + { + int decryptedLength = 0; + _ptrExternalDecryption->decrypt_rtcp(_channelId, receivedPacket, + _ptrDecryptionBuffer, + (int) receivedPacketLength, + (int*) &decryptedLength); + if (decryptedLength <= 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, + _channelId), + "RTP decryption failed"); + return -1; + } else if (decryptedLength > kViEMaxMtu) + { + WEBRTC_TRACE( + webrtc::kTraceCritical, + webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + " %d bytes is allocated as RTP decrytption output => memory is now corrupted", + kViEMaxMtu); + return -1; + } + receivedPacket = _ptrDecryptionBuffer; + receivedPacketLength = decryptedLength; + } +#ifdef WEBRTC_SRTP + if (_ptrSrtcp) + { + int decryptedLength = 0; + _ptrSrtcp->decrypt_rtcp(_channelId, receivedPacket, _ptrSrtcpBuffer, (int) receivedPacketLength, (int*) &decryptedLength); + if (decryptedLength <= 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), "RTP decryption failed"); + return -1; + } + else if (decryptedLength > kViEMaxMtu) + { + WEBRTC_TRACE(webrtc::kTraceCritical, webrtc::kTraceVideo, ViEId(_engineId, _channelId), " %d bytes is allocated as RTP decrytption output => memory is now corrupted", kViEMaxMtu); + return -1; + } + receivedPacket = _ptrSrtcpBuffer; + receivedPacketLength = decryptedLength; + } +#endif + if (_rtpDump) + { + _rtpDump->DumpPacket(receivedPacket, + (WebRtc_UWord16) receivedPacketLength); + } + } + return _rtpRtcp.IncomingPacket(receivedPacket, receivedPacketLength); +} + +// ---------------------------------------------------------------------------- +// StartReceive +// +// Only used for external transport +// ---------------------------------------------------------------------------- + +void ViEReceiver::StartReceive() +{ + _receiving = true; +} + +// ---------------------------------------------------------------------------- +// StopReceive +// +// Only used for external transport +// ---------------------------------------------------------------------------- + +void ViEReceiver::StopReceive() +{ + _receiving = false; +} + +// ---------------------------------------------------------------------------- +// StartRTPDump +// ---------------------------------------------------------------------------- + +int ViEReceiver::StartRTPDump(const char fileNameUTF8[1024]) +{ + CriticalSectionScoped cs(_receiveCritsect); + if (_rtpDump) + { + // Restart it if it already exists and is started + _rtpDump->Stop(); + } else + { + _rtpDump = RtpDump::CreateRtpDump(); + if (_rtpDump == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, + _channelId), + "%s: Failed to create RTP dump", __FUNCTION__); + return -1; + } + } + if (_rtpDump->Start(fileNameUTF8) != 0) + { + RtpDump::DestroyRtpDump(_rtpDump); + _rtpDump = NULL; + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Failed to start RTP dump", __FUNCTION__); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// StopRTPDump +// ---------------------------------------------------------------------------- + +int ViEReceiver::StopRTPDump() +{ + CriticalSectionScoped cs(_receiveCritsect); + if (_rtpDump) + { + if (_rtpDump->IsActive()) + { + _rtpDump->Stop(); + } else + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, + _channelId), + "%s: Dump not active", __FUNCTION__); + } + RtpDump::DestroyRtpDump(_rtpDump); + _rtpDump = NULL; + } else + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: RTP dump not started", + __FUNCTION__); + return -1; + } + return 0; +} + +// Implements RtpVideoFeedback +void ViEReceiver::OnReceivedIntraFrameRequest(const WebRtc_Word32 id, + const WebRtc_UWord8 message) +{ + // Don't do anything, action trigged on default module + return; +} + +void ViEReceiver::OnNetworkChanged(const WebRtc_Word32 id, + const WebRtc_UWord32 minBitrateBps, + const WebRtc_UWord32 maxBitrateBps, + const WebRtc_UWord8 fractionLost, + const WebRtc_UWord16 roundTripTimeMs, + const WebRtc_UWord16 bwEstimateKbitMin, + const WebRtc_UWord16 bwEstimateKbitMax) +{ + // Called for default module + return; +} +} // namespace webrtc diff --git a/video_engine/main/source/vie_receiver.h b/video_engine/main/source/vie_receiver.h new file mode 100644 index 0000000000..086b74e108 --- /dev/null +++ b/video_engine/main/source/vie_receiver.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_receiver.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_RECEIVER_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_RECEIVER_H_ + +// Defines +#include "engine_configurations.h" +#include "vie_defines.h" +#include "typedefs.h" +#include "udp_transport.h" +#include "rtp_rtcp_defines.h" + +#ifdef WEBRTC_SRTP +class SrtpModule; +#endif + +namespace webrtc +{ +class CriticalSectionWrapper; +// Forward declarations +class RtpDump; +class RtpRtcp; +class VideoCodingModule; +class Encryption; + +class ViEReceiver: public UdpTransportData, + public RtpData, + public RtpVideoFeedback +{ +public: + ViEReceiver(int engineId, int channelId, RtpRtcp& moduleRtpRtcp, + webrtc::VideoCodingModule& moduleVcm); + ~ViEReceiver(); + + int RegisterExternalDecryption(Encryption* decryption); + int DeregisterExternalDecryption(); + +#ifdef WEBRTC_SRTP + int RegisterSRTPModule(SrtpModule* srtpModule); + int DeregisterSRTPModule(); + + int RegisterSRTCPModule(SrtpModule* srtpModule); + int DeregisterSRTCPModule(); +#endif + + void StartReceive(); + void StopReceive(); + int StartRTPDump(const char fileNameUTF8[1024]); + int StopRTPDump(); + + // From SocketTransportData, receiving packets from the socket + virtual void IncomingRTPPacket(const WebRtc_Word8* incomingRtpPacket, + const WebRtc_Word32 incomingRtpPacketLength, + const WebRtc_Word8* fromIP, + const WebRtc_UWord16 fromPort); + virtual void IncomingRTCPPacket(const WebRtc_Word8* incomingRtcpPacket, + const WebRtc_Word32 incomingRtcpPacketLength, + const WebRtc_Word8* fromIP, + const WebRtc_UWord16 fromPort); + + // Receives packets from external transport + int ReceivedRTPPacket(const void* rtpPacket, int rtpPacketLength); + + int ReceivedRTCPPacket(const void* rtcpPacket, int rtcpPacketLength); + + // From RtpData, callback for data from RTP module + virtual WebRtc_Word32 + OnReceivedPayloadData(const WebRtc_UWord8* payloadData, + const WebRtc_UWord16 payloadSize, + const WebRtcRTPHeader* rtpHeader); + + // Implements RtpVideoFeedback + virtual void OnReceivedIntraFrameRequest(const WebRtc_Word32 id, + const WebRtc_UWord8 message = 0); + virtual void OnNetworkChanged(const WebRtc_Word32 id, + const WebRtc_UWord32 minBitrateBps, + const WebRtc_UWord32 maxBitrateBps, + const WebRtc_UWord8 fractionLost, + const WebRtc_UWord16 roundTripTimeMs, + const WebRtc_UWord16 bwEstimateKbitMin, + const WebRtc_UWord16 bwEstimateKbitMax); +private: + int InsertRTPPacket(const WebRtc_Word8* rtpPacket, int rtpPacketLength); + int InsertRTCPPacket(const WebRtc_Word8* rtcpPacket, int rtcpPacketLength); + // Registered members + CriticalSectionWrapper& _receiveCritsect; + int _engineId; + int _channelId; + RtpRtcp& _rtpRtcp; + VideoCodingModule& _vcm; + +#ifdef WEBRTC_SRTP + SrtpModule* _ptrSrtp; + SrtpModule* _ptrSrtcp; + WebRtc_UWord8* _ptrSrtpBuffer; + WebRtc_UWord8* _ptrSrtcpBuffer; +#endif + Encryption* _ptrExternalDecryption; + WebRtc_UWord8* _ptrDecryptionBuffer; + RtpDump* _rtpDump; + bool _receiving; // Only needed to protect external transport +}; +} // namespace webrt +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_RECEIVER_H_ diff --git a/video_engine/main/source/vie_ref_count.cc b/video_engine/main/source/vie_ref_count.cc new file mode 100644 index 0000000000..b935567668 --- /dev/null +++ b/video_engine/main/source/vie_ref_count.cc @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * ViERefCount.cpp + */ + + +#include "vie_ref_count.h" +#include "critical_section_wrapper.h" + + +ViERefCount::ViERefCount() : + _count(0), + _crit(*webrtc::CriticalSectionWrapper::CreateCriticalSection()) +{ +} + +ViERefCount::~ViERefCount() +{ + delete &_crit; +} + +ViERefCount& +ViERefCount::operator++(int) +{ + webrtc::CriticalSectionScoped lock(_crit); + _count++; + return *this; +} + +ViERefCount& +ViERefCount::operator--(int) +{ + webrtc::CriticalSectionScoped lock(_crit); + _count--; + return *this; +} + +void +ViERefCount::Reset() +{ + webrtc::CriticalSectionScoped lock(_crit); + _count = 0; +} + +int +ViERefCount::GetCount() const +{ + return _count; +} diff --git a/video_engine/main/source/vie_ref_count.h b/video_engine/main/source/vie_ref_count.h new file mode 100644 index 0000000000..ccf6279667 --- /dev/null +++ b/video_engine/main/source/vie_ref_count.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_ref_count.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_REF_COUNT_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_REF_COUNT_H_ + +namespace webrtc { +class CriticalSectionWrapper; +} + +class ViERefCount +{ +public: + ViERefCount(); + ~ViERefCount(); + + ViERefCount& operator++(int); + ViERefCount& operator--(int); + + void Reset(); + int GetCount() const; + +private: + volatile int _count; + webrtc::CriticalSectionWrapper& _crit; +}; + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_REF_COUNT_H_ diff --git a/video_engine/main/source/vie_render_impl.cc b/video_engine/main/source/vie_render_impl.cc new file mode 100644 index 0000000000..add6909c13 --- /dev/null +++ b/video_engine/main/source/vie_render_impl.cc @@ -0,0 +1,563 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_render_impl.cc + */ + +#include "vie_render_impl.h" + +// Defines +#include "engine_configurations.h" +#include "vie_defines.h" + +#include "trace.h" +#include "video_render.h" +#include "video_render_defines.h" +#include "vie_errors.h" +#include "vie_impl.h" +#include "vie_capturer.h" +#include "vie_channel.h" +#include "vie_frame_provider_base.h" +#include "vie_channel_manager.h" +#include "vie_input_manager.h" +#include "vie_render_manager.h" + +namespace webrtc +{ + +// ---------------------------------------------------------------------------- +// GetInterface +// ---------------------------------------------------------------------------- + +ViERender* ViERender::GetInterface(VideoEngine* videoEngine) +{ +#ifdef WEBRTC_VIDEO_ENGINE_RENDER_API + if (videoEngine == NULL) + { + return NULL; + } + VideoEngineImpl* vieImpl = reinterpret_cast (videoEngine); + ViERenderImpl* vieRenderImpl = vieImpl; + (*vieRenderImpl)++; // Increase ref count + + return vieRenderImpl; +#else + return NULL; +#endif +} + +// ---------------------------------------------------------------------------- +// Release +// +// Releases the interface, i.e. reduces the reference counter. The number of +// remaining references is returned, -1 if released too many times. +// ---------------------------------------------------------------------------- + +int ViERenderImpl::Release() +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, _instanceId, + "ViERender::Release()"); + (*this)--; // Decrease ref count + + WebRtc_Word32 refCount = GetCount(); + if (refCount < 0) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, _instanceId, + "ViERender release too many times"); + // SetLastError() + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, _instanceId, + "ViERender reference count: %d", refCount); + return refCount; +} + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + +ViERenderImpl::ViERenderImpl() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, _instanceId, + "ViERenderImpl::ViERenderImpl() Ctor"); +} + +// ---------------------------------------------------------------------------- +// Destructor +// ---------------------------------------------------------------------------- + +ViERenderImpl::~ViERenderImpl() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, _instanceId, + "ViERenderImpl::~ViERenderImpl() Dtor"); +} + +// ============================================================================ +// Registration of render module +// ============================================================================ + +// ---------------------------------------------------------------------------- +// RegisterVideoRenderModule +// +// Registers a video render module, must be called before +// AddRenderer is called for an input stream associated +// with the same window as the module. +// ---------------------------------------------------------------------------- + +int ViERenderImpl::RegisterVideoRenderModule( + VideoRender& renderModule) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s (&renderModule: %p)", __FUNCTION__, &renderModule); + + if (_renderManager.RegisterVideoRenderModule(renderModule) != 0) + { + // Error logging is done in RegisterVideoRenderModule + SetLastError(kViERenderUnknownError); + return -1; + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// DeRegisterVideoRenderModule +// +// De-registers a video render module, must be called after +// RemoveRenderer has been called for all input streams associated +// with the same window as the module. +// ---------------------------------------------------------------------------- + +int ViERenderImpl::DeRegisterVideoRenderModule( + VideoRender& renderModule) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s (&renderModule: %p)", __FUNCTION__, &renderModule); + if (_renderManager.DeRegisterVideoRenderModule(renderModule) != 0) + { + // Error logging is done in DeRegisterVideoRenderModule + SetLastError(kViERenderUnknownError); + return -1; + } + + return 0; +} + +// ============================================================================ +// Add renderer +// ============================================================================ + +int ViERenderImpl::AddRenderer(const int renderId, void* window, + const unsigned int zOrder, const float left, + const float top, const float right, + const float bottom) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s (renderId: %d, window: 0x%p, zOrder: %u, left: %f, " + "top: %f, right: %f, bottom: %f)", + __FUNCTION__, renderId, window, zOrder, left, top, right, + bottom); + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + + { // Check if the renderer exist already + ViERenderManagerScoped rs(_renderManager); + if (rs.Renderer(renderId) != NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - Renderer already exist %d.", __FUNCTION__, + renderId); + SetLastError(kViERenderAlreadyExists); + return -1; + } + } + + if (renderId >= kViEChannelIdBase && renderId <= kViEChannelIdMax) + { + // This is a channel + ViEChannelManagerScoped cm(_channelManager); + ViEFrameProviderBase* frameProvider = cm.Channel(renderId); + if (frameProvider == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: FrameProvider id %d doesn't exist", __FUNCTION__, + renderId); + SetLastError(kViERenderInvalidRenderId); + return -1; + } + ViERenderer* renderer = _renderManager.AddRenderStream(renderId, + window, zOrder, + left, top, + right, bottom); + if (renderer == NULL) + { + SetLastError(kViERenderUnknownError); + return -1; + } + return frameProvider->RegisterFrameCallback(renderId, renderer); + } + else // camera or file + { + ViEInputManagerScoped is(_inputManager); + ViEFrameProviderBase* frameProvider = is.FrameProvider(renderId); + if (frameProvider == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: FrameProvider id %d doesn't exist", __FUNCTION__, + renderId); + SetLastError(kViERenderInvalidRenderId); + return -1; + } + ViERenderer* renderer = _renderManager.AddRenderStream(renderId, + window, zOrder, + left, top, + right, bottom); + if (renderer == NULL) + { + SetLastError(kViERenderUnknownError); + return -1; + } + return frameProvider->RegisterFrameCallback(renderId, renderer); + } + SetLastError(kViERenderInvalidRenderId); + return -1; + +} + +int ViERenderImpl::RemoveRenderer(const int renderId) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), + "%s(renderId: %d)", __FUNCTION__, renderId); + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + + ViERenderer* renderer = NULL; + { + ViERenderManagerScoped rs(_renderManager); + renderer = rs.Renderer(renderId); + if (!renderer) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, ViEId(_instanceId), + "%s No render exist with renderId: %d", __FUNCTION__, + renderId); + SetLastError(kViERenderInvalidRenderId); + return -1; + } + } // Leave the scope lock since we don't want to lock two managers + // simultanousely + + if (renderId >= kViEChannelIdBase && renderId <= kViEChannelIdMax) + { + // This is a channel + ViEChannelManagerScoped cm(_channelManager); + ViEChannel* channel = cm.Channel(renderId); + if (!channel) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: no channel with id %d exists ", __FUNCTION__, + renderId); + SetLastError(kViERenderInvalidRenderId); + return -1; + } + channel->DeregisterFrameCallback(renderer); + } + else //Provider owned by inputmanager - ie file or capture device + { + ViEInputManagerScoped is(_inputManager); + ViEFrameProviderBase* provider = is.FrameProvider(renderId); + if (!provider) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: no provider with id %d exists ", __FUNCTION__, + renderId); + SetLastError(kViERenderInvalidRenderId); + return -1; + } + provider->DeregisterFrameCallback(renderer); + + } + if (_renderManager.RemoveRenderStream(renderId) != 0) + { + SetLastError(kViERenderUnknownError); + return -1; + } + return 0; +} + +// ============================================================================ +// Start/stop +// ============================================================================ + +// ---------------------------------------------------------------------------- +// StartRender +// +// Starts rendering the stream from the channel +// ---------------------------------------------------------------------------- + +int ViERenderImpl::StartRender(const int renderId) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, renderId), "%s(channel: %d)", __FUNCTION__, + renderId); + + ViERenderManagerScoped rs(_renderManager); + ViERenderer* ptrRender = rs.Renderer(renderId); + if (ptrRender == NULL) + { + // No renderer for this channel + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, renderId), + "%s: No renderer with render Id %d exist.", __FUNCTION__, + renderId); + SetLastError(kViERenderInvalidRenderId); + return -1; + } + + if (ptrRender->StartRender() != 0) + { + SetLastError(kViERenderUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// StopRender +// +// Stop rendering a stream +// ---------------------------------------------------------------------------- + +int ViERenderImpl::StopRender(const int renderId) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, renderId), "%s(channel: %d)", __FUNCTION__, + renderId); + + ViERenderManagerScoped rs(_renderManager); + ViERenderer* ptrRender = rs.Renderer(renderId); + if (ptrRender == NULL) + { + // No renderer for this channel + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, renderId), + "%s: No renderer with renderId %d exist.", __FUNCTION__, + renderId); + SetLastError(kViERenderInvalidRenderId); + return -1; + } + if (ptrRender->StopRender() != 0) + { + SetLastError(kViERenderUnknownError); + return -1; + } + return 0; +} + +// ============================================================================ +// Stream configurations +// ============================================================================ + +// ---------------------------------------------------------------------------- +// ConfigureRender +// +// Reconfigures an already added render stream +// ---------------------------------------------------------------------------- + +int ViERenderImpl::ConfigureRender(int renderId, const unsigned int zOrder, + const float left, const float top, + const float right, const float bottom) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId, renderId), + "%s(channel: %d)", __FUNCTION__, renderId); + + ViERenderManagerScoped rs(_renderManager); + ViERenderer* ptrRender = rs.Renderer(renderId); + if (ptrRender == NULL) + { + // No renderer for this channel + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, renderId), + "%s: No renderer with renderId %d exist.", __FUNCTION__, + renderId); + SetLastError(kViERenderInvalidRenderId); + return -1; + } + + if (ptrRender->ConfigureRenderer(zOrder, left, top, right, bottom) != 0) + { + SetLastError(kViERenderUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// MirrorRenderStream +// +// Enables mirror rendering +// ---------------------------------------------------------------------------- + +int ViERenderImpl::MirrorRenderStream(const int renderId, const bool enable, + const bool mirrorXAxis, + const bool mirrorYAxis) +{ + ViERenderManagerScoped rs(_renderManager); + ViERenderer* ptrRender = rs.Renderer(renderId); + if (ptrRender == NULL) + { + // No renderer for this channel + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, renderId), + "%s: No renderer with renderId %d exist.", __FUNCTION__, + renderId); + SetLastError(kViERenderInvalidRenderId); + return -1; + } + if (ptrRender->EnableMirroring(renderId, enable, mirrorXAxis, mirrorYAxis) + != 0) + { + SetLastError(kViERenderUnknownError); + return -1; + } + return 0; +} + +// ============================================================================ +// External render +// ============================================================================ + + +// ---------------------------------------------------------------------------- +// +// +// AddRenderer +// ---------------------------------------------------------------------------- + +int ViERenderImpl::AddRenderer(const int renderId, + webrtc::RawVideoType videoInputFormat, + ExternalRenderer* externalRenderer) +{ + // check if the client requested a format that we can convert the frames to + if (videoInputFormat != webrtc::kVideoI420 + && videoInputFormat != webrtc::kVideoYV12 + && videoInputFormat != webrtc::kVideoYUY2 + && videoInputFormat != webrtc::kVideoUYVY + && videoInputFormat != webrtc::kVideoARGB + && videoInputFormat != webrtc::kVideoRGB24 + && videoInputFormat != webrtc::kVideoRGB565 + && videoInputFormat != webrtc::kVideoARGB4444 + && videoInputFormat != webrtc::kVideoARGB1555) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, renderId), + "%s: Unsupported video frame format requested", + __FUNCTION__, renderId); + SetLastError(kViERenderInvalidFrameFormat); + return -1; + } + + if (!IsInitialized()) + { + SetLastError(kViENotInitialized); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - ViE instance %d not initialized", __FUNCTION__, + _instanceId); + return -1; + } + + { // Check if the renderer exist already + ViERenderManagerScoped rs(_renderManager); + if (rs.Renderer(renderId) != NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s - Renderer already exist %d.", __FUNCTION__, + renderId); + SetLastError(kViERenderAlreadyExists); + return -1; + } + } + + if (renderId >= kViEChannelIdBase && renderId <= kViEChannelIdMax) + { + // This is a channel + ViEChannelManagerScoped cm(_channelManager); + ViEFrameProviderBase* frameProvider = cm.Channel(renderId); + if (frameProvider == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: FrameProvider id %d doesn't exist", __FUNCTION__, + renderId); + SetLastError(kViERenderInvalidRenderId); + return -1; + } + ViERenderer* ptrRender = _renderManager.AddRenderStream(renderId, NULL, + 0, 0.0f, 0.0f, + 1.0f, 1.0f); + if (ptrRender == NULL) + { + SetLastError(kViERenderUnknownError); + return -1; + } + if (-1 == ptrRender->SetExternalRenderer(renderId, videoInputFormat, + externalRenderer)) + { + SetLastError(kViERenderUnknownError); + return -1; + } + + return frameProvider->RegisterFrameCallback(renderId, ptrRender); + } + else // camera or file + { + ViEInputManagerScoped is(_inputManager); + ViEFrameProviderBase* frameProvider = is.FrameProvider(renderId); + if (frameProvider == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId), + "%s: FrameProvider id %d doesn't exist", __FUNCTION__, + renderId); + SetLastError(kViERenderInvalidRenderId); + return -1; + } + ViERenderer* ptrRender = _renderManager.AddRenderStream(renderId, NULL, + 0, 0.0f, 0.0f, + 1.0f, 1.0f); + if (ptrRender == NULL) + { + SetLastError(kViERenderUnknownError); + return -1; + } + if (-1 == ptrRender->SetExternalRenderer(renderId, videoInputFormat, + externalRenderer)) + { + SetLastError(kViERenderUnknownError); + return -1; + } + return frameProvider->RegisterFrameCallback(renderId, ptrRender); + } + SetLastError(kViERenderInvalidRenderId); + return -1; + +} + +} // namespace webrtc diff --git a/video_engine/main/source/vie_render_impl.h b/video_engine/main/source/vie_render_impl.h new file mode 100644 index 0000000000..2d1bd3653c --- /dev/null +++ b/video_engine/main/source/vie_render_impl.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_render_impl.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_RENDER_IMPL_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_RENDER_IMPL_H_ + +#include "vie_defines.h" + +#include "typedefs.h" +#include "video_render_defines.h" +#include "vie_ref_count.h" +#include "vie_render.h" +#include "vie_shared_data.h" + +namespace webrtc +{ + +// ---------------------------------------------------------------------------- +// ViERenderImpl +// ---------------------------------------------------------------------------- + +class ViERenderImpl: public virtual ViESharedData, + public ViERender, + public ViERefCount +{ +public: + virtual int Release(); + + // Registration of render module + virtual int RegisterVideoRenderModule(VideoRender& renderModule); + + virtual int DeRegisterVideoRenderModule( + VideoRender& renderModule); + + // Add/remove renderer + virtual int AddRenderer(const int renderId, void* window, + const unsigned int zOrder, const float left, + const float top, const float right, + const float bottom); + + virtual int RemoveRenderer(const int renderId); + + // Start/stop + virtual int StartRender(const int renderId); + + virtual int StopRender(const int renderId); + + virtual int ConfigureRender(int renderId, const unsigned int zOrder, + const float left, const float top, + const float right, const float bottom); + + virtual int MirrorRenderStream(const int renderId, const bool enable, + const bool mirrorXAxis, + const bool mirrorYAxis); + + // External render + virtual int AddRenderer(const int renderId, + webrtc::RawVideoType videoInputFormat, + ExternalRenderer* renderer); + +protected: + ViERenderImpl(); + virtual ~ViERenderImpl(); +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_RENDER_IMPL_H_ diff --git a/video_engine/main/source/vie_render_manager.cc b/video_engine/main/source/vie_render_manager.cc new file mode 100644 index 0000000000..3d82c4e1e2 --- /dev/null +++ b/video_engine/main/source/vie_render_manager.cc @@ -0,0 +1,300 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * ViERenderManager.cpp + */ + +#include "vie_render_manager.h" +#include "engine_configurations.h" +#include "vie_defines.h" + +#include "critical_section_wrapper.h" +#include "video_render.h" +#include "video_render_defines.h" +#include "rw_lock_wrapper.h" +#include "trace.h" + +namespace webrtc { + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + + + +ViERenderManagerScoped::ViERenderManagerScoped(const ViERenderManager& vieRenderManager) + : + ViEManagerScopedBase(vieRenderManager) +{ + +} + + +// ---------------------------------------------------------------------------- +// Renderer() +// +// Returns a pointer to the ViERender object +// ---------------------------------------------------------------------------- + +ViERenderer* ViERenderManagerScoped::Renderer(WebRtc_Word32 renderId) const +{ + return static_cast (_vieManager)->ViERenderPtr(renderId); +} + + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + +ViERenderManager::ViERenderManager(WebRtc_Word32 engineId) : + _listCritsect(*CriticalSectionWrapper::CreateCriticalSection()), + _engineId(engineId), + _streamToViERenderer(), + _renderList(), + _useExternalRenderModule(false) +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, ViEId(engineId), + "ViERenderManager::ViERenderManager(engineId: %d) - Constructor", engineId); +} + +// ---------------------------------------------------------------------------- +// Destructor +// ---------------------------------------------------------------------------- + +ViERenderManager::~ViERenderManager() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, ViEId(_engineId), + "ViERenderManager Destructor, engineId: %d", _engineId); + + while(_streamToViERenderer.Size()!=0) + { + MapItem* item=_streamToViERenderer.First(); + assert(item); + const WebRtc_Word32 renderId=item->GetId(); + item=NULL;// Deleted be RemoveRenderStream; + RemoveRenderStream(renderId); + } + delete &_listCritsect; + +} + +// ---------------------------------------------------------------------------- +// RegisterVideoRenderModule +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViERenderManager::RegisterVideoRenderModule(VideoRender& renderModule) +{ + // See if there is already a render module registered for the window that + // the registrant render module is associated with + VideoRender* ptrCurrentModule = FindRenderModule(renderModule.Window()); + if (ptrCurrentModule) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "A module is already registered for this window (window=%p, current module=%p, registrant module=%p", + renderModule.Window(), ptrCurrentModule, &renderModule); + return -1; + } + + // Register module + _renderList.PushBack(static_cast(&renderModule)); + _useExternalRenderModule=true; + + return 0; +} + +// ---------------------------------------------------------------------------- +// DeRegisterVideoRenderModule +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViERenderManager::DeRegisterVideoRenderModule(VideoRender& renderModule) +{ + // Check if there are streams in the module + WebRtc_UWord32 nStreams = renderModule.GetNumIncomingRenderStreams(); + if (nStreams != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), + "There are still %d streams in this module, cannot de-register", nStreams); + return -1; + } + + // Erase the render module from the map + ListItem* listItem = NULL; + bool found = false; + for (listItem = _renderList.First(); listItem != NULL; listItem = _renderList.Next(listItem)) + { + if (&renderModule == static_cast(listItem->GetItem())) + { + // We've found our renderer + _renderList.Erase(listItem); + found = true; + break; + } + } + if (!found) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), "Module not registered"); + return -1; + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// AddRenderStream +// ---------------------------------------------------------------------------- + +ViERenderer* ViERenderManager::AddRenderStream(const WebRtc_Word32 renderId, + void* window, + const WebRtc_UWord32 zOrder, + const float left, + const float top, + const float right, + const float bottom) +{ + CriticalSectionScoped cs(_listCritsect); + + if (_streamToViERenderer.Find(renderId) != NULL) + { + // This stream is already added to a renderer, not allowed! + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), "Render stream already exists"); + return NULL; + } + + // Get the render module for this window + VideoRender* ptrRenderer = FindRenderModule(window); + if (ptrRenderer == NULL) + { + // No render module for this window, create a new one + #ifndef WEBRTC_VIDEO_EXTERNAL_CAPTURE_AND_RENDER + ptrRenderer = VideoRender::CreateVideoRender(ViEModuleId(_engineId, -1), window, false); + #else + ptrRenderer = VideoRender::CreateVideoRender(ViEModuleId(_engineId, -1), window, false, kRenderExternal); + #endif //WEBRTC_VIDEO_EXTERNAL_CAPTURE_AND_RENDER + if (!ptrRenderer) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId), "Could not create new render module"); + return NULL; + } + _renderList.PushBack((void*) ptrRenderer); + } + + ViERenderer* vieRenderer= ViERenderer::CreateViERenderer(renderId,_engineId,*ptrRenderer,*this,zOrder,left,top,right,bottom); + if(!vieRenderer) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId,renderId), "Could not create new render stream"); + return NULL; + } + + _streamToViERenderer.Insert(renderId, vieRenderer); + + return vieRenderer; +} + +// ---------------------------------------------------------------------------- +// RemoveRenderStream +// ---------------------------------------------------------------------------- + +WebRtc_Word32 ViERenderManager::RemoveRenderStream(const WebRtc_Word32 renderId) +{ + // We need exclusive right to the items in the rendermanager to delete a stream + ViEManagerWriteScoped(*this); + + // Protect the list/map + CriticalSectionScoped cs(_listCritsect); + + MapItem* mapItem = _streamToViERenderer.Find(renderId); + if (mapItem == NULL) + { + // No such stream + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, ViEId(_engineId), "No renderer for this stream found, channelId"); + return 0; + } + + // Get the vieRender object. + ViERenderer* ptrViERenderer = static_cast(mapItem->GetItem()); + assert(ptrViERenderer); + + // Get the render module pointer for this vieRender object + VideoRender& renderer=ptrViERenderer->RenderModule(); + + // Delete the vieRender. + // This deletes the stream in the render module. + delete ptrViERenderer; + + // Remove from the stream map + _streamToViERenderer.Erase(mapItem); + + // Check if there are other streams in the module + if (!_useExternalRenderModule && renderer.GetNumIncomingRenderStreams() == 0) + { + // Erase the render module from the map + ListItem* listItem = NULL; + for (listItem = _renderList.First(); listItem != NULL; listItem = _renderList.Next(listItem)) + { + if (&renderer == static_cast(listItem->GetItem())) + { + // We've found our renderer + _renderList.Erase(listItem); + break; + } + } + // Destroy the module + VideoRender::DestroyVideoRender(&renderer); + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// FindRenderModule +// +// Returns a pointer to the render module if it exists in the render list. +// Assumed protected +// ---------------------------------------------------------------------------- + +VideoRender* ViERenderManager::FindRenderModule(void* window) +{ + VideoRender* ptrRenderer = NULL; + ListItem* listItem = NULL; + for (listItem = _renderList.First(); listItem != NULL; listItem = _renderList.Next(listItem)) + { + ptrRenderer = static_cast(listItem->GetItem()); + if (ptrRenderer == NULL) + { + break; + } + if (ptrRenderer->Window() == window) + { + // We've found the render module + break; + } + ptrRenderer = NULL; + } + return ptrRenderer; +} + +ViERenderer* ViERenderManager::ViERenderPtr(WebRtc_Word32 renderId) const +{ + ViERenderer* ptrRenderer = NULL; + + MapItem* mapItem = _streamToViERenderer.Find(renderId); + if (mapItem == NULL) + { + // No such stream in any renderer + return NULL; + } + ptrRenderer = static_cast(mapItem->GetItem()); + + return ptrRenderer; +} + +} //namespace webrtc + diff --git a/video_engine/main/source/vie_render_manager.h b/video_engine/main/source/vie_render_manager.h new file mode 100644 index 0000000000..b8da6da0e8 --- /dev/null +++ b/video_engine/main/source/vie_render_manager.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_render_manager.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_RENDER_MANAGER_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_RENDER_MANAGER_H_ + +// Defines +#include "engine_configurations.h" +#include "vie_defines.h" + +#include "typedefs.h" +#include "list_wrapper.h" +#include "map_wrapper.h" + +#include "vie_manager_base.h" + +#include "vie_renderer.h" + +namespace webrtc { + +class CriticalSectionWrapper; +class RWLockWrapper; +class VideoRender; +class VideoRenderCallback; + +class ViERenderManager: private ViEManagerBase +{ + friend class ViERenderManagerScoped; +public: + ViERenderManager(WebRtc_Word32 engineId); + ~ViERenderManager(); + + WebRtc_Word32 RegisterVideoRenderModule(VideoRender& renderModule); + WebRtc_Word32 DeRegisterVideoRenderModule(VideoRender& renderModule); + + ViERenderer* AddRenderStream(const WebRtc_Word32 renderId, + void* window, + const WebRtc_UWord32 zOrder, + const float left, + const float top, + const float right, + const float bottom); + + WebRtc_Word32 RemoveRenderStream(WebRtc_Word32 renderId); + + VideoRender* FindRenderModule(void* window); + +private: + + // Methods used by ViERenderScoped + ViERenderer* ViERenderPtr(WebRtc_Word32 renderId) const; + + // Members + CriticalSectionWrapper& _listCritsect; + + WebRtc_Word32 _engineId; + MapWrapper _streamToViERenderer; // Protected by ViEManagerBase + ListWrapper _renderList; + bool _useExternalRenderModule; +}; + + +// ------------------------------------------------------------------ +// ViERenderManagerScoped +// ------------------------------------------------------------------ +class ViERenderManagerScoped: private ViEManagerScopedBase +{ +public: + ViERenderManagerScoped(const ViERenderManager& vieRenderManager); + ViERenderer* Renderer(WebRtc_Word32 renderId) const; +}; + +} //namespace webrtc + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_RENDER_MANAGER_H_ diff --git a/video_engine/main/source/vie_renderer.cc b/video_engine/main/source/vie_renderer.cc new file mode 100644 index 0000000000..7522d1ae0d --- /dev/null +++ b/video_engine/main/source/vie_renderer.cc @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "vie_renderer.h" +#include "video_render.h" +#include "video_render_defines.h" +#include "vie_render_manager.h" +#include "vplib.h" + +namespace webrtc { + +ViERenderer* ViERenderer::CreateViERenderer( + const WebRtc_Word32 renderId, + const WebRtc_Word32 engineId, + VideoRender& renderModule, + ViERenderManager& renderManager, + const WebRtc_UWord32 zOrder, + const float left, + const float top, + const float right, + const float bottom) +{ + ViERenderer* self=new ViERenderer(renderId,engineId,renderModule,renderManager); + if(!self || self->Init(zOrder,left,top,right,bottom)!=0) + { + delete self; + self=NULL; + } + return self; +} + +ViERenderer::~ViERenderer(void) +{ + if(_ptrRenderCallback) + { + _renderModule.DeleteIncomingRenderStream( _renderId); + } + + if(_ptrIncomingExternalCallback){ + delete _ptrIncomingExternalCallback; + } + +} + +ViERenderer::ViERenderer(const WebRtc_Word32 renderId,const WebRtc_Word32 engineId, + VideoRender& renderModule, + ViERenderManager& renderManager) +: +_renderId(renderId), +_engineId(engineId), +_renderModule(renderModule), +_renderManager(renderManager), +_ptrRenderCallback(NULL), +_ptrIncomingExternalCallback(new ViEExternalRendererImpl()) +{ + +} + +WebRtc_Word32 ViERenderer::Init(const WebRtc_UWord32 zOrder, + const float left, + const float top, + const float right, + const float bottom) +{ + _ptrRenderCallback = (VideoRenderCallback*)_renderModule.AddIncomingRenderStream( _renderId, zOrder, left, top, right, bottom); + if (_ptrRenderCallback == NULL) + { + // Logging done + return -1; + } + + return 0; +} + +WebRtc_Word32 ViERenderer::GetLastRenderedFrame(const WebRtc_Word32 renderID, webrtc::VideoFrame& videoFrame) +{ + return _renderModule.GetLastRenderedFrame(renderID, videoFrame); +} + +WebRtc_Word32 ViERenderer::StartRender() +{ + return _renderModule.StartRender(_renderId); +} +WebRtc_Word32 ViERenderer::StopRender() +{ + return _renderModule.StopRender(_renderId); +} + + // Implement ViEFrameCallback +void ViERenderer::DeliverFrame(int id, + webrtc::VideoFrame& videoFrame, + int numCSRCs, + const WebRtc_UWord32 CSRC[kRtpCsrcSize]) +{ + + + _ptrRenderCallback->RenderFrame(_renderId,videoFrame); + +} + +// Implement ViEFrameCallback +void ViERenderer::ProviderDestroyed(int id) +{ + _renderManager.RemoveRenderStream(_renderId); // Remove the render stream since the provider is destroyed. +} + +VideoRender& ViERenderer::RenderModule() +{ + return _renderModule; +} + +WebRtc_Word32 ViERenderer::ConfigureRenderer(const unsigned int zOrder, + const float left, + const float top, + const float right, + const float bottom) +{ + return _renderModule.ConfigureRenderer(_renderId, zOrder, left, top, right, bottom); +} + + + +WebRtc_Word32 ViERenderer::SetTimeoutImage(const webrtc::VideoFrame& timeoutImage,const WebRtc_Word32 timeoutValue) +{ + return _renderModule.SetTimeoutImage(_renderId,timeoutImage,timeoutValue); +} + +WebRtc_Word32 ViERenderer::SetRenderStartImage(const webrtc::VideoFrame& startImage) +{ + return _renderModule.SetStartImage(_renderId,startImage); +} + + + +WebRtc_Word32 ViERenderer::EnableMirroring(const WebRtc_Word32 renderId, const bool enable, const bool mirrorXAxis, const bool mirrorYAxis) +{ + return _renderModule.MirrorRenderStream(renderId, enable, mirrorXAxis, mirrorYAxis); +} + + +WebRtc_Word32 ViERenderer::SetExternalRenderer(const WebRtc_Word32 renderId, webrtc::RawVideoType videoInputFormat, ExternalRenderer* externalRenderer) +{ + if(NULL == _ptrIncomingExternalCallback){ + return -1; + } + + _ptrIncomingExternalCallback->SetViEExternalRenderer(externalRenderer, videoInputFormat); + return _renderModule.AddExternalRenderCallback(renderId, _ptrIncomingExternalCallback); +} + + + +ViEExternalRendererImpl::ViEExternalRendererImpl() : +_externalRenderer(NULL), +_externalRendererFormat(), +_externalRendererWidth(0), +_externalRendererHeight(0) +{ +} + +int ViEExternalRendererImpl::SetViEExternalRenderer(ExternalRenderer* externalRenderer, webrtc::RawVideoType videoInputFormat) +{ + _externalRenderer = externalRenderer; + _externalRendererFormat = videoInputFormat; + return 0; +} + +// implements VideoRenderCallback +WebRtc_Word32 ViEExternalRendererImpl::RenderFrame(const WebRtc_UWord32 streamId, + webrtc::VideoFrame& videoFrame) +{ + webrtc::VideoFrame convertedFrame; + webrtc::VideoFrame* pConvertedFrame = &convertedFrame; + + // convert to requested format + switch(_externalRendererFormat) + { + case webrtc::kVideoI420: + pConvertedFrame = &videoFrame; + break; + case webrtc::kVideoYV12: + convertedFrame.VerifyAndAllocate(webrtc::CalcBufferSize(webrtc::kYV12,videoFrame.Width(),videoFrame.Height())); + webrtc::ConvertI420ToYV12(videoFrame.Buffer(), convertedFrame.Buffer(), videoFrame.Width(), videoFrame.Height(), 0); + break; + case webrtc::kVideoYUY2: + convertedFrame.VerifyAndAllocate(webrtc::CalcBufferSize(webrtc::kYUY2,videoFrame.Width(),videoFrame.Height())); + webrtc::ConvertI420ToYUY2(videoFrame.Buffer(), convertedFrame.Buffer(), videoFrame.Width(), videoFrame.Height(), 0); + break; + case webrtc::kVideoUYVY: + convertedFrame.VerifyAndAllocate(webrtc::CalcBufferSize(webrtc::kUYVY,videoFrame.Width(),videoFrame.Height())); + webrtc::ConvertI420ToUYVY(videoFrame.Buffer(), convertedFrame.Buffer(), videoFrame.Width(), videoFrame.Height(), 0); + break; + case webrtc::kVideoIYUV: + // no conversion available + break; + case webrtc::kVideoARGB: + convertedFrame.VerifyAndAllocate(webrtc::CalcBufferSize(webrtc::kARGB,videoFrame.Width(),videoFrame.Height())); + webrtc::ConvertI420ToARGB(videoFrame.Buffer(), convertedFrame.Buffer(), videoFrame.Width(), videoFrame.Height(), 0); + break; + case webrtc::kVideoRGB24: + convertedFrame.VerifyAndAllocate(webrtc::CalcBufferSize(webrtc::kRGB24,videoFrame.Width(),videoFrame.Height())); + webrtc::ConvertI420ToRGB24(videoFrame.Buffer(), convertedFrame.Buffer(), videoFrame.Width(), videoFrame.Height()); + break; + case webrtc::kVideoRGB565: + convertedFrame.VerifyAndAllocate(webrtc::CalcBufferSize(webrtc::kRGB565,videoFrame.Width(),videoFrame.Height())); + webrtc::ConvertI420ToRGB565(videoFrame.Buffer(), convertedFrame.Buffer(), videoFrame.Width(), videoFrame.Height()); + break; + case webrtc::kVideoARGB4444: + convertedFrame.VerifyAndAllocate(webrtc::CalcBufferSize(webrtc::kARGB4444,videoFrame.Width(),videoFrame.Height())); + webrtc::ConvertI420ToARGB4444(videoFrame.Buffer(), convertedFrame.Buffer(), videoFrame.Width(), videoFrame.Height(), 0); + break; + case webrtc::kVideoARGB1555 : + convertedFrame.VerifyAndAllocate(webrtc::CalcBufferSize(webrtc::kARGB1555,videoFrame.Width(),videoFrame.Height())); + webrtc::ConvertI420ToARGB1555(videoFrame.Buffer(), convertedFrame.Buffer(), videoFrame.Width(), videoFrame.Height(), 0); + break; + default: + // the format is something funny. Should never reach here... + assert(false); + pConvertedFrame = NULL; + break; + } + + if(_externalRendererWidth!=videoFrame.Width() || _externalRendererHeight!=videoFrame.Height()) + { + _externalRendererWidth = videoFrame.Width(); + _externalRendererHeight = videoFrame.Height(); + _externalRenderer->FrameSizeChange(_externalRendererWidth, _externalRendererHeight, streamId); + } + + if(pConvertedFrame) + { + _externalRenderer->DeliverFrame(pConvertedFrame->Buffer(), pConvertedFrame->Length()); + } + return 0; +} + +} //namespace webrtc + + + + diff --git a/video_engine/main/source/vie_renderer.h b/video_engine/main/source/vie_renderer.h new file mode 100644 index 0000000000..8434aad22d --- /dev/null +++ b/video_engine/main/source/vie_renderer.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_renderer.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_RENDERER_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_RENDERER_H_ + + +#include "vie_frame_provider_base.h" +#include "map_wrapper.h" +#include "vie_render.h" +#include "video_render_defines.h" + +namespace webrtc { + +class VideoRender; +class VideoRenderCallback; +class ViERenderManager; + + +class ViEExternalRendererImpl : public VideoRenderCallback +{ +public: + ViEExternalRendererImpl(); + int SetViEExternalRenderer(ExternalRenderer* externalRenderer, webrtc::RawVideoType videoInputFormat); + + // implements VideoRenderCallback + virtual WebRtc_Word32 RenderFrame(const WebRtc_UWord32 streamId, + webrtc::VideoFrame& videoFrame); + + virtual ~ViEExternalRendererImpl(){}; + +private: + ExternalRenderer* _externalRenderer; + webrtc::RawVideoType _externalRendererFormat; + WebRtc_UWord32 _externalRendererWidth; + WebRtc_UWord32 _externalRendererHeight; +}; + + +class ViERenderer: public ViEFrameCallback +{ +public: + static ViERenderer* CreateViERenderer(const WebRtc_Word32 renderId, + const WebRtc_Word32 engineId, + VideoRender& renderModule, + ViERenderManager& renderManager, + const WebRtc_UWord32 zOrder, + const float left, + const float top, + const float right, + const float bottom); + + ~ViERenderer(void); + + WebRtc_Word32 StartRender(); + WebRtc_Word32 StopRender(); + + WebRtc_Word32 GetLastRenderedFrame(const WebRtc_Word32 renderID, webrtc::VideoFrame& videoFrame); + + WebRtc_Word32 ConfigureRenderer(const unsigned int zOrder, + const float left, + const float top, + const float right, + const float bottom); + + + VideoRender& RenderModule(); + + WebRtc_Word32 EnableMirroring(const WebRtc_Word32 renderId, const bool enable, const bool mirrorXAxis, const bool mirrorYAxis); + + WebRtc_Word32 SetTimeoutImage(const webrtc::VideoFrame& timeoutImage,const WebRtc_Word32 timeoutValue); + WebRtc_Word32 SetRenderStartImage(const webrtc::VideoFrame& startImage); + WebRtc_Word32 SetExternalRenderer(const WebRtc_Word32 renderId, webrtc::RawVideoType videoInputFormat, ExternalRenderer* externalRenderer); + +private: + WebRtc_Word32 Init(const WebRtc_UWord32 zOrder, + const float left, + const float top, + const float right, + const float bottom); + + ViERenderer(const WebRtc_Word32 renderId,const WebRtc_Word32 engineId, + VideoRender& renderModule, + ViERenderManager& renderManager); + + + // Implement ViEFrameCallback + + virtual void DeliverFrame(int id, VideoFrame& videoFrame, int numCSRCs = 0, + const WebRtc_UWord32 CSRC[kRtpCsrcSize] = NULL); + virtual void DelayChanged(int id, int frameDelay){return;} + virtual int GetPreferedFrameSettings(int &width, int &height, + int &frameRate){return -1;} + + virtual void ProviderDestroyed(int id); + + + WebRtc_UWord32 _renderId; + WebRtc_Word32 _engineId; + VideoRender& _renderModule; + ViERenderManager& _renderManager; + VideoRenderCallback* _ptrRenderCallback; + ViEExternalRendererImpl* _ptrIncomingExternalCallback; + +}; + +} //namespace webrtc + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_RENDERER_H_ diff --git a/video_engine/main/source/vie_rtp_rtcp_impl.cc b/video_engine/main/source/vie_rtp_rtcp_impl.cc new file mode 100644 index 0000000000..c4c0f9ff2b --- /dev/null +++ b/video_engine/main/source/vie_rtp_rtcp_impl.cc @@ -0,0 +1,1275 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_rtp_rtcp_impl.cc + */ + +#include "vie_rtp_rtcp_impl.h" + +// Defines +#include "engine_configurations.h" +#include "vie_defines.h" + +#include "vie_errors.h" +#include "file_wrapper.h" +#include "trace.h" +#include "vie_impl.h" +#include "vie_channel.h" +#include "vie_channel_manager.h" +#include "vie_encoder.h" + +namespace webrtc +{ + +// ---------------------------------------------------------------------------- +// GetInterface +// ---------------------------------------------------------------------------- + +ViERTP_RTCP* ViERTP_RTCP::GetInterface(VideoEngine* videoEngine) +{ +#ifdef WEBRTC_VIDEO_ENGINE_RTP_RTCP_API + if (videoEngine == NULL) + { + return NULL; + } + VideoEngineImpl* vieImpl = reinterpret_cast (videoEngine); + ViERTP_RTCPImpl* vieRTPImpl = vieImpl; + (*vieRTPImpl)++; // Increase ref count + + return vieRTPImpl; +#else + return NULL; +#endif +} + +// ---------------------------------------------------------------------------- +// Release +// +// Releases the interface, i.e. reduces the reference counter. The number of +// remaining references is returned, -1 if released too many times. +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::Release() +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, _instanceId, + "ViERTP_RTCP::Release()"); + (*this)--; // Decrease ref count + + WebRtc_Word32 refCount = GetCount(); + if (refCount < 0) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, _instanceId, + "ViERTP_RTCP release too many times"); + SetLastError(kViEAPIDoesNotExist); + return -1; + } + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, _instanceId, + "ViERTP_RTCP reference count: %d", refCount); + return refCount; +} + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + +ViERTP_RTCPImpl::ViERTP_RTCPImpl() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, _instanceId, + "ViERTP_RTCPImpl::ViERTP_RTCPImpl() Ctor"); +} + +// ---------------------------------------------------------------------------- +// Destructor +// ---------------------------------------------------------------------------- + +ViERTP_RTCPImpl::~ViERTP_RTCPImpl() +{ + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, _instanceId, + "ViERTP_RTCPImpl::~ViERTP_RTCPImpl() Dtor"); +} + +// ============================================================================ +// SSRC/CSRC +// ============================================================================ + +// ---------------------------------------------------------------------------- +// SetLocalSSRC +// +// Sets the SSRC on the outgoing stream +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::SetLocalSSRC(const int videoChannel, + const unsigned int SSRC) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d, SSRC: %d)", + __FUNCTION__, videoChannel, SSRC); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + if (ptrViEChannel->SetSSRC(SSRC) != 0) + { + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + + return 0; +} + +// ---------------------------------------------------------------------------- +// GetLocalSSRC +// +// Gets the SSRC of the outgoing stream +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::GetLocalSSRC(const int videoChannel, + unsigned int& SSRC) const +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d, SSRC: %d)", + __FUNCTION__, videoChannel, SSRC); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + + if (ptrViEChannel->GetLocalSSRC((WebRtc_UWord32&) SSRC) != 0) + { + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; + +} + +// ---------------------------------------------------------------------------- +// GetRemoteSSRC +// +// Gets the SSRC of the incoming stream +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::GetRemoteSSRC(const int videoChannel, + unsigned int& SSRC) const +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel, SSRC); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + + if (ptrViEChannel->GetRemoteSSRC((WebRtc_UWord32&) SSRC) != 0) + { + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// GetRemoteCSRCs +// +// Gets the CSRC of the incoming stream +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::GetRemoteCSRCs(const int videoChannel, + unsigned int CSRCs[kRtpCsrcSize]) const +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + + if (ptrViEChannel->GetRemoteCSRC(CSRCs) != 0) + { + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// SetStartSequenceNumber +// +// Sets the starting sequence number, instead of a random number. +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::SetStartSequenceNumber(const int videoChannel, + unsigned short sequenceNumber) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(channel: %d, sequenceNumber: %u)", __FUNCTION__, + videoChannel, sequenceNumber); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + if (ptrViEChannel->Sending()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d already sending.", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpAlreadySending); + return -1; + } + + if (ptrViEChannel->SetStartSequenceNumber(sequenceNumber) != 0) + { + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; +} + +// ============================================================================ +// RTCP +// ============================================================================ + +// ---------------------------------------------------------------------------- +// SetRTCPStatus +// +// Sets the RTCP status for the channel +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::SetRTCPStatus(const int videoChannel, + const ViERTCPMode rtcpMode) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId,videoChannel), "%s(channel: %d, mode: %d)", + __FUNCTION__, videoChannel, rtcpMode); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + + RTCPMethod moduleMode = ViERTCPModeToRTCPMethod(rtcpMode); + + if (ptrViEChannel->SetRTCPMode(moduleMode) != 0) + { + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; + +} + +// ---------------------------------------------------------------------------- +// GetRTCPStatus +// +// Gets the RTCP status for the specified channel +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::GetRTCPStatus(const int videoChannel, + ViERTCPMode& rtcpMode) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel, rtcpMode); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + + RTCPMethod moduleMode = kRtcpOff; + if (ptrViEChannel->GetRTCPMode(moduleMode) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: could not get current RTCP mode", __FUNCTION__); + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + + rtcpMode = RTCPMethodToViERTCPMode(moduleMode); + return 0; +} + +// ---------------------------------------------------------------------------- +// SetRTCPCName +// +// Specifies what CName to use +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::SetRTCPCName(const int videoChannel, + const char rtcpCName[KMaxRTCPCNameLength]) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d, name: %s)", + __FUNCTION__, videoChannel, rtcpCName); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + + if (ptrViEChannel->Sending()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d already sending.", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpAlreadySending); + return -1; + } + + if (ptrViEChannel->SetRTCPCName(rtcpCName) != 0) + { + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; + +} + +// ---------------------------------------------------------------------------- +// GetRTCPCName +// +// Gets the set CName +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::GetRTCPCName(const int videoChannel, + char rtcpCName[KMaxRTCPCNameLength]) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + if (ptrViEChannel->GetRTCPCName(rtcpCName) != 0) + { + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; + +} + +// ---------------------------------------------------------------------------- +// GetRemoteRTCPCName +// +// Gets the CName of for the incoming stream +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::GetRemoteRTCPCName(const int videoChannel, + char rtcpCName[KMaxRTCPCNameLength]) const +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + if (ptrViEChannel->GetRemoteRTCPCName(rtcpCName) != 0) + { + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// SendApplicationDefinedRTCPPacket +// +// From RFC 3550: +// +// 6.7 APP: Application-Defined RTCP 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| subtype | PT=APP=204 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC/CSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | name (ASCII) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | application-dependent data ... +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// The APP packet is intended for experimental use as new applications +// and new features are developed, without requiring packet type value +// registration. APP packets with unrecognized names SHOULD be ignored. +// After testing and if wider use is justified, it is RECOMMENDED that +// each APP packet be redefined without the subtype and name fields and +// registered with IANA using an RTCP packet type. +// +// version (V), padding (P), length: +// As described for the SR packet (see Section 6.4.1). +// +// subtype: 5 bits +// May be used as a subtype to allow a set of APP packets to be +// defined under one unique name, or for any application-dependent +// data. +// +// packet type (PT): 8 bits +// Contains the constant 204 to identify this as an RTCP APP packet. +// +// name: 4 octets +// A name chosen by the person defining the set of APP packets to be +// unique with respect to other APP packets this application might +// receive. The application creator might choose to use the +// application name, and then coordinate the allocation of subtype +// values to others who want to define new packet types for the +// application. Alternatively, it is RECOMMENDED that others choose +// a name based on the entity they represent, then coordinate the use +// of the name within that entity. The name is interpreted as a +// sequence of four ASCII characters, with uppercase and lowercase +// characters treated as distinct. +// +// application-dependent data: variable length +// Application-dependent data may or may not appear in an APP packet. +// It is interpreted by the application and not RTP itself. It MUST +// be a multiple of 32 bits long. +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::SendApplicationDefinedRTCPPacket( + const int videoChannel, const unsigned char subType, unsigned int name, + const char* data, unsigned short dataLengthInBytes) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(channel: %d, subType: %c, name: %d, data: x, length: %u)", + __FUNCTION__, videoChannel, subType, name, dataLengthInBytes); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + if (!ptrViEChannel->Sending()) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d not sending", __FUNCTION__, videoChannel); + SetLastError(kViERtpRtcpNotSending); + return -1; + } + RTCPMethod method; + if (ptrViEChannel->GetRTCPMode(method) != 0 || method == kRtcpOff) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: RTCP disabled on channel %d.", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpRtcpDisabled); + return -1; + } + + if (ptrViEChannel->SendApplicationDefinedRTCPPacket( + subType, name, (const WebRtc_UWord8 *) data, dataLengthInBytes) != 0) + { + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// SetNACKStatus +// +// Enables NACK for the specified channel +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::SetNACKStatus(const int videoChannel, const bool enable) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(channel: %d, enable: %d)", __FUNCTION__, videoChannel, + enable); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + // Update the channel status + if (ptrViEChannel->SetNACKStatus(enable) != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: failed for channel %d", __FUNCTION__, videoChannel); + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + + // Update the encoder + ViEEncoder* ptrViEEncoder = cs.Encoder(videoChannel); + if (ptrViEEncoder == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Could not get encoder for channel %d", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + ptrViEEncoder->UpdateProtectionMethod(); + + return 0; +} + +// ---------------------------------------------------------------------------- +// SetFECStatus +// +// Enables/disables FEC and sets the payloadtypes +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::SetFECStatus(const int videoChannel, const bool enable, + const unsigned char payloadTypeRED, + const unsigned char payloadTypeFEC) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(channel: %d, enable: %d, payloadTypeRED: %u, " + "payloadTypeFEC: %u)", + __FUNCTION__, videoChannel, enable, payloadTypeRED, + payloadTypeFEC); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + + // Update the channel status + if (ptrViEChannel->SetFECStatus(enable, payloadTypeRED, payloadTypeFEC) + != 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: failed for channel %d", __FUNCTION__, videoChannel); + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + + // Update the encoder + ViEEncoder* ptrViEEncoder = cs.Encoder(videoChannel); + if (ptrViEEncoder == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Could not get encoder for channel %d", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + ptrViEEncoder->UpdateProtectionMethod(); + return 0; +} + +// ---------------------------------------------------------------------------- +// SetKeyFrameRequestMethod +// +// Sets the key frame request method to use +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::SetKeyFrameRequestMethod( + const int videoChannel, const ViEKeyFrameRequestMethod method) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(channel: %d, method: %d)", __FUNCTION__, videoChannel, + method); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + + KeyFrameRequestMethod moduleMethod = APIRequestToModuleRequest(method); + if (ptrViEChannel->SetKeyFrameRequestMethod(moduleMethod) != 0) + { + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// SetTMMBRStatus +// +// Enables/disables TTMBR +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::SetTMMBRStatus(const int videoChannel, const bool enable) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(channel: %d, enable: %d)", __FUNCTION__, videoChannel, + enable); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + + if (ptrViEChannel->EnableTMMBR(enable) != 0) + { + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; +} + +// ============================================================================ +// Statistics +// ============================================================================ + +// ---------------------------------------------------------------------------- +// GetReceivedRTCPStatistics +// +// Gets statistics received in from remote side +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::GetReceivedRTCPStatistics( + const int videoChannel, unsigned short& fractionLost, + unsigned int& cumulativeLost, unsigned int& extendedMax, + unsigned int& jitter, int& rttMs) const +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + + if (ptrViEChannel->GetReceivedRtcpStatistics( + (WebRtc_UWord16&) fractionLost, (WebRtc_UWord32&) cumulativeLost, + (WebRtc_UWord32&) extendedMax, (WebRtc_UWord32&) jitter, + (WebRtc_Word32&) rttMs) != 0) + { + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// GetSentRTCPStatistics +// +// Gets statistics sent in RTCP to the remote side +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::GetSentRTCPStatistics(const int videoChannel, + unsigned short& fractionLost, + unsigned int& cumulativeLost, + unsigned int& extendedMax, + unsigned int& jitter, + int& rttMs) const +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + + if (ptrViEChannel->GetSendRtcpStatistics((WebRtc_UWord16&) fractionLost, + (WebRtc_UWord32&) cumulativeLost, + (WebRtc_UWord32&) extendedMax, + (WebRtc_UWord32&) jitter, + (WebRtc_Word32&) rttMs) != 0) + { + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// GetRTCPStatistics +// +// Gets statistics about sent/received rtp packets +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::GetRTPStatistics(const int videoChannel, + unsigned int& bytesSent, + unsigned int& packetsSent, + unsigned int& bytesReceived, + unsigned int& packetsReceived) const +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + + if (ptrViEChannel->GetRtpStatistics((WebRtc_UWord32&) bytesSent, + (WebRtc_UWord32&) packetsSent, + (WebRtc_UWord32&) bytesReceived, + (WebRtc_UWord32&) packetsReceived) != 0) + { + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; +} + +// ============================================================================ +// Keep alive +// ============================================================================ + +// ---------------------------------------------------------------------------- +// SetRTPKeepAliveStatus +// +// Enable/disable RTP keepaliv packets on a non-sending channel +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::SetRTPKeepAliveStatus( + const int videoChannel, bool enable, const char unknownPayloadType, + const unsigned int deltaTransmitTimeSeconds) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(channel: %d, enable: %d, unknownPayloadType: %d, " + "deltaTransmitTimeMS: %ul)", + __FUNCTION__, videoChannel, enable, (int) unknownPayloadType, + deltaTransmitTimeSeconds); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + WebRtc_UWord16 deltaTransmitTimeMs = 1000 * deltaTransmitTimeSeconds; + if (ptrViEChannel->SetKeepAliveStatus(enable, unknownPayloadType, + deltaTransmitTimeMs) != 0) + { + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// GetRTPKeepAliveStatus +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::GetRTPKeepAliveStatus( + const int videoChannel, bool& enabled, char& unknownPayloadType, + unsigned int& deltaTransmitTimeSeconds) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + + WebRtc_UWord16 deltaTimeMs = 0; + int retVal = ptrViEChannel->GetKeepAliveStatus(enabled, unknownPayloadType, + deltaTimeMs); + deltaTransmitTimeSeconds = deltaTimeMs / 1000; + if (retVal != 0) + { + SetLastError(kViERtpRtcpUnknownError); + } + return retVal; +} + +// ============================================================================ +// Dump RTP stream, for debug purpose +// ============================================================================ + +// ---------------------------------------------------------------------------- +// StartRTPDump +// +// SAves all incoming/outgoing packets to a file +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::StartRTPDump(const int videoChannel, + const char fileNameUTF8[1024], + RTPDirections direction) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(channel: %d, fileName: %s, direction: %d)", __FUNCTION__, + videoChannel, fileNameUTF8, direction); + + assert(FileWrapper::kMaxFileNameSize == 1024); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + + if (ptrViEChannel->StartRTPDump(fileNameUTF8, direction) != 0) + { + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// StopRTPDump +// +// Stops the RTP dump +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::StopRTPDump(const int videoChannel, + RTPDirections direction) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s(channel: %d, direction: %d)", __FUNCTION__, videoChannel, + direction); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + + if (ptrViEChannel->StopRTPDump(direction) != 0) + { + SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; +} + +// ============================================================================ +// Callback +// ============================================================================ + +// ---------------------------------------------------------------------------- +// RegisterRTPObserver +// +// +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::RegisterRTPObserver(const int videoChannel, + ViERTPObserver& observer) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + + if (ptrViEChannel->RegisterRtpObserver(&observer) != 0) + { + SetLastError(kViERtpRtcpObserverAlreadyRegistered); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// DeregisterRTPObserver +// +// Deregisters a set observer +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::DeregisterRTPObserver(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + + if (ptrViEChannel->RegisterRtpObserver(NULL) != 0) + { + SetLastError(kViERtpRtcpObserverNotRegistered); + return -1; + } + return 0; +} +// ---------------------------------------------------------------------------- +// RegisterRTCPObserver +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::RegisterRTCPObserver(const int videoChannel, + ViERTCPObserver& observer) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + + if (ptrViEChannel->RegisterRtcpObserver(&observer) != 0) + { + SetLastError(kViERtpRtcpObserverAlreadyRegistered); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// DeregisterRTCPObserver +// ---------------------------------------------------------------------------- + +int ViERTP_RTCPImpl::DeregisterRTCPObserver(const int videoChannel) +{ + WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), "%s(channel: %d)", + __FUNCTION__, videoChannel); + + // Get the channel + ViEChannelManagerScoped cs(_channelManager); + ViEChannel* ptrViEChannel = cs.Channel(videoChannel); + if (ptrViEChannel == NULL) + { + // The channel doesn't exists + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_instanceId, videoChannel), + "%s: Channel %d doesn't exist", __FUNCTION__, + videoChannel); + SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + + } + + if (ptrViEChannel->RegisterRtcpObserver(NULL) != 0) + { + SetLastError(kViERtpRtcpObserverNotRegistered); + return -1; + } + return 0; +} + +// ============================================================================ +// Prsivate methods +// ============================================================================ + +// ---------------------------------------------------------------------------- +// ViERTCPModeToRTCPMethod +// +// Help method for converting API mode to Module mode +// ---------------------------------------------------------------------------- + +RTCPMethod ViERTP_RTCPImpl::ViERTCPModeToRTCPMethod(ViERTCPMode apiMode) +{ + switch (apiMode) + { + case kRtcpNone: + return kRtcpOff; + + case kRtcpCompound_RFC4585: + return kRtcpCompound; + + case kRtcpNonCompound_RFC5506: + return kRtcpNonCompound; + + default: + assert(false); + return kRtcpOff; + } +} + +// ---------------------------------------------------------------------------- +// RTCPMethodToViERTCPMode +// +// Help method for converting API mode to Module mode +// ---------------------------------------------------------------------------- + +ViERTCPMode ViERTP_RTCPImpl::RTCPMethodToViERTCPMode(RTCPMethod moduleMethod) +{ + switch (moduleMethod) + { + case kRtcpOff: + return kRtcpNone; + + case kRtcpCompound: + return kRtcpCompound_RFC4585; + + case kRtcpNonCompound: + return kRtcpNonCompound_RFC5506; + + default: + assert(false); + return kRtcpNone; + } +} + +KeyFrameRequestMethod ViERTP_RTCPImpl::APIRequestToModuleRequest( + ViEKeyFrameRequestMethod apiMethod) +{ + switch (apiMethod) + { + case kViEKeyFrameRequestNone: + return kKeyFrameReqFirRtp; + + case kViEKeyFrameRequestPliRtcp: + return kKeyFrameReqPliRtcp; + + case kViEKeyFrameRequestFirRtp: + return kKeyFrameReqFirRtp; + + case kViEKeyFrameRequestFirRtcp: + return kKeyFrameReqFirRtcp; + + default: + assert(false); + return kKeyFrameReqFirRtp; + } +} +} // namespace webrtc diff --git a/video_engine/main/source/vie_rtp_rtcp_impl.h b/video_engine/main/source/vie_rtp_rtcp_impl.h new file mode 100644 index 0000000000..8c429ed3b7 --- /dev/null +++ b/video_engine/main/source/vie_rtp_rtcp_impl.h @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_rtp_rtcp_impl.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_RTP_RTCP_IMPL_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_RTP_RTCP_IMPL_H_ + +#include "vie_defines.h" + +#include "rtp_rtcp_defines.h" +#include "typedefs.h" +#include "vie_ref_count.h" +#include "vie_rtp_rtcp.h" +#include "vie_shared_data.h" + +namespace webrtc +{ + +// ---------------------------------------------------------------------------- +// ViERTP_RTCPImpl +// ---------------------------------------------------------------------------- + +class ViERTP_RTCPImpl : public virtual ViESharedData, + public ViERTP_RTCP, + public ViERefCount +{ +public: + virtual int Release(); + + // SSRC/CSRC + virtual int SetLocalSSRC(const int videoChannel, const unsigned int SSRC); + + virtual int GetLocalSSRC(const int videoChannel, unsigned int& SSRC) const; + + virtual int GetRemoteSSRC(const int videoChannel, unsigned int& SSRC) const; + + virtual int GetRemoteCSRCs(const int videoChannel, + unsigned int CSRCs[kRtpCsrcSize]) const; + + virtual int SetStartSequenceNumber(const int videoChannel, + unsigned short sequenceNumber); + + // RTCP + virtual int SetRTCPStatus(const int videoChannel, + const ViERTCPMode rtcpMode); + + virtual int GetRTCPStatus(const int videoChannel, ViERTCPMode& rtcpMode); + + virtual int SetRTCPCName(const int videoChannel, + const char rtcpCName[KMaxRTCPCNameLength]); + + virtual int GetRTCPCName(const int videoChannel, + char rtcpCName[KMaxRTCPCNameLength]); + + virtual int GetRemoteRTCPCName(const int videoChannel, + char rtcpCName[KMaxRTCPCNameLength]) const; + + virtual int + SendApplicationDefinedRTCPPacket(const int videoChannel, + const unsigned char subType, + unsigned int name, const char* data, + unsigned short dataLengthInBytes); + + virtual int SetNACKStatus(const int videoChannel, const bool enable); + + virtual int SetFECStatus(const int videoChannel, const bool enable, + const unsigned char payloadTypeRED, + const unsigned char payloadTypeFEC); + + virtual int SetKeyFrameRequestMethod(const int videoChannel, + const ViEKeyFrameRequestMethod method); + + virtual int SetTMMBRStatus(const int videoChannel, const bool enable); + + // Statistics + virtual int GetReceivedRTCPStatistics( + const int videoChannel, unsigned short& fractionLost, + unsigned int& cumulativeLost, unsigned int& extendedMax, + unsigned int& jitter, int& rttMs) const; + + virtual int GetSentRTCPStatistics(const int videoChannel, + unsigned short& fractionLost, + unsigned int& cumulativeLost, + unsigned int& extendedMax, + unsigned int& jitter, int& rttMs) const; + + virtual int GetRTPStatistics(const int videoChannel, + unsigned int& bytesSent, + unsigned int& packetsSent, + unsigned int& bytesReceived, + unsigned int& packetsReceived) const; + + // Keep alive + virtual int SetRTPKeepAliveStatus( + const int videoChannel, bool enable, const char unknownPayloadType, + const unsigned int deltaTransmitTimeSeconds); + + virtual int GetRTPKeepAliveStatus(const int videoChannel, bool& enabled, + char& unkownPayloadType, + unsigned int& deltaTransmitTimeSeconds); + + // Dump RTP stream, for debug purpose + virtual int StartRTPDump(const int videoChannel, + const char fileNameUTF8[1024], + RTPDirections direction); + + virtual int StopRTPDump(const int videoChannel, RTPDirections direction); + + // Callbacks + virtual int RegisterRTPObserver(const int videoChannel, + ViERTPObserver& observer); + + virtual int DeregisterRTPObserver(const int videoChannel); + + virtual int RegisterRTCPObserver(const int videoChannel, + ViERTCPObserver& observer); + + virtual int DeregisterRTCPObserver(const int videoChannel); + +protected: + ViERTP_RTCPImpl(); + virtual ~ViERTP_RTCPImpl(); + +private: + RTCPMethod ViERTCPModeToRTCPMethod(ViERTCPMode apiMode); + ViERTCPMode RTCPMethodToViERTCPMode(RTCPMethod moduleMethod); + KeyFrameRequestMethod + APIRequestToModuleRequest(const ViEKeyFrameRequestMethod apiMethod); +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_RTP_RTCP_IMPL_H_ diff --git a/video_engine/main/source/vie_sender.cc b/video_engine/main/source/vie_sender.cc new file mode 100644 index 0000000000..3315f4db68 --- /dev/null +++ b/video_engine/main/source/vie_sender.cc @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_sender.cc + */ + +#include "vie_sender.h" + +#include "critical_section_wrapper.h" +#include "rtp_rtcp.h" +#ifdef WEBRTC_SRTP +#include "SrtpModule.h" +#endif +#include "rtp_dump.h" +#include "trace.h" + +namespace webrtc { + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + +ViESender::ViESender(int engineId, int channelId, + RtpRtcp& rtpRtcpModule) + : _engineId(engineId), _channelId(channelId), + _sendCritsect(*CriticalSectionWrapper::CreateCriticalSection()), + _rtpRtcp(rtpRtcpModule), +#ifdef WEBRTC_SRTP + _ptrSrtp(NULL), + _ptrSrtcp(NULL), +#endif + _ptrExternalEncryption(NULL), _ptrSrtpBuffer(NULL), + _ptrSrtcpBuffer(NULL), _ptrEncryptionBuffer(NULL), _ptrTransport(NULL), + _rtpDump(NULL) +{ +} + +// ---------------------------------------------------------------------------- +// Destructor +// ---------------------------------------------------------------------------- +ViESender::~ViESender() +{ + delete &_sendCritsect; + + if (_ptrSrtpBuffer) + { + delete[] _ptrSrtpBuffer; + _ptrSrtpBuffer = NULL; + } + if (_ptrSrtcpBuffer) + { + delete[] _ptrSrtcpBuffer; + _ptrSrtcpBuffer = NULL; + } + if (_ptrEncryptionBuffer) + { + delete[] _ptrEncryptionBuffer; + _ptrEncryptionBuffer = NULL; + } + if (_rtpDump) + { + _rtpDump->Stop(); + RtpDump::DestroyRtpDump(_rtpDump); + _rtpDump = NULL; + } +} + +// ---------------------------------------------------------------------------- +// RegisterExternalEncryption +// ---------------------------------------------------------------------------- + +int ViESender::RegisterExternalEncryption(Encryption* encryption) +{ + CriticalSectionScoped cs(_sendCritsect); + if (_ptrExternalEncryption) + { + return -1; + } + _ptrEncryptionBuffer = new WebRtc_UWord8[kViEMaxMtu]; + if (_ptrEncryptionBuffer == NULL) + { + return -1; + } + _ptrExternalEncryption = encryption; + return 0; +} + +// ---------------------------------------------------------------------------- +// DeregisterExternalEncryption +// ---------------------------------------------------------------------------- + +int ViESender::DeregisterExternalEncryption() +{ + CriticalSectionScoped cs(_sendCritsect); + if (_ptrExternalEncryption == NULL) + { + return -1; + } + if (_ptrEncryptionBuffer) + { + delete _ptrEncryptionBuffer; + _ptrEncryptionBuffer = NULL; + } + _ptrExternalEncryption = NULL; + return 0; +} + +// ---------------------------------------------------------------------------- +// RegisterSendTransport +// ---------------------------------------------------------------------------- + +int ViESender::RegisterSendTransport(Transport* transport) +{ + CriticalSectionScoped cs(_sendCritsect); + if (_ptrTransport) + { + return -1; + } + _ptrTransport = transport; + return 0; +} + +// ---------------------------------------------------------------------------- +// DeregisterSendTransport +// ---------------------------------------------------------------------------- + +int ViESender::DeregisterSendTransport() +{ + CriticalSectionScoped cs(_sendCritsect); + if (_ptrTransport == NULL) + { + return -1; + } + _ptrTransport = NULL; + return 0; +} + +#ifdef WEBRTC_SRTP +// ---------------------------------------------------------------------------- +// RegisterSRTPModule +// ---------------------------------------------------------------------------- + +int ViESender::RegisterSRTPModule(SrtpModule* srtpModule) +{ + CriticalSectionScoped cs(_sendCritsect); + if (_ptrSrtp || + srtpModule == NULL) + { + return -1; + } + _ptrSrtpBuffer = new WebRtc_UWord8[KMaxPacketSize]; + if (_ptrSrtpBuffer == NULL) + { + return -1; + } + _ptrSrtp = srtpModule; + + return 0; +} + +// ---------------------------------------------------------------------------- +// DeregisterSRTPModule +// ---------------------------------------------------------------------------- + +int ViESender::DeregisterSRTPModule() +{ + CriticalSectionScoped cs(_sendCritsect); + if (_ptrSrtp == NULL) + { + return -1; + } + if (_ptrSrtpBuffer) + { + delete [] _ptrSrtpBuffer; + _ptrSrtpBuffer = NULL; + } + _ptrSrtp = NULL; + return 0; +} +// ---------------------------------------------------------------------------- +// RegisterSRTCPModule +// ---------------------------------------------------------------------------- + +int ViESender::RegisterSRTCPModule(SrtpModule* srtcpModule) +{ + CriticalSectionScoped cs(_sendCritsect); + if (_ptrSrtcp || + srtcpModule == NULL) + { + return -1; + } + _ptrSrtcpBuffer = new WebRtc_UWord8[KMaxPacketSize]; + if (_ptrSrtcpBuffer == NULL) + { + return -1; + } + _ptrSrtcp = srtcpModule; + + return 0; +} + +// ---------------------------------------------------------------------------- +// DeregisterSRTCPModule +// ---------------------------------------------------------------------------- + +int ViESender::DeregisterSRTCPModule() +{ + CriticalSectionScoped cs(_sendCritsect); + if (_ptrSrtcp == NULL) + { + return -1; + } + if (_ptrSrtcpBuffer) + { + delete [] _ptrSrtcpBuffer; + _ptrSrtcpBuffer = NULL; + } + _ptrSrtcp = NULL; + return 0; +} +#endif + +// ---------------------------------------------------------------------------- +// StartRTPDump +// ---------------------------------------------------------------------------- + +int ViESender::StartRTPDump(const char fileNameUTF8[1024]) +{ + CriticalSectionScoped cs(_sendCritsect); + if (_rtpDump) + { + // Restart it if it already exists and is started + _rtpDump->Stop(); + } else + { + _rtpDump = RtpDump::CreateRtpDump(); + if (_rtpDump == NULL) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, + _channelId), + "%s: Failed to create RTP dump", __FUNCTION__); + return -1; + } + } + if (_rtpDump->Start(fileNameUTF8) != 0) + { + RtpDump::DestroyRtpDump(_rtpDump); + _rtpDump = NULL; + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), + "%s: Failed to start RTP dump", __FUNCTION__); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// StopRTPDump +// ---------------------------------------------------------------------------- + +int ViESender::StopRTPDump() +{ + CriticalSectionScoped cs(_sendCritsect); + if (_rtpDump) + { + if (_rtpDump->IsActive()) + { + _rtpDump->Stop(); + } else + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, + _channelId), + "%s: Dump not active", __FUNCTION__); + } + RtpDump::DestroyRtpDump(_rtpDump); + _rtpDump = NULL; + } else + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, + ViEId(_engineId, _channelId), "%s: RTP dump not started", + __FUNCTION__); + return -1; + } + return 0; +} + +// ---------------------------------------------------------------------------- +// SendPacket +// ---------------------------------------------------------------------------- +int ViESender::SendPacket(int vieId, const void *data, int len) +{ + CriticalSectionScoped cs(_sendCritsect); + if (!_ptrTransport) + { + // No transport + return -1; + } + + int channelId = ChannelId(vieId); + assert(channelId == _channelId); + + // Prepare for possible encryption and sending + WebRtc_UWord8* sendPacket = (WebRtc_UWord8*) data; + int sendPacketLength = len; + + if (_rtpDump) + { + _rtpDump->DumpPacket(sendPacket, sendPacketLength); + } +#ifdef WEBRTC_SRTP + if (_ptrSrtp) + { + _ptrSrtp->encrypt(_channelId, sendPacket, _ptrSrtpBuffer, sendPacketLength, (int*) &sendPacketLength); + if (sendPacketLength <= 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), "RTP encryption failed for channel"); + return -1; + } + else if (sendPacketLength > KMaxPacketSize) + { + WEBRTC_TRACE(webrtc::kTraceCritical, webrtc::kTraceVideo, ViEId(_engineId, _channelId), + " %d bytes is allocated as RTP output => memory is now corrupted", KMaxPacketSize); + return -1; + } + sendPacket = _ptrSrtpBuffer; + } +#endif + if (_ptrExternalEncryption) + { + _ptrExternalEncryption->encrypt(_channelId, sendPacket, + _ptrEncryptionBuffer, sendPacketLength, + (int*) &sendPacketLength); + sendPacket = _ptrEncryptionBuffer; + } + + return _ptrTransport->SendPacket(_channelId, sendPacket, sendPacketLength); +} +// ---------------------------------------------------------------------------- +// SendRTCPPacket +// ---------------------------------------------------------------------------- + +int ViESender::SendRTCPPacket(int vieId, const void *data, int len) +{ + CriticalSectionScoped cs(_sendCritsect); + + if (!_ptrTransport) + { + // No transport + return -1; + } + int channelId = ChannelId(vieId); + assert(channelId == _channelId); + + // Prepare for possible encryption and sending + WebRtc_UWord8* sendPacket = (WebRtc_UWord8*) data; + int sendPacketLength = len; + + if (_rtpDump) + { + _rtpDump->DumpPacket(sendPacket, sendPacketLength); + } +#ifdef WEBRTC_SRTP + if (_ptrSrtcp) + { + _ptrSrtcp->encrypt_rtcp(_channelId, sendPacket, _ptrSrtcpBuffer, sendPacketLength, (int*) &sendPacketLength); + if (sendPacketLength <= 0) + { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId), "RTCP encryption failed for channel"); + return -1; + } + else if (sendPacketLength > KMaxPacketSize) + { + WEBRTC_TRACE(webrtc::kTraceCritical, webrtc::kTraceVideo, ViEId(_engineId, _channelId), " %d bytes is allocated as RTCP output => memory is now corrupted", KMaxPacketSize); + return -1; + } + sendPacket = _ptrSrtcpBuffer; + } +#endif + if (_ptrExternalEncryption) + { + _ptrExternalEncryption->encrypt_rtcp(_channelId, sendPacket, + _ptrEncryptionBuffer, + sendPacketLength, + (int*) &sendPacketLength); + sendPacket = _ptrEncryptionBuffer; + } + + return _ptrTransport->SendRTCPPacket(_channelId, sendPacket, + sendPacketLength); +} +} // namespace webrtc diff --git a/video_engine/main/source/vie_sender.h b/video_engine/main/source/vie_sender.h new file mode 100644 index 0000000000..4d6726710f --- /dev/null +++ b/video_engine/main/source/vie_sender.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_sender.h + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_SENDER_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_SENDER_H_ + +// Defines +#include "engine_configurations.h" +#include "vie_defines.h" +#include "typedefs.h" +#include "common_types.h" + +// Forward declarations + +#ifdef WEBRTC_SRTP + class SrtpModule; +#endif + +namespace webrtc { +class CriticalSectionWrapper; +class RtpDump; +class RtpRtcp; +class Transport; +class VideoCodingModule; + +class ViESender: public Transport +{ +public: + ViESender(int engineId, int channelId, RtpRtcp& rtpRtcpModule); + ~ViESender(); + + int RegisterExternalEncryption(Encryption* encryption); + int DeregisterExternalEncryption(); + + int RegisterSendTransport(Transport* transport); + int DeregisterSendTransport(); + +#ifdef WEBRTC_SRTP + int RegisterSRTPModule(SrtpModule* srtpModule); + int DeregisterSRTPModule(); + + int RegisterSRTCPModule(SrtpModule* srtpModule); + int DeregisterSRTCPModule(); +#endif + + int StartRTPDump(const char fileNameUTF8[1024]); + int StopRTPDump(); + + // Implements Transport + virtual int SendPacket(int vieId, const void *data, int len); + virtual int SendRTCPPacket(int vieId, const void *data, int len); + +private: + int _engineId; + int _channelId; + CriticalSectionWrapper& _sendCritsect; + RtpRtcp& _rtpRtcp; + +#ifdef WEBRTC_SRTP + SrtpModule* _ptrSrtp; + SrtpModule* _ptrSrtcp; +#endif + + Encryption* _ptrExternalEncryption; + WebRtc_UWord8* _ptrSrtpBuffer; + WebRtc_UWord8* _ptrSrtcpBuffer; + WebRtc_UWord8* _ptrEncryptionBuffer; + Transport* _ptrTransport; + RtpDump* _rtpDump; +}; + +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_SENDER_H_ diff --git a/video_engine/main/source/vie_shared_data.cc b/video_engine/main/source/vie_shared_data.cc new file mode 100644 index 0000000000..800a9b827e --- /dev/null +++ b/video_engine/main/source/vie_shared_data.cc @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// ViESharedData.cpp + +#include "vie_shared_data.h" +#include "vie_defines.h" + +#include "cpu_wrapper.h" +#include "critical_section_wrapper.h" +#include "process_thread.h" +#include "trace.h" +#include "vie_channel_manager.h" +#include "vie_input_manager.h" +#include "vie_render_manager.h" + +namespace webrtc { + +// Active instance counter +int ViESharedData::_instanceCounter = 0; + +ViESharedData::ViESharedData() + : _instanceId(++_instanceCounter), + _apiCritsect(*CriticalSectionWrapper::CreateCriticalSection()), + _isInitialized(false), _numberOfCores(CpuWrapper::DetectNumberOfCores()), + _moduleProcessThreadPtr(ProcessThread::CreateProcessThread()), + _viePerformanceMonitor(ViEPerformanceMonitor(_instanceId)), + _channelManager(*new ViEChannelManager(_instanceId, _numberOfCores, + _viePerformanceMonitor)), + _inputManager(*new ViEInputManager(_instanceId)), + _renderManager(*new ViERenderManager(_instanceId)), _lastError(0) +{ + Trace::CreateTrace(); + _channelManager.SetModuleProcessThread(*_moduleProcessThreadPtr); + _inputManager.SetModuleProcessThread(*_moduleProcessThreadPtr); + _moduleProcessThreadPtr->Start(); +} + +ViESharedData::~ViESharedData() +{ + delete &_inputManager; + delete &_channelManager; + delete &_renderManager; + + _moduleProcessThreadPtr->Stop(); + ProcessThread::DestroyProcessThread(_moduleProcessThreadPtr); + delete &_apiCritsect; + Trace::ReturnTrace(); +} + +bool ViESharedData::IsInitialized() const +{ + return _isInitialized; +} + +int ViESharedData::SetInitialized() +{ + _isInitialized = true; + return 0; +} + +int ViESharedData::SetUnInitialized() +{ + _isInitialized = false; + return 0; +} + +void ViESharedData::SetLastError(const int error) const +{ + _lastError = error; +} + +int ViESharedData::LastErrorInternal() const +{ + int error = _lastError; + _lastError = 0; + return error; +} + +int ViESharedData::NumberOfCores() const +{ + return _numberOfCores; +} +} // namespace webrtc diff --git a/video_engine/main/source/vie_shared_data.h b/video_engine/main/source/vie_shared_data.h new file mode 100644 index 0000000000..ec3d83fbe6 --- /dev/null +++ b/video_engine/main/source/vie_shared_data.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// vie_shared_data.h + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_SHARED_DATA_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_SHARED_DATA_H_ + +#include "vie_defines.h" +#include "vie_performance_monitor.h" + +namespace webrtc { +class CriticalSectionWrapper; +class ViERenderManager; +class ViEChannelManager; +class ViEInputManager; +class ProcessThread; + +class ViESharedData +{ +protected: + ViESharedData(); + ~ViESharedData(); + + bool IsInitialized() const; + int SetInitialized(); + int SetUnInitialized(); + void SetLastError(const int error) const; + int LastErrorInternal() const; +protected: + int NumberOfCores() const; + + static int _instanceCounter; + const int _instanceId; + CriticalSectionWrapper& _apiCritsect; + bool _isInitialized; + const int _numberOfCores; + + ViEPerformanceMonitor _viePerformanceMonitor; + ViEChannelManager& _channelManager; + ViEInputManager& _inputManager; + ViERenderManager& _renderManager; + ProcessThread* _moduleProcessThreadPtr; +private: + mutable int _lastError; +}; +} // namespace webrtc + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_SHARED_DATA_H_ diff --git a/video_engine/main/source/vie_sync_module.cc b/video_engine/main/source/vie_sync_module.cc new file mode 100644 index 0000000000..ccddab8c83 --- /dev/null +++ b/video_engine/main/source/vie_sync_module.cc @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "vie_sync_module.h" +#include "critical_section_wrapper.h" +#include "voe_video_sync.h" +#include "rtp_rtcp.h" +#include "trace.h" +#include "video_coding.h" + +namespace webrtc { + +ViESyncModule::ViESyncModule(int id, VideoCodingModule& vcm, + RtpRtcp& rtcpModule) + : _dataCritsect(*CriticalSectionWrapper::CreateCriticalSection()), _id(id), + _vcm(vcm), _rtcpModule(rtcpModule), _voiceChannelId(-1), + _voiceSyncInterface(NULL), _lastSyncTime(TickTime::Now()) +{ +} + +ViESyncModule::~ViESyncModule() +{ + delete &_dataCritsect; +} + +int ViESyncModule::SetVoiceChannel(int voiceChannelId, + VoEVideoSync* veSyncInterface) +{ + CriticalSectionScoped cs(_dataCritsect); + _voiceChannelId = voiceChannelId; + _voiceSyncInterface = veSyncInterface; + _rtcpModule.DeRegisterSyncModule(); + + if (!veSyncInterface) + { + _voiceChannelId = -1; + if (voiceChannelId >= 0) // trying to set a voice channel but no interface exist + { + return -1; + } + return 0; + } + RtpRtcp* voiceRTPRTCP = NULL; + veSyncInterface->GetRtpRtcp(_voiceChannelId, voiceRTPRTCP); + return _rtcpModule.RegisterSyncModule(voiceRTPRTCP); +} + +int ViESyncModule::VoiceChannel() +{ + return _voiceChannelId; +} + +// ---------------------------------------------------------------------------- +// SetNetworkDelay +// +// Set how long time in ms voice is ahead of video when received on the network. +// Positive means audio is ahead of video. +// ---------------------------------------------------------------------------- +void ViESyncModule::SetNetworkDelay(int networkDelay) +{ + _channelDelay.networkDelay = networkDelay; +} + +// Implements Module +WebRtc_Word32 ViESyncModule::Version(WebRtc_Word8* version, + WebRtc_UWord32& remainingBufferInBytes, + WebRtc_UWord32& position) const +{ + if (version == NULL) + { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, -1, + "Invalid in argument to ViESyncModule Version()"); + return -1; + } + WebRtc_Word8 ourVersion[] = "ViESyncModule 1.1.0"; + WebRtc_UWord32 ourLength = (WebRtc_UWord32) strlen(ourVersion); + if (remainingBufferInBytes < ourLength + 1) + { + return -1; + } + memcpy(version, ourVersion, ourLength); + version[ourLength] = '\0'; // null terminaion + remainingBufferInBytes -= (ourLength + 1); + position += (ourLength + 1); + return 0; +} + +WebRtc_Word32 ViESyncModule::ChangeUniqueId(const WebRtc_Word32 id) +{ + _id = id; + return 0; +} + +WebRtc_Word32 ViESyncModule::TimeUntilNextProcess() +{ + return (WebRtc_Word32) (kSyncInterval - (TickTime::Now() + - _lastSyncTime).Milliseconds()); +} + +// Do the lip sync. +WebRtc_Word32 ViESyncModule::Process() +{ + CriticalSectionScoped cs(_dataCritsect); + _lastSyncTime = TickTime::Now(); + + int totalVideoDelayTargetMS = _vcm.Delay(); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, _id, + "Video delay (JB + decoder) is %d ms", totalVideoDelayTargetMS); + + if (_voiceChannelId != -1) + { + // Get //Sync start + int currentAudioDelayMS = 0; + if (_voiceSyncInterface->GetDelayEstimate(_voiceChannelId, + currentAudioDelayMS) != 0) + { + // Could not get VoE delay value, probably not a valid channel Id. + WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, _id, + "%s: VE_GetDelayEstimate error for voiceChannel %d", + __FUNCTION__, totalVideoDelayTargetMS, _voiceChannelId); + return 0; + } + int currentDiffMS = 0; + int videoDelayMS = 0; // Total video delay + if (currentAudioDelayMS > 40) // Voice Engine report delay estimates even when not started. Ignore + { + + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, _id, + "Audio delay is: %d for voice channel: %d", + currentAudioDelayMS, _voiceChannelId); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, _id, + "Network delay diff is: %d for voice channel: %d", + _channelDelay.networkDelay, _voiceChannelId); + // Calculate the diff between the lowest possible + // video delay and the current audio delay + currentDiffMS = totalVideoDelayTargetMS - currentAudioDelayMS + + _channelDelay.networkDelay; + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, _id, + "Current diff is: %d for audio channel: %d", + currentDiffMS, _voiceChannelId); + + if (currentDiffMS > 0) + { + // The minimum video delay is longer than the current audio delay. + // We need to decrease extra video delay, if we have added extra delay + // earlier, or add extra audio delay. + if (_channelDelay.extraVideoDelayMS > 0) + { + // We have extra delay added to ViE. + // Reduce this delay before adding delay to VE. + + // This is the desired delay, we can't reduce more than this. + videoDelayMS = totalVideoDelayTargetMS; + + // Check we don't reduce the delay too much + if (videoDelayMS < _channelDelay.lastVideoDelayMS + - kMaxVideoDiffMS) + { + // Too large step... + videoDelayMS = _channelDelay.lastVideoDelayMS + - kMaxVideoDiffMS; + _channelDelay.extraVideoDelayMS = videoDelayMS + - totalVideoDelayTargetMS; + } else + { + _channelDelay.extraVideoDelayMS = 0; + } + _channelDelay.lastVideoDelayMS = videoDelayMS; + _channelDelay.lastSyncDelay = -1; + _channelDelay.extraAudioDelayMS = 0; + } else + { + // We have no extra video delay to remove. + // Increase the audio delay + if (_channelDelay.lastSyncDelay >= 0) + { + // We have increased the audio delay earlier, + // increase it even more. + int audioDiffMS = currentDiffMS / 2; + if (audioDiffMS > kMaxAudioDiffMS) + { + // We only allow a maximum change of KMaxAudioDiffMS for audio + // due to NetEQ maximum changes. + audioDiffMS = kMaxAudioDiffMS; + } + // Increase the audio delay + _channelDelay.extraAudioDelayMS += audioDiffMS; + + // Don't set a too high delay. + if (_channelDelay.extraAudioDelayMS > kMaxDelay) + { + _channelDelay.extraAudioDelayMS = kMaxDelay; + } + + // Don't add any extra video delay. + videoDelayMS = totalVideoDelayTargetMS; + _channelDelay.extraVideoDelayMS = 0; + _channelDelay.lastVideoDelayMS = videoDelayMS; + + _channelDelay.lastSyncDelay = 1; + } else // lastSyncDelay < 0 + { + // First time after a delay change, don't add any extra delay. + // This is to not toggle back and forth too much. + _channelDelay.extraAudioDelayMS = 0; + // Set minimum video delay + videoDelayMS = totalVideoDelayTargetMS; + _channelDelay.extraVideoDelayMS = 0; + _channelDelay.lastVideoDelayMS = videoDelayMS; + _channelDelay.lastSyncDelay = 0; + } + } + } else // if (currentDiffMS > 0) + { + // The minimum video delay is lower than the current audio delay. + // We need to decrease possible extra audio delay, or + // add extra video delay. + + if (_channelDelay.extraAudioDelayMS > 0) + { + // We have extra delay in VoiceEngine + // Start with decreasing the voice delay + int audioDiffMS = currentDiffMS / 2; // This is a negative value + if (audioDiffMS < -1 * kMaxAudioDiffMS) + { + // Don't change the delay too much at once. + audioDiffMS = -1 * kMaxAudioDiffMS; + } + _channelDelay.extraAudioDelayMS += audioDiffMS; // Add the negative change... + + if (_channelDelay.extraAudioDelayMS < 0) + { + // Negative values not allowed + _channelDelay.extraAudioDelayMS = 0; + _channelDelay.lastSyncDelay = 0; + } else + { + // There is more audio delay to use for the next round. + _channelDelay.lastSyncDelay = 1; + } + + // Keep the video delay at the minimum values. + videoDelayMS = totalVideoDelayTargetMS; + _channelDelay.extraVideoDelayMS = 0; + _channelDelay.lastVideoDelayMS = videoDelayMS; + } else + { + // We have no extra delay in VoiceEngine + // Increase the video delay + _channelDelay.extraAudioDelayMS = 0; + + // Make the diff positive + int videoDiffMS = -1 * currentDiffMS; + + // This is the desired delay we want + videoDelayMS = totalVideoDelayTargetMS + videoDiffMS; + if (videoDelayMS > _channelDelay.lastVideoDelayMS) + { + if (videoDelayMS > _channelDelay.lastVideoDelayMS + + kMaxVideoDiffMS) + { + // Don't increase the delay too much at once + videoDelayMS = _channelDelay.lastVideoDelayMS + + kMaxVideoDiffMS; + } + // Verify we don't go above the maximum allowed delay + if (videoDelayMS > kMaxDelay) + { + videoDelayMS = kMaxDelay; + } + } else + { + if (videoDelayMS < _channelDelay.lastVideoDelayMS + - kMaxVideoDiffMS) + { + // Don't decrease the delay too much at once + videoDelayMS = _channelDelay.lastVideoDelayMS + - kMaxVideoDiffMS; + } + // Verify we don't go below the minimum delay + if (videoDelayMS < totalVideoDelayTargetMS) + { + videoDelayMS = totalVideoDelayTargetMS; + } + } + // Store the values + _channelDelay.extraVideoDelayMS = videoDelayMS + - totalVideoDelayTargetMS; + _channelDelay.lastVideoDelayMS = videoDelayMS; + _channelDelay.lastSyncDelay = -1; + } + } + } + + WEBRTC_TRACE( + webrtc::kTraceInfo, + webrtc::kTraceVideo, + _id, + "Sync video delay %d ms for video channel and audio delay %d for audio channel %d", + videoDelayMS, _channelDelay.extraAudioDelayMS, + _voiceChannelId); + + // Set the extra audio delay + if (_voiceSyncInterface->SetMinimumPlayoutDelay(_voiceChannelId, + _channelDelay.extraAudioDelayMS) == -1) + { + WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideo, _id, + "Error setting voice delay"); + } + + // sanity + // negative not valid + if (videoDelayMS < 0) + { + videoDelayMS = 0; + } + totalVideoDelayTargetMS = (totalVideoDelayTargetMS > videoDelayMS) ? + totalVideoDelayTargetMS : videoDelayMS; + _vcm.SetMinimumPlayoutDelay(totalVideoDelayTargetMS); + WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, _id, + "New Video delay target is: %d", totalVideoDelayTargetMS); + } + return 0; +} +} // namespace webrtc diff --git a/video_engine/main/source/vie_sync_module.h b/video_engine/main/source/vie_sync_module.h new file mode 100644 index 0000000000..fc92f862cd --- /dev/null +++ b/video_engine/main/source/vie_sync_module.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_sync_module.h + * Responsible for doing Audio/Video sync + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_SYNC_MODULE_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_SYNC_MODULE_H_ + +#include "module.h" +#include "tick_util.h" + +namespace webrtc +{ +class CriticalSectionWrapper; +class RtpRtcp; +class VideoCodingModule; +class VoEVideoSync; + + +class ViESyncModule : public Module +{ +public: + enum { kSyncInterval = 1000}; + enum { kMaxVideoDiffMS = 80 }; // Video sync + enum { kMaxAudioDiffMS = 80 }; // Video sync + enum { kMaxDelay = 1500 }; // Video sync + + ViESyncModule(int id, VideoCodingModule& vcm, + RtpRtcp& rtcpModule); + ~ViESyncModule(); + int SetVoiceChannel(int voiceChannelId, VoEVideoSync* voiceSyncInterface); + int VoiceChannel(); + void SetNetworkDelay(int networkDelay); + + // Implements Module + virtual WebRtc_Word32 Version(WebRtc_Word8* version, + WebRtc_UWord32& remainingBufferInBytes, + WebRtc_UWord32& position) const; + + virtual WebRtc_Word32 ChangeUniqueId(const WebRtc_Word32 id); + virtual WebRtc_Word32 TimeUntilNextProcess(); + virtual WebRtc_Word32 Process(); + +private: + // Critical sections + CriticalSectionWrapper& _dataCritsect; + int _id; + VideoCodingModule& _vcm; + RtpRtcp& _rtcpModule; + int _voiceChannelId; + VoEVideoSync* _voiceSyncInterface; + TickTime _lastSyncTime; + + struct ViESyncDelay + { + ViESyncDelay() + { + extraVideoDelayMS = 0; + lastVideoDelayMS = 0; + extraAudioDelayMS = 0; + lastSyncDelay = 0; + networkDelay = 120; + } + int extraVideoDelayMS; + int lastVideoDelayMS; + int extraAudioDelayMS; //audioDelayMS; + int lastSyncDelay; + int networkDelay; + }; + ViESyncDelay _channelDelay; + +}; +} // namespace webrtc +#endif // WEBRTC_VIDEO_ENGINE_MAIN_SOURCE_VIE_SYNC_MODULE_H_ diff --git a/video_engine/main/test/AndroidTest/.classpath b/video_engine/main/test/AndroidTest/.classpath new file mode 100644 index 0000000000..f2adf55d20 --- /dev/null +++ b/video_engine/main/test/AndroidTest/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/video_engine/main/test/AndroidTest/.project b/video_engine/main/test/AndroidTest/.project new file mode 100644 index 0000000000..6765ec26c5 --- /dev/null +++ b/video_engine/main/test/AndroidTest/.project @@ -0,0 +1,33 @@ + + + ViEAndroidDemo + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/video_engine/main/test/AndroidTest/AndroidManifest.xml b/video_engine/main/test/AndroidTest/AndroidManifest.xml new file mode 100644 index 0000000000..670f2a3ccb --- /dev/null +++ b/video_engine/main/test/AndroidTest/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/video_engine/main/test/AndroidTest/default.properties b/video_engine/main/test/AndroidTest/default.properties new file mode 100644 index 0000000000..c206f58583 --- /dev/null +++ b/video_engine/main/test/AndroidTest/default.properties @@ -0,0 +1,13 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "build.properties", and override values to adapt the script to your +# project structure. + +# Indicates whether an apk should be generated for each density. +split.density=false +# Project target. +target=android-9 diff --git a/video_engine/main/test/AndroidTest/gen/org/webrtc/androidapp/R.java b/video_engine/main/test/AndroidTest/gen/org/webrtc/androidapp/R.java new file mode 100644 index 0000000000..c35047b661 --- /dev/null +++ b/video_engine/main/test/AndroidTest/gen/org/webrtc/androidapp/R.java @@ -0,0 +1,68 @@ +/* AUTO-GENERATED FILE. DO NOT MODIFY. + * + * This class was automatically generated by the + * aapt tool from the resource data it found. It + * should not be modified by hand. + */ + +package org.webrtc.videoengineapp; + +public final class R { + public static final class array { + public static final int codecSize=0x7f040001; + public static final int codectype=0x7f040000; + public static final int voiceCodecType=0x7f040002; + } + public static final class attr { + } + public static final class drawable { + public static final int bar=0x7f020000; + public static final int bg=0x7f020001; + public static final int logo=0x7f020002; + public static final int robot=0x7f020003; + public static final int video=0x7f020004; + } + public static final class id { + public static final int LinearLayout01=0x7f060010; + public static final int LinearLayout02=0x7f060006; + public static final int TextView01=0x7f060005; + public static final int TextView02=0x7f06000b; + public static final int TextView03=0x7f060004; + public static final int btStartBoth=0x7f060013; + public static final int btStartListen=0x7f060011; + public static final int btStartSend=0x7f060012; + public static final int cbLoopback=0x7f06000e; + public static final int cbVoice=0x7f06000d; + public static final int etRemoteIp=0x7f06000c; + public static final int ivPreview=0x7f060014; + public static final int ivTopBar=0x7f060002; + public static final int rlSurfaces=0x7f060000; + public static final int spCodecSize=0x7f06000a; + public static final int spCodecType=0x7f060007; + public static final int spVoiceCodecType=0x7f060008; + public static final int svLocal=0x7f060001; + public static final int tvCodecSize=0x7f060009; + public static final int tvLocalIp=0x7f06000f; + public static final int tvTitle=0x7f060003; + } + public static final class layout { + public static final int both=0x7f030000; + public static final int main=0x7f030001; + public static final int send=0x7f030002; + } + public static final class string { + public static final int app_name=0x7f050001; + public static final int codecSize=0x7f050007; + public static final int codecType=0x7f050006; + public static final int codectype_prompt=0x7f050004; + public static final int demoTitle=0x7f050005; + public static final int enableVoice=0x7f05000d; + public static final int error=0x7f050002; + public static final int errorCamera=0x7f050003; + public static final int loopback=0x7f050009; + public static final int remoteIp=0x7f050008; + public static final int startBoth=0x7f05000c; + public static final int startListen=0x7f05000a; + public static final int startSend=0x7f05000b; + } +} diff --git a/video_engine/main/test/AndroidTest/jni/org_webrtc_videoengineapp_vie_android_java_api.h b/video_engine/main/test/AndroidTest/jni/org_webrtc_videoengineapp_vie_android_java_api.h new file mode 100644 index 0000000000..e74b7a7846 --- /dev/null +++ b/video_engine/main/test/AndroidTest/jni/org_webrtc_videoengineapp_vie_android_java_api.h @@ -0,0 +1,442 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_webrtc_videoengineapp_ViEAndroidJavaAPI */ + +#ifndef _Included_org_webrtc_videoengineapp_ViEAndroidJavaAPI +#define _Included_org_webrtc_videoengineapp_ViEAndroidJavaAPI +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: NativeInit + * Signature: (Landroid/content/Context;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_NativeInit + (JNIEnv *, jobject, jobject); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: GetVideoEngine + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_GetVideoEngine + (JNIEnv *, jobject); + + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: Init + * Signature: (IIIZ)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1Init + (JNIEnv *, jobject, jint, jint, jint, jboolean); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: Terminate + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1Terminate + (JNIEnv *, jobject); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: StartSend + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1StartSend + (JNIEnv *, jobject, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: StopRender + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1StopRender + (JNIEnv *, jobject, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: StopSend + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1StopSend + (JNIEnv *, jobject,jint); + + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: StartReceive + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1StartReceive + (JNIEnv *, jobject,jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: StopReceive + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1StopReceive + (JNIEnv *, jobject,jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: CreateChannel + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1CreateChannel + (JNIEnv *, jobject,jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: SetLocalReceiver + * Signature: (II)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1SetLocalReceiver + (JNIEnv *, jobject, jint, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: SetSendDestination + * Signature: (II[B)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1SetSendDestination + (JNIEnv *, jobject, jint, jint, jbyteArray); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: SetReceiveCodec + * Signature: (IIIIIIZ)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1SetReceiveCodec + (JNIEnv *, jobject, jint, jint, jint, jint, jint, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: SetSendCodec + * Signature: (IIIIIIZ)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1SetSendCodec + (JNIEnv *, jobject, jint, jint, jint, jint, jint, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: AddRemoteRenderer + * Signature: (ILandroid/view/SurfaceView;)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1AddRemoteRenderer + (JNIEnv *, jobject, jint, jobject); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: RemoveRemoteRenderer + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1RemoveRemoteRenderer + (JNIEnv *, jobject, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: StartRender + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1StartRender + (JNIEnv *, jobject, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: StartCamera + * Signature: (II)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1StartCamera + (JNIEnv *, jobject,jint channel,jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: StopCamera + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1StopCamera + (JNIEnv *, jobject,jint); + + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: GetCameraOrientation + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1GetCameraOrientation + (JNIEnv *, jobject, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: SetRotation + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1SetRotation + (JNIEnv *, jobject, jint, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: EnableNACK + * Signature: (IZ)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1EnableNACK + (JNIEnv *, jobject, jint, jboolean); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: EnablePLI + * Signature: (IZ)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1EnablePLI + (JNIEnv *, jobject, jint, jboolean); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: StartSendNative + * Signature: (III[BIIIIIILandroid/view/SurfaceView;III)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1StartSendNative + (JNIEnv *, jobject, jint, jint, jint, jbyteArray, jint, jint, jint, jint, jint, jint, jobject, jint, jint, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: StartListenNative + * Signature: (III[BIIIIIIILandroid/view/SurfaceView;)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1StartListenNative + (JNIEnv *, jobject, jint, jint, jint, jbyteArray, jint, jint, jint, jint, jint, jint, jint, jobject); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: StopAllNative + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1StopAllNative + (JNIEnv *, jobject, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: SetCallback + * Signature: (ILorg/webrtc/videoengineapp/IViEAndroidCallback;)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1SetCallback + (JNIEnv *, jobject, jint, jobject); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_Create + * Signature: (Landroid/app/Activity;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1Create + (JNIEnv *, jobject, jobject); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_Delete + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1Delete + (JNIEnv *, jobject); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_Init + * Signature: (IIIZZ)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1Init + (JNIEnv *, jobject, jint, jint, jint, jboolean, jboolean); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_Terminate + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1Terminate + (JNIEnv *, jobject); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_CreateChannel + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1CreateChannel + (JNIEnv *, jobject); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_DeleteChannel + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1DeleteChannel + (JNIEnv *, jobject, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_SetLocalReceiver + * Signature: (II)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1SetLocalReceiver + (JNIEnv *, jobject, jint, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_SetSendDestination + * Signature: (IILjava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1SetSendDestination + (JNIEnv *, jobject, jint, jint, jstring); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_StartListen + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1StartListen + (JNIEnv *, jobject, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_StartPlayout + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1StartPlayout + (JNIEnv *, jobject, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_StartSend + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1StartSend + (JNIEnv *, jobject, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_StopListen + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1StopListen + (JNIEnv *, jobject, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_StopPlayout + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1StopPlayout + (JNIEnv *, jobject, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_StopSend + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1StopSend + (JNIEnv *, jobject, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_SetSpeakerVolume + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1SetSpeakerVolume + (JNIEnv *, jobject, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_SetLoudspeakerStatus + * Signature: (Z)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1SetLoudspeakerStatus + (JNIEnv *, jobject, jboolean); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_StartPlayingFileLocally + * Signature: (ILjava/lang/String;Z)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1StartPlayingFileLocally + (JNIEnv *, jobject, jint, jstring, jboolean); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_StopPlayingFileLocally + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1StopPlayingFileLocally + (JNIEnv *, jobject, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_StartPlayingFileAsMicrophone + * Signature: (ILjava/lang/String;Z)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1StartPlayingFileAsMicrophone + (JNIEnv *, jobject, jint, jstring, jboolean); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_StopPlayingFileAsMicrophone + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1StopPlayingFileAsMicrophone + (JNIEnv *, jobject, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_NumOfCodecs + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1NumOfCodecs + (JNIEnv *, jobject); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_SetSendCodec + * Signature: (II)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1SetSendCodec + (JNIEnv *, jobject, jint, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_SetECStatus + * Signature: (ZI)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1SetECStatus + (JNIEnv *, jobject, jboolean, jint, jint, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_SetNSStatus + * Signature: (ZI)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1SetNSStatus + (JNIEnv *, jobject, jboolean, jint); + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: VE_SetAGCStatus + * Signature: (ZI)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1SetAGCStatus + (JNIEnv *, jobject, jboolean, jint); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/video_engine/main/test/AndroidTest/jni/vie_android_java_api.cc b/video_engine/main/test/AndroidTest/jni/vie_android_java_api.cc new file mode 100644 index 0000000000..b80f9d44cd --- /dev/null +++ b/video_engine/main/test/AndroidTest/jni/vie_android_java_api.cc @@ -0,0 +1,1751 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include + +#include "org_webrtc_videoengineapp_vie_android_java_api.h" + +#include "voe_base.h" +#include "voe_codec.h" +#include "voe_file.h" +#include "voe_network.h" +#include "voe_audio_processing.h" +#include "voe_volume_control.h" +#include "voe_hardware.h" + +#include "vie_base.h" +#include "vie_codec.h" +#include "vie_capture.h" +#include "vie_network.h" +#include "vie_render.h" +#include "vie_rtp_rtcp.h" + +#include "common_types.h" + +#define WEBRTC_LOG_TAG "*WEBRTCN*" +#define VALIDATE_BASE_POINTER \ + if (!veData.base) \ + { \ + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, "Base pointer doesn't exist"); \ + return -1; \ + } +#define VALIDATE_CODEC_POINTER \ + if (!veData.codec) \ + { \ + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, "Codec pointer doesn't exist"); \ + return -1; \ + } +#define VALIDATE_FILE_POINTER \ + if (!veData.file) \ + { \ + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, "File pointer doesn't exist"); \ + return -1; \ + } +#define VALIDATE_APM_POINTER \ + if (!veData.codec) \ + { \ + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, "Apm pointer doesn't exist"); \ + return -1; \ + } +#define VALIDATE_HARDWARE_POINTER \ + if (!veData.hardware) \ + { \ + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, "Hardware pointer doesn't exist"); \ + return -1; \ + } +#define VALIDATE_VOLUME_POINTER \ + if (!veData.volume) \ + { \ + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, "Volume pointer doesn't exist"); \ + return -1; \ + } + +using namespace webrtc; + +//Forward declaration. +class VideoCallbackAndroid; + +// VoiceEngine data struct +typedef struct +{ + // VoiceEngine + VoiceEngine* ve; + // Sub-APIs + VoEBase* base; + VoECodec* codec; + VoEFile* file; + VoENetwork* netw; + VoEAudioProcessing* apm; + VoEVolumeControl* volume; + VoEHardware* hardware; + JavaVM* jvm; +} VoiceEngineData; + +class AndroidVideoRenderCallback; +// VideoEngine data struct +typedef struct +{ + VideoEngine* vie; + ViEBase* base; + ViECodec* codec; + ViENetwork* netw; + ViERTP_RTCP* rtp; + ViERender* render; + ViECapture* capture; + VideoCallbackAndroid* callback; + +} VideoEngineData; + +// Global variables +JavaVM* webrtcGlobalVM; + +// Global variables visible in this file +static VoiceEngineData veData; +static VideoEngineData vieData; + +// "Local" functions (i.e. not Java accessible) +#define WEBRTC_TRACE_MAX_MESSAGE_SIZE 1024 +static bool VE_GetSubApis(); +static bool VE_ReleaseSubApis(); + +#define CHECK_API_RETURN(ret) \ + if (ret!=0) \ + { \ + __android_log_print(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, "Return error %d",ret); \ + break; \ + } + +class VideoCallbackAndroid: public ViEDecoderObserver, + public ViEEncoderObserver +{ + + // Implements ViEDecoderObserver + virtual void IncomingRate(const int videoChannel, + const unsigned int framerate, + const unsigned int bitrate) + { + //Let's print out the network statistics from this call back as well + unsigned short fraction_lost; + unsigned int dummy; + int intdummy; + _vieData.rtp->GetReceivedRTCPStatistics(videoChannel, fraction_lost, + dummy, dummy, dummy, intdummy); + unsigned short packetLossRate = 0; + if (fraction_lost > 0) + { + // Change from frac to % + packetLossRate = (fraction_lost * 100) >> 8; + } + + JNIEnv* threadEnv = NULL; + int ret = webrtcGlobalVM->AttachCurrentThread(&threadEnv, NULL); + // Get the JNI env for this thread + if ((ret < 0) || !threadEnv) + { + __android_log_print(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, + "Could not attach thread to JVM (%d, %p)", ret, + threadEnv); + return; + } + threadEnv->CallIntMethod(_callbackObj, _callbackId, framerate, bitrate, + packetLossRate, _frameRateO, _bitRateO); + webrtcGlobalVM->DetachCurrentThread(); + } + ; + + virtual void IncomingCodecChanged(const int videoChannel, + const webrtc::VideoCodec& videoCodec) + { + } + ; + + virtual void RequestNewKeyFrame(const int videoChannel) + { + } + ; + + virtual void OutgoingRate(const int videoChannel, + const unsigned int framerate, + const unsigned int bitrate) + { + _frameRateO = framerate; + _bitRateO = bitrate; + //__android_log_print(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "SendRate frameRate %d bitrate %d\n",frameRate,bitrate); + } + ; + +public: + VideoEngineData& _vieData; + JNIEnv * _env; + jobject _callbackObj; + jclass _callbackCls; + jmethodID _callbackId; + int _frameRateO, _bitRateO; + VideoCallbackAndroid(VideoEngineData& vieData, JNIEnv * env, + jobject callback) : + _vieData(vieData), _env(env), _callbackObj(callback), _frameRateO(0), + _bitRateO(0) + { + _callbackCls = _env->GetObjectClass(_callbackObj); + _callbackId + = _env->GetMethodID(_callbackCls, "UpdateStats", "(IIIII)I"); + if (_callbackId == NULL) + { + __android_log_print(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Failed to get jid"); + } + _callbackObj = _env->NewGlobalRef(_callbackObj); + } +}; + +////////////////////////////////////////////////////////////////// +// General functions +////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////// +// JNI_OnLoad +jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) +{ + webrtcGlobalVM = vm; + if (!webrtcGlobalVM) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "JNI_OnLoad did not receive a valid VM pointer"); + return -1; + } + + // Get JNI + JNIEnv* env; + if (JNI_OK != vm->GetEnv(reinterpret_cast (&env), JNI_VERSION_1_4)) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "JNI_OnLoad could not get JNI env"); + return -1; + } + + // Init VoiceEngine data + memset(&veData, 0, sizeof(veData)); + // Store the JVM + veData.jvm = vm; + + // Init VideoEngineData data + memset(&vieData, 0, sizeof(vieData)); + + return JNI_VERSION_1_4; +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: NativeInit + * Signature: (Landroid/content/Context;)Z + */ +JNIEXPORT jboolean JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_NativeInit( + JNIEnv * env, + jobject, + jobject context) +{ + + return true; +} + +////////////////////////////////////////////////////////////////// +// VideoEngine API wrapper functions +////////////////////////////////////////////////////////////////// +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: GetVideoEngine + * Signature: ()I + */ +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_GetVideoEngine( + JNIEnv *, + jobject context) +{ + + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "GetVideoEngine"); + + VideoEngine::SetAndroidObjects(webrtcGlobalVM, context); + + // Check if already got + if (vieData.vie) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "ViE already got"); + return -1; + } + + // Create + vieData.vie = VideoEngine::Create(); + if (!vieData.vie) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, "Get ViE failed"); + return -1; + } + vieData.base = ViEBase::GetInterface(vieData.vie); + if (!vieData.base) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Get base sub-API failed"); + return -1; + } + + vieData.codec = ViECodec::GetInterface(vieData.vie); + if (!vieData.codec) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Get codec sub-API failed"); + return -1; + } + + vieData.netw = ViENetwork::GetInterface(vieData.vie); + if (!vieData.netw) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Get network sub-API failed"); + return -1; + } + + vieData.rtp = ViERTP_RTCP::GetInterface(vieData.vie); + if (!vieData.rtp) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Get RTP sub-API failed"); + return -1; + } + + vieData.render = ViERender::GetInterface(vieData.vie); + if (!vieData.render) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Get Render sub-API failed"); + return -1; + } + vieData.capture = ViECapture::GetInterface(vieData.vie); + if (!vieData.capture) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Get Capture sub-API failed"); + return -1; + } + + return 0; +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: Init + * Signature: (IIIZ)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1Init( + JNIEnv *, + jobject, + jboolean enableTrace) +{ + if (vieData.vie) + { + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "Init"); + + int ret = vieData.base->Init(); + __android_log_print(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, + "Init return %d", ret); + if (enableTrace) + { + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, + "SetTraceFile"); + if (0 != vieData.vie->SetTraceFile(("/sdcard/trace.txt"), false)) + { + __android_log_print(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Video Engine could not enable trace"); + } + + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, + "SetTraceFilter"); + if (0 != vieData.vie->SetTraceFilter(webrtc::kTraceDefault)) + { + __android_log_write(ANDROID_LOG_WARN, WEBRTC_LOG_TAG, + "Could not set trace filter"); + } + } + else + { + if (0 != vieData.vie->SetTraceFilter(webrtc::kTraceNone)) + { + __android_log_write(ANDROID_LOG_WARN, WEBRTC_LOG_TAG, + "Could not set trace filter"); + } + } + if (veData.ve) // VoiceEngine is enabled + { + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, + "SetVoiceEngine"); + if (0 != vieData.base->SetVoiceEngine(veData.ve)) + { + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, + "SetVoiceEngine failed"); + } + } + return ret; + } + else + { + return -1; + } +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: Terminate + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1Terminate( + JNIEnv *, + jobject) +{ + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "Terminate"); + + if (vieData.vie) + { + if (!vieData.rtp || vieData.rtp->Release() != 0) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Failed to release RTP sub-API"); + + } + if (!vieData.netw || vieData.netw->Release() != 0) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Failed to release Network sub-API"); + + } + if (!vieData.codec || vieData.codec->Release() != 0) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Failed to release Codec sub-API"); + + } + if (!vieData.render || vieData.render->Release()) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Failed to release Render sub-API"); + } + if (!vieData.capture || vieData.capture->Release()) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Failed to release Capture sub-API"); + } + + if (!vieData.base || vieData.base->Release() != 0) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Failed to release Base sub-API"); + } + // Delete Vie + if (!VideoEngine::Delete(vieData.vie)) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Failed to delete ViE "); + return -1; + } + memset(&vieData, 0, sizeof(vieData)); + VideoEngine::SetAndroidObjects(NULL, NULL); + return 0; + } + else + { + return -1; + } +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: StartSend + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1StartSend( + JNIEnv *, + jobject, + jint channel) +{ + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "StartSend"); + + if (vieData.base) + { + int ret = vieData.base->StartSend(channel); + return ret; + } + else + { + return -1; + } +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: StopRender + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1StopRender( + JNIEnv *, + jobject, + jint channel) +{ + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "StopRender"); + + if (vieData.render) + { + return vieData.render->StopRender(channel); + } + else + { + return -1; + } +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: Stop + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1StopSend( + JNIEnv *, + jobject, + jint channel) +{ + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "StopSend"); + + if (vieData.base) + { + return vieData.base->StopSend(channel); + } + else + { + return -1; + } +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: StartReceive + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1StartReceive( + JNIEnv *, + jobject, + jint channel) +{ + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "StartReceive"); + + if (vieData.base) + { + return vieData.base->StartReceive(channel); + } + else + { + return -1; + } +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: StopReceive + * Signature: ()I + */ + +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1StopReceive( + JNIEnv *, + jobject, + jint channel) +{ + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "StopReceive"); + if (vieData.base) + { + return vieData.base->StopReceive(channel); + } + else + { + return -1; + } +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: CreateChannel + * Signature: ()I + */ +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1CreateChannel( + JNIEnv *, + jobject, + jint voiceChannel) +{ + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "CreateChannel"); + + if (vieData.vie) + { + int channel = 0; + if (vieData.base->CreateChannel(channel) != 0) + { + return -1; + } + if (voiceChannel >= 0) + { + vieData.base->ConnectAudioChannel(channel, voiceChannel); + } + + return channel; + } + else + { + return -1; + } +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: SetLocalReceiver + * Signature: (II)I + */ +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1SetLocalReceiver( + JNIEnv *, + jobject, + jint channel, + jint port) +{ + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "SetLocalReceiver"); + + if (vieData.vie) + { + int ret = vieData.netw->SetLocalReceiver(channel, port); + return ret; + } + else + { + return -1; + } +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: SetSendDestination + * Signature: (II[B)I + */ +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1SetSendDestination( + JNIEnv * env, + jobject, + jint channel, + jint port, + jbyteArray ipadr) +{ + + if (NULL == vieData.vie) + return -1; + + char ip[64]; + jsize len = env->GetArrayLength(ipadr); + if ((len >= 64) || (len == 0)) + return -1; + env->GetByteArrayRegion(ipadr, 0, len, (jbyte*) ip); + ip[len] = '\0'; + + __android_log_print(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, + "SetSendDestination: channel=%d, port=%d, ip=%s\n", + channel, port, ip); + + int ret = vieData.netw->SetSendDestination(channel, ip, port); + return ret; +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: SetReceiveCodec + * Signature: (IIIIII)I + */ +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1SetReceiveCodec( + JNIEnv *, + jobject, + jint channel, + jint codecNum, + jint intbitRate, + jint width, + jint height, + jint frameRate) +{ + if (NULL == vieData.codec) + return -1; + + //Create codec + webrtc::VideoCodec codec; + vieData.codec->GetCodec(codecNum, codec); + + __android_log_print( + ANDROID_LOG_DEBUG, + WEBRTC_LOG_TAG, + "SetReceiveCodec %s, pltype=%d, bitRate=%d, maxBitRate=%d," + " width=%d, height=%d, frameRate=%d, codecSpecific=%d \n", + codec.plName, codec.plType, codec.startBitrate, + codec.maxBitrate, codec.width, codec.height, + codec.maxFramerate, codec.codecSpecific); + int ret = vieData.codec->SetReceiveCodec(channel, codec); + __android_log_print(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, + "SetReceiveCodec return %d", ret); + return ret; +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: SetSendCodec + * Signature: (IIIIII)I + */ +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1SetSendCodec( + JNIEnv *, + jobject, + jint channel, + jint codecNum, + jint intbitRate, + jint width, + jint height, + jint frameRate) +{ + if (NULL == vieData.codec) + return -1; + + //Create codec + webrtc::VideoCodec codec; + vieData.codec->GetCodec(codecNum, codec); + codec.startBitrate = intbitRate; + codec.maxBitrate = 600; + codec.width = width; + codec.height = height; + codec.maxFramerate = frameRate; + + for (int i = 0; i < vieData.codec->NumberOfCodecs(); ++i) + { + webrtc::VideoCodec codecToList; + vieData.codec->GetCodec(i, codecToList); + __android_log_print( + ANDROID_LOG_DEBUG, + WEBRTC_LOG_TAG, + "Codec list %s, pltype=%d, bitRate=%d, maxBitRate=%d," + " width=%d, height=%d, frameRate=%d\n", + codecToList.plName, codecToList.plType, + codecToList.startBitrate, codecToList.maxBitrate, + codecToList.width, codecToList.height, + codecToList.maxFramerate); + } + __android_log_print( + ANDROID_LOG_DEBUG, + WEBRTC_LOG_TAG, + "SetSendCodec %s, pltype=%d, bitRate=%d, maxBitRate=%d, " + "width=%d, height=%d, frameRate=%d\n", + codec.plName, codec.plType, codec.startBitrate, + codec.maxBitrate, codec.width, codec.height, + codec.maxFramerate); + + return vieData.codec->SetSendCodec(channel, codec); +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: AddRemoteRenderer + * Signature: (ILandroid/view/SurfaceView;)I + */ +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1AddRemoteRenderer( + JNIEnv *, + jobject, + jint channel, + jobject glSurface) +{ + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "AddRemoteRenderer"); + if (vieData.vie) + { + return vieData.render->AddRenderer(channel, glSurface, 0, 0, 0, 1, 1); + } + else + { + return -1; + } +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: RemoveRemoteRenderer + * Signature: (I)I + */ +JNIEXPORT jint +JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1RemoveRemoteRenderer( + JNIEnv *, + jobject, + jint channel) +{ + if (vieData.vie) + { + return vieData.render->RemoveRenderer(channel); + } + else + { + return -1; + } +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: StartRender + * Signature: (I)I + */ +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1StartRender( + JNIEnv *, + jobject, + jint channel) +{ + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "StartRender"); + + if (vieData.render) + { + return vieData.render->StartRender(channel); + } + else + { + return -1; + } +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: StarteCamera + * Signature: (II)I + */ +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1StartCamera( + JNIEnv * env, + jobject, + jint channel, + jint cameraNum) +{ + if (NULL == vieData.vie) + return -1; + + int i = 0; + char deviceName[64]; + char deviceUniqueName[64]; + int re; + do + { + re = vieData.capture->GetCaptureDevice(i, deviceName, + sizeof(deviceName), + deviceUniqueName, + sizeof(deviceUniqueName)); + __android_log_print( + ANDROID_LOG_DEBUG, + WEBRTC_LOG_TAG, + "GetCaptureDevice ret %d devicenum %d deviceUniqueName %s", + re, i, deviceUniqueName); + i++; + } while (re == 0); + + int ret; + int cameraId; + vieData.capture->GetCaptureDevice(cameraNum, deviceName, + sizeof(deviceName), deviceUniqueName, + sizeof(deviceUniqueName)); + vieData.capture->AllocateCaptureDevice(deviceUniqueName, + sizeof(deviceUniqueName), cameraId); + + if (cameraId >= 0) + { //Connect the + ret = vieData.capture->ConnectCaptureDevice(cameraId, channel); + __android_log_print(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, + "ConnectCaptureDevice ret %d ", ret); + + ret = vieData.capture->StartCapture(cameraId); + __android_log_print(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, + "StartCapture ret %d ", ret); + + } + + return cameraId; +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: StopCamera + * Signature: ()I + */ +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1StopCamera( + JNIEnv *, + jobject, + jint cameraId) +{ + if (NULL == vieData.capture) + return -1; + + int ret = vieData.capture->StopCapture(cameraId); + __android_log_print(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, + "StopCapture ret %d ", ret); + ret = vieData.capture->ReleaseCaptureDevice(cameraId); + __android_log_print(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, + "ReleaseCaptureDevice ret %d ", ret); + + return ret; +} +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: GetCameraOrientation + * Signature: (I)I + */ +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1GetCameraOrientation( + JNIEnv *, + jobject, + jint cameraNum) +{ + char deviceName[64]; + char deviceUniqueName[64]; + int ret; + + ret = vieData.capture->GetCaptureDevice(cameraNum, deviceName, + sizeof(deviceName), + deviceUniqueName, + sizeof(deviceUniqueName)); + if (ret != 0) + { + return -1; + } + + RotateCapturedFrame orientation; + ret = vieData.capture->GetOrientation(deviceUniqueName, orientation); + __android_log_print(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, + "GetOrientation ret %d orientation %d", ret, + orientation); + + return (jint) orientation; + +} +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: SetRotation + * Signature: (II)I + */ +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1SetRotation( + JNIEnv *, + jobject, + jint captureId, + jint degrees) +{ + + if (NULL == vieData.capture) + return -1; + RotateCapturedFrame rotation = RotateCapturedFrame_0; + if (degrees == 90) + rotation = RotateCapturedFrame_90; + else if (degrees == 180) + rotation = RotateCapturedFrame_180; + else if (degrees == 270) + rotation = RotateCapturedFrame_270; + + int ret = vieData.capture->SetRotateCapturedFrames(captureId, rotation); + return ret; +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: EnableNACK + * Signature: (IZ)I + */ +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1EnableNACK( + JNIEnv *, + jobject, + jint channel, + jboolean enable) +{ + if (NULL == vieData.rtp) + return -1; + + if (enable) + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, + "EnableNACK enable"); + else + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, + "EnableNACK disable"); + + int ret = vieData.rtp->SetNACKStatus(channel, enable); + return ret; +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: EnablePLI + * Signature: (IZ)I + */ +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1EnablePLI( + JNIEnv *, + jobject, + jint channel, + jboolean enable) +{ + if (NULL == vieData.rtp) + return -1; + + if (enable) + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, + "EnablePLI enable"); + else + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, + "EnablePLI disable"); + + int ret = vieData.rtp->SetKeyFrameRequestMethod(channel, + kViEKeyFrameRequestPliRtcp); + return ret; +} + +/* + * Class: org_webrtc_videoengineapp_ViEAndroidJavaAPI + * Method: SetCallback + * Signature: (ILorg/webrtc/videoengineapp/IViEAndroidCallback;)I + */ +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_1SetCallback( + JNIEnv * env, + jobject, + jint channel, + jobject callback) +{ + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "SetCallback"); + + if (NULL == vieData.codec) + return -1; + if (vieData.callback == NULL) + { + vieData.callback = new VideoCallbackAndroid(vieData, env, callback); + } + else if (vieData.codec) + { + vieData.codec->DeregisterDecoderObserver(channel); // Wrong channel? + vieData.codec->DeregisterEncoderObserver(channel); + } + + vieData.codec->RegisterDecoderObserver(channel, *vieData.callback); + vieData.codec->RegisterEncoderObserver(channel, *vieData.callback); + + return 0; +} +////////////////////////////////////////////////////////////////// +// VoiceEngine API wrapper functions +////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////// +// Create VoiceEngine instance +// +JNIEXPORT jboolean JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1Create( + JNIEnv *env, + jobject, + jobject context) +{ + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "Create"); + + // Check if already created + if (veData.ve) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "VoE already created"); + return false; + } + + // Init Android Object + VoiceEngine::SetAndroidObjects(veData.jvm, env, context); + // Create + veData.ve = VoiceEngine::Create(); + if (!veData.ve) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Create VoE failed"); + return false; + } + + // Get sub-APIs + if (!VE_GetSubApis()) + { + // If not OK, release all sub-APIs and delete VoE + VE_ReleaseSubApis(); + if (!VoiceEngine::Delete(veData.ve)) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Delete VoE failed"); + } + return false; + } + + return true; +} + +///////////////////////////////////////////// +// Delete VoiceEngine instance +// +JNIEXPORT jboolean JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1Delete( + JNIEnv *, + jobject) +{ + // Check if exists + if (!veData.ve) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "VoE does not exist"); + return false; + } + + // Release sub-APIs + VE_ReleaseSubApis(); + + // Delete + if (!VoiceEngine::Delete(veData.ve)) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Delete VoE failed"); + return false; + } + + veData.ve = NULL; + + // Clear instance independent Java objects + VoiceEngine::SetAndroidObjects(NULL, NULL, NULL); + + return true; +} + +///////////////////////////////////////////// +// [Base] Initialize VoiceEngine +// +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1Init( + JNIEnv *, + jobject, + jboolean enableTrace, + jboolean useExtTrans) +{ + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "VE_Init"); + + VALIDATE_BASE_POINTER; + + if (useExtTrans) + { + // Not implemented + return -1; + } + + return veData.base->Init(); +} + +///////////////////////////////////////////// +// [Base] Terminate VoiceEngine +// +JNIEXPORT jint +JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1Terminate( + JNIEnv *, + jobject) +{ + VALIDATE_BASE_POINTER; + + jint retVal = veData.base->Terminate(); + return retVal; +} + +///////////////////////////////////////////// +// [Base] Create channel +// +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1CreateChannel( + JNIEnv *, + jobject) +{ + VALIDATE_BASE_POINTER; + + webrtc::CodecInst voiceCodec; + int numOfVeCodecs = veData.codec->NumOfCodecs(); + + //enum all the supported codec + __android_log_print(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, + "Supported Voice Codec:\n"); + for (int i = 0; i < numOfVeCodecs; ++i) + { + if (veData.codec->GetCodec(i, voiceCodec) != -1) + { + __android_log_print(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, + "num: %d name: %s\n", i, voiceCodec.plname); + } + } + + jint channel = veData.base->CreateChannel(); + + return channel; +} + +///////////////////////////////////////////// +// [Base] Delete channel +// +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1DeleteChannel( + JNIEnv *, + jobject, + jint channel) + { + VALIDATE_BASE_POINTER; + return veData.base->DeleteChannel(channel); +} + +///////////////////////////////////////////// +// [Base] SetLocalReceiver +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1SetLocalReceiver( + JNIEnv *, + jobject, + jint channel, + jint port) +{ + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "SetLocalReceiver"); + VALIDATE_BASE_POINTER; + return veData.base->SetLocalReceiver(channel, port); +} + +///////////////////////////////////////////// +// [Base] SetSendDestination +// +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1SetSendDestination( + JNIEnv *env, + jobject, + jint channel, + jint port, + jstring ipaddr) +{ + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "SetSendDestination"); + VALIDATE_BASE_POINTER; + + const char* ipaddrNative = env->GetStringUTFChars(ipaddr, NULL); + if (!ipaddrNative) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Could not get UTF string"); + return -1; + } + jint retVal = veData.base->SetSendDestination(channel, port, ipaddrNative); + env->ReleaseStringUTFChars(ipaddr, ipaddrNative); + return retVal; +} + +///////////////////////////////////////////// +// [Base] StartListen +// +JNIEXPORT jint JNICALL Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1StartListen( + JNIEnv *, + jobject, + jint channel) +{ + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "StartListen"); + VALIDATE_BASE_POINTER; + return veData.base->StartReceive(channel); +} + +///////////////////////////////////////////// +// [Base] Start playout +// +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1StartPlayout( + JNIEnv *, + jobject, + jint channel) +{ + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "StartPlayout"); + VALIDATE_BASE_POINTER; + return veData.base->StartPlayout(channel); +} + +///////////////////////////////////////////// +// [Base] Start send +// +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1StartSend( + JNIEnv *, + jobject, + jint channel) +{ + __android_log_write(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "StartSend"); + VALIDATE_BASE_POINTER; + return veData.base->StartSend(channel); +} + +///////////////////////////////////////////// +// [Base] Stop listen +// +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1StopListen( + JNIEnv *, + jobject, + jint channel) +{ + VALIDATE_BASE_POINTER; + return veData.base->StartReceive(channel); +} + +///////////////////////////////////////////// +// [Base] Stop playout +// +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1StopPlayout( + JNIEnv *, + jobject, + jint channel) +{ + VALIDATE_BASE_POINTER; + return veData.base->StopPlayout(channel); +} + +///////////////////////////////////////////// +// [Base] Stop send +// +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1StopSend( + JNIEnv *, + jobject, + jint channel) +{ + VALIDATE_BASE_POINTER; + return veData.base->StopSend(channel); +} + +///////////////////////////////////////////// +// [codec] Number of codecs +// +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1NumOfCodecs( + JNIEnv *, + jobject) +{ + VALIDATE_CODEC_POINTER; + return veData.codec->NumOfCodecs(); +} + +///////////////////////////////////////////// +// [codec] Set send codec +// +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1SetSendCodec( + JNIEnv *, + jobject, + jint channel, + jint index) +{ + VALIDATE_CODEC_POINTER; + + webrtc::CodecInst codec; + + for (int i = 0; i < veData.codec->NumOfCodecs(); ++i) + { + webrtc::CodecInst codecToList; + veData.codec->GetCodec(i, codecToList); + __android_log_print(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, + "VE Codec list %s, pltype=%d\n", + codecToList.plname, codecToList.pltype); + } + + if (veData.codec->GetCodec(index, codec) != 0) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Failed to get codec"); + return -1; + } + __android_log_print(ANDROID_LOG_DEBUG, WEBRTC_LOG_TAG, "SetSendCodec %s\n", + codec.plname); + + return veData.codec->SetSendCodec(channel, codec); +} + +///////////////////////////////////////////// +// [audioprocessing] SetNSStatus +// +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1SetNSStatus( + JNIEnv *, + jobject, + jboolean enable, + jint mode) +{ + //TODO implement + return -1; +} + +///////////////////////////////////////////// +// [audioprocessing] SetAGCStatus +// +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1SetAGCStatus( + JNIEnv *, + jobject, + jboolean enable, + jint mode) +{ + //TODO implement + return -1; +} + +///////////////////////////////////////////// +// [audioprocessing] SetECStatus +// +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1SetECStatus( + JNIEnv *, + jobject, + jboolean enable, + jint mode, + jint AESmode, + jint AESattenuation) +{ + //TODO implement + return -1; +} + +///////////////////////////////////////////// +// [File] Start play file locally +// +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1StartPlayingFileLocally( + JNIEnv * env, + jobject, + jint channel, + jstring fileName, + jboolean loop) +{ + VALIDATE_FILE_POINTER; + + const char* fileNameNative = env->GetStringUTFChars(fileName, NULL); + if (!fileNameNative) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Could not get UTF string"); + return -1; + } + + jint retVal = veData.file->StartPlayingFileLocally(channel, fileNameNative, + loop); + + env->ReleaseStringUTFChars(fileName, fileNameNative); + + return retVal; +} + +///////////////////////////////////////////// +// [File] Stop play file locally +// +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_SVE_1StopPlayingFileLocally( + JNIEnv *, + jobject, + jint channel) +{ + VALIDATE_FILE_POINTER; + return veData.file->StopPlayingFileLocally(channel); +} + +///////////////////////////////////////////// +// [File] Start playing file as microphone +// +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1StartPlayingFileAsMicrophone( + JNIEnv *env, + jobject, + jint channel, + jstring fileName, + jboolean loop) +{ + VALIDATE_FILE_POINTER; + + const char* fileNameNative = env->GetStringUTFChars(fileName, NULL); + if (!fileNameNative) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Could not get UTF string"); + return -1; + } + + jint retVal = veData.file->StartPlayingFileAsMicrophone(channel, + fileNameNative, + loop); + + env->ReleaseStringUTFChars(fileName, fileNameNative); + + return retVal; +} + +///////////////////////////////////////////// +// [File] Stop playing file as microphone +// +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1StopPlayingFileAsMicrophone( + JNIEnv *, + jobject, + jint channel) +{ + VALIDATE_FILE_POINTER; + return veData.file->StopPlayingFileAsMicrophone(channel); +} + +///////////////////////////////////////////// +// [Volume] Set speaker volume +// +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1SetSpeakerVolume( + JNIEnv *, + jobject, + jint level) +{ + VALIDATE_VOLUME_POINTER; + + if (veData.volume->SetSpeakerVolume(level) != 0) + { + return -1; + } + + unsigned int storedVolume = 0; + if (veData.volume->GetSpeakerVolume(storedVolume) != 0) + { + return -1; + } + + if (storedVolume != level) + { + return -1; + } + + return 0; +} + +///////////////////////////////////////////// +// [Hardware] Set speaker volume +// +JNIEXPORT jint JNICALL +Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_VE_1SetLoudspeakerStatus( + JNIEnv *, + jobject, + jboolean enable) +{ + VALIDATE_HARDWARE_POINTER; + + if (veData.hardware->SetLoudspeakerStatus(enable) != 0) + { + return -1; + } + + return 0; +} + +////////////////////////////////////////////////////////////////// +//local function +////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////// +// Get all sub-APIs +// +bool VE_GetSubApis() +{ + bool getOK = true; + + // Base + veData.base = VoEBase::GetInterface(veData.ve); + if (!veData.base) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Get base sub-API failed"); + getOK = false; + } + + // Codec + veData.codec = VoECodec::GetInterface(veData.ve); + if (!veData.codec) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Get codec sub-API failed"); + getOK = false; + } + + // File + veData.file = VoEFile::GetInterface(veData.ve); + if (!veData.file) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Get file sub-API failed"); + getOK = false; + } + + // Network + veData.netw = VoENetwork::GetInterface(veData.ve); + if (!veData.netw) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Get network sub-API failed"); + getOK = false; + } + + // audioprocessing + veData.apm = VoEAudioProcessing::GetInterface(veData.ve); + if (!veData.apm) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Get VoEAudioProcessing sub-API failed"); + getOK = false; + } + + // Volume + veData.volume = VoEVolumeControl::GetInterface(veData.ve); + if (!veData.volume) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Get volume sub-API failed"); + getOK = false; + } + + // Hardware + veData.hardware = VoEHardware::GetInterface(veData.ve); + if (!veData.hardware) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Get hardware sub-API failed"); + getOK = false; + } + + return getOK; +} + +///////////////////////////////////////////// +// Release all sub-APIs +// +bool VE_ReleaseSubApis() +{ + bool releaseOK = true; + + // Base + if (veData.base) + { + if (0 != veData.base->Release()) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Release base sub-API failed"); + releaseOK = false; + } + else + { + veData.base = NULL; + } + } + + // Codec + if (veData.codec) + { + if (0 != veData.codec->Release()) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Release codec sub-API failed"); + releaseOK = false; + } + else + { + veData.codec = NULL; + } + } + + // File + if (veData.file) + { + if (0 != veData.file->Release()) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Release file sub-API failed"); + releaseOK = false; + } + else + { + veData.file = NULL; + } + } + + // Network + if (veData.netw) + { + if (0 != veData.netw->Release()) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Release network sub-API failed"); + releaseOK = false; + } + else + { + veData.netw = NULL; + } + } + + // apm + if (veData.apm) + { + if (0 != veData.apm->Release()) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Release apm sub-API failed"); + releaseOK = false; + } + else + { + veData.apm = NULL; + } + } + + // Volume + if (veData.volume) + { + if (0 != veData.volume->Release()) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Release volume sub-API failed"); + releaseOK = false; + } + else + { + veData.volume = NULL; + } + } + + // Hardware + if (veData.hardware) + { + if (0 != veData.hardware->Release()) + { + __android_log_write(ANDROID_LOG_ERROR, WEBRTC_LOG_TAG, + "Release hardware sub-API failed"); + releaseOK = false; + } + else + { + veData.hardware = NULL; + } + } + + return releaseOK; +} diff --git a/video_engine/main/test/AndroidTest/res/drawable/logo.png b/video_engine/main/test/AndroidTest/res/drawable/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c3e0a123b58538d1a3be00a3ae71be48f56c3553 GIT binary patch literal 409 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1SE5RJ;(=AjKx9jP7LeL$-D$|*pj^6T^Rm@ z;DWu&Cj&(|3p^r=85p>QL70(Y)*K0-AbW|YuPgg)W;rfX)#Wey#et?sdb&7sD!b&0eOlsQ2?fwlJM}$0`oAz1sWy%bM3wp{FLWSCk&TZq~ZOvl{6`#NWaunDd3)&eg@Bd&92<<(0 zCBJ@tML6r#=kqg;O + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/video_engine/main/test/AndroidTest/res/layout/both.xml b/video_engine/main/test/AndroidTest/res/layout/both.xml new file mode 100644 index 0000000000..d29d9063fc --- /dev/null +++ b/video_engine/main/test/AndroidTest/res/layout/both.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + diff --git a/video_engine/main/test/AutoTest/Android/res/values/strings.xml b/video_engine/main/test/AutoTest/Android/res/values/strings.xml new file mode 100644 index 0000000000..b06ef52734 --- /dev/null +++ b/video_engine/main/test/AutoTest/Android/res/values/strings.xml @@ -0,0 +1,31 @@ + + + +ViEAutotest +ViE Autotest Android +Run Test +Test type... + + Standard + API + Extended + Loopback + Custom + +Run... + + All + Base + Capture + Codec + Mix + Encryption + External Codec + File + Image Process + Network + Render + RTP/RTCP + + + diff --git a/video_engine/main/test/AutoTest/Android/src/org/webrtc/vieautotest/ViEAutotest.java b/video_engine/main/test/AutoTest/Android/src/org/webrtc/vieautotest/ViEAutotest.java new file mode 100644 index 0000000000..dc57e38f2a --- /dev/null +++ b/video_engine/main/test/AutoTest/Android/src/org/webrtc/vieautotest/ViEAutotest.java @@ -0,0 +1,159 @@ +package org.webrtc.vieautotest; + +import org.webrtc.vieautotest.R; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.widget.Button; +import android.view.SurfaceView; +import android.view.View; +import android.view.SurfaceHolder; +import android.widget.LinearLayout; +import android.opengl.GLSurfaceView; +import android.widget.Spinner; +import android.widget.ArrayAdapter; +import android.widget.AdapterView; + + +public class ViEAutotest extends Activity + implements + AdapterView.OnItemSelectedListener, + View.OnClickListener { + + private Thread _testThread; + private Spinner _testSpinner; + private Spinner _subtestSpinner; + private int _testSelection; + private int _subTestSelection; + + // View for remote video + private LinearLayout _remoteSurface = null; + private GLSurfaceView _glSurfaceView = null; + private SurfaceView _surfaceView = null; + + private LinearLayout _localSurface = null; + private GLSurfaceView _glLocalSurfaceView = null; + private SurfaceView _localSurfaceView = null; + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + + Log.d("*WEBRTC*", "onCreate called"); + + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + + // Set the Start button action + final Button buttonStart = (Button) findViewById(R.id.Button01); + buttonStart.setOnClickListener(this); + + // Set test spinner + _testSpinner = (Spinner) findViewById(R.id.testSpinner); + ArrayAdapter adapter = + ArrayAdapter.createFromResource(this, R.array.test_array, + android.R.layout.simple_spinner_item); + + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + _testSpinner.setAdapter(adapter); + _testSpinner.setOnItemSelectedListener(this); + + + // Set sub test spinner + _subtestSpinner = (Spinner) findViewById(R.id.subtestSpinner); + ArrayAdapter subtestAdapter = + ArrayAdapter.createFromResource(this, R.array.subtest_array, + android.R.layout.simple_spinner_item); + + subtestAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + _subtestSpinner.setAdapter(subtestAdapter); + _subtestSpinner.setOnItemSelectedListener(this); + + _remoteSurface = (LinearLayout) findViewById(R.id.RemoteView); + _surfaceView = new SurfaceView(this); + _remoteSurface.addView(_surfaceView); + + _localSurface = (LinearLayout) findViewById(R.id.LocalView); + _localSurfaceView = new SurfaceView(this); + _localSurfaceView.setZOrderMediaOverlay(true); + _localSurface.addView(_localSurfaceView); + + + // Set members + _testSelection = 0; + _subTestSelection = 0; + } + + public void onClick(View v) { + Log.d("*WEBRTC*", "Button clicked..."); + switch (v.getId()) { + case R.id.Button01: + + + new Thread(new Runnable() { + public void run() { + // + Log.d("*WEBRTC*", "Calling RunTest..."); + RunTest(_testSelection, _subTestSelection, _localSurfaceView, _surfaceView);// + Log.d("*WEBRTC*", "RunTest done"); + } + }).start(); + } + }; + + public void onItemSelected(AdapterView parent, View v, int position, long id) { + + if (parent == (Spinner) findViewById(R.id.testSpinner)) { + _testSelection = position; + } else { + _subTestSelection = position; + } + } + + public void onNothingSelected(AdapterView parent) { + ; + } + + @Override + protected void onStart() { + super.onStart(); + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + } + + @Override + protected void onStop() { + super.onStop(); + } + + @Override + protected void onDestroy() { + + super.onDestroy(); + } + + // C++ function performing the chosen test + // private native int RunTest(int testSelection, int subtestSelection, + // GLSurfaceView window1, GLSurfaceView window2); + private native int RunTest(int testSelection, int subtestSelection, SurfaceView window1, + SurfaceView window2); + + + /* + * this is used to load the 'ViEAutotestJNIAPI' library on application + * startup. + */ + static { + Log.d("*WEBRTC*", "Loading ViEAutotest..."); + System.loadLibrary("ViEAutotestJNIAPI"); + } +} diff --git a/video_engine/main/test/AutoTest/interface/tb_I420_codec.h b/video_engine/main/test/AutoTest/interface/tb_I420_codec.h new file mode 100644 index 0000000000..62559d4e59 --- /dev/null +++ b/video_engine/main/test/AutoTest/interface/tb_I420_codec.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * tb_I420_codec.h + * + * This file contains the interface to I420 "codec" + * This is a dummy wrapper to allow VCM deal with raw I420 sequences + * + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_TB_I420_CODEC_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_TB_I420_CODEC_H_ + +#include "video_codec_interface.h" + +class tbI420Encoder: public webrtc::VideoEncoder +{ +public: + + tbI420Encoder(); + + virtual ~tbI420Encoder(); + + static WebRtc_Word32 VersionStatic(WebRtc_Word8* version, + WebRtc_Word32 length); + virtual WebRtc_Word32 Version(WebRtc_Word8 *version, + WebRtc_Word32 length) const; + + virtual WebRtc_Word32 InitEncode(const webrtc::VideoCodec* codecSettings, + WebRtc_Word32 numberOfCores, + WebRtc_UWord32 maxPayloadSize); + + virtual WebRtc_Word32 Encode(const webrtc::RawImage& inputImage, + const void* codecSpecificInfo = NULL, + webrtc::VideoFrameType frameType = + webrtc::kDeltaFrame); + + virtual WebRtc_Word32 RegisterEncodeCompleteCallback( + webrtc::EncodedImageCallback* callback); + + virtual WebRtc_Word32 Release(); + + virtual WebRtc_Word32 Reset(); + + virtual WebRtc_Word32 SetPacketLoss(WebRtc_UWord32 packetLoss); + + virtual WebRtc_Word32 SetRates(WebRtc_UWord32 newBitRate, + WebRtc_UWord32 frameRate); + + virtual WebRtc_Word32 SetPeriodicKeyFrames(bool enable); + + virtual WebRtc_Word32 CodecConfigParameters(WebRtc_UWord8* /*buffer*/, + WebRtc_Word32 /*size*/); + + struct FunctionCalls + { + WebRtc_Word32 InitEncode; + WebRtc_Word32 Encode; + WebRtc_Word32 RegisterEncodeCompleteCallback; + WebRtc_Word32 Release; + WebRtc_Word32 Reset; + WebRtc_Word32 SetRates; + WebRtc_Word32 SetPacketLoss; + WebRtc_Word32 SetPeriodicKeyFrames; + WebRtc_Word32 CodecConfigParameters; + + }; + + FunctionCalls GetFunctionCalls(); +private: + bool _inited; + webrtc::EncodedImage _encodedImage; + FunctionCalls _functionCalls; + webrtc::EncodedImageCallback* _encodedCompleteCallback; + +}; // end of tbI420Encoder class + + +/***************************/ +/* tbI420Decoder class */ +/***************************/ + +class tbI420Decoder: public webrtc::VideoDecoder +{ +public: + + tbI420Decoder(); + + virtual ~tbI420Decoder(); + + virtual WebRtc_Word32 InitDecode(const webrtc::VideoCodec* inst, + WebRtc_Word32 numberOfCores); + virtual WebRtc_Word32 Decode(const webrtc::EncodedImage& inputImage, + bool missingFrames, + const void* codecSpecificInfo = NULL, + WebRtc_Word64 renderTimeMs = -1); + virtual WebRtc_Word32 + RegisterDecodeCompleteCallback(webrtc::DecodedImageCallback* callback); + virtual WebRtc_Word32 Release(); + virtual WebRtc_Word32 Reset(); + + struct FunctionCalls + { + WebRtc_Word32 InitDecode; + WebRtc_Word32 Decode; + WebRtc_Word32 RegisterDecodeCompleteCallback; + WebRtc_Word32 Release; + WebRtc_Word32 Reset; + }; + + FunctionCalls GetFunctionCalls(); + +private: + + webrtc::RawImage _decodedImage; + WebRtc_Word32 _width; + WebRtc_Word32 _height; + bool _inited; + FunctionCalls _functionCalls; + webrtc::DecodedImageCallback* _decodeCompleteCallback; + +}; // end of tbI420Decoder class + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_TB_I420_CODEC_H_ diff --git a/video_engine/main/test/AutoTest/interface/tb_capture_device.h b/video_engine/main/test/AutoTest/interface/tb_capture_device.h new file mode 100644 index 0000000000..44aa961de4 --- /dev/null +++ b/video_engine/main/test/AutoTest/interface/tb_capture_device.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_TB_CAPTURE_DEVICE_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_TB_CAPTURE_DEVICE_H_ + +#include "tb_interfaces.h" +#include "video_capture.h" + +class tbCaptureDevice +{ +public: + tbCaptureDevice(tbInterfaces& Engine, int& nrOfErrors); + ~tbCaptureDevice(void); + + int captureId; + void ConnectTo(int videoChannel); + void Disconnect(int videoChannel); +private: + int& numberOfErrors; + tbInterfaces& ViE; + webrtc::VideoCaptureModule* vcpm_; +}; + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_TB_CAPTURE_DEVICE_H_ diff --git a/video_engine/main/test/AutoTest/interface/tb_external_transport.h b/video_engine/main/test/AutoTest/interface/tb_external_transport.h new file mode 100644 index 0000000000..a2f56af1c8 --- /dev/null +++ b/video_engine/main/test/AutoTest/interface/tb_external_transport.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// +// tb_external_transport.h +// + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_TB_EXTERNAL_TRANSPORT_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_TB_EXTERNAL_TRANSPORT_H_ + +#include "common_types.h" +#include "list_wrapper.h" + +namespace webrtc +{ +class CriticalSectionWrapper; +class EventWrapper; +class ThreadWrapper; +class ViENetwork; +} + +class tbExternalTransport: public webrtc::Transport +{ +public: + tbExternalTransport(webrtc::ViENetwork& vieNetwork); + ~tbExternalTransport(void); + + virtual int SendPacket(int channel, const void *data, int len); + virtual int SendRTCPPacket(int channel, const void *data, int len); + + WebRtc_Word32 SetPacketLoss(WebRtc_Word32 lossRate); // Rate in % + void SetNetworkDelay(WebRtc_Word64 delayMs); + + void ClearStats(); + void GetStats(WebRtc_Word32& numRtpPackets, + WebRtc_Word32& numDroppedPackets, + WebRtc_Word32& numRtcpPackets); + + void EnableSSRCCheck(); + unsigned int ReceivedSSRC(); + + void EnableSequenceNumberCheck(); + unsigned short GetFirstSequenceNumber(); + +protected: + static bool ViEExternalTransportRun(void* object); + bool ViEExternalTransportProcess(); +private: + WebRtc_Word64 NowMs(); + + enum + { + KMaxPacketSize = 1650 + }; + enum + { + KMaxWaitTimeMs = 100 + }; + typedef struct + { + WebRtc_Word8 packetBuffer[KMaxPacketSize]; + WebRtc_Word32 length; + WebRtc_Word32 channel; + WebRtc_Word64 receiveTime; + } VideoPacket; + + webrtc::ViENetwork& _vieNetwork; + webrtc::ThreadWrapper& _thread; + webrtc::EventWrapper& _event; + webrtc::CriticalSectionWrapper& _crit; + webrtc::CriticalSectionWrapper& _statCrit; + + WebRtc_Word32 _lossRate; + WebRtc_Word64 _networkDelayMs; + WebRtc_Word32 _rtpCount; + WebRtc_Word32 _rtcpCount; + WebRtc_Word32 _dropCount; + + webrtc::ListWrapper _rtpPackets; + webrtc::ListWrapper _rtcpPackets; + + bool _checkSSRC; + WebRtc_UWord32 _lastSSRC; + bool _checkSequenceNumber; + WebRtc_UWord16 _firstSequenceNumber; +}; + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_TB_EXTERNAL_TRANSPORT_H_ diff --git a/video_engine/main/test/AutoTest/interface/tb_interfaces.h b/video_engine/main/test/AutoTest/interface/tb_interfaces.h new file mode 100644 index 0000000000..e03781593f --- /dev/null +++ b/video_engine/main/test/AutoTest/interface/tb_interfaces.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_TB_INTERFACES_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_TB_INTERFACES_H_ + +#include "vie_autotest_defines.h" + +#include "common_types.h" +#include "vie_base.h" +#include "vie_capture.h" +#include "vie_codec.h" +#include "vie_image_process.h" +#include "vie_network.h" +#include "vie_render.h" +#include "vie_rtp_rtcp.h" +#include "vie_encryption.h" +#include "vie_defines.h" + +//using namespace webrtc; + +class tbInterfaces +{ +public: + tbInterfaces(const char* testName, int& nrOfErrors); + + ~tbInterfaces(void); + webrtc::VideoEngine* ptrViE; + webrtc::ViEBase* ptrViEBase; + webrtc::ViECapture* ptrViECapture; + webrtc::ViERender* ptrViERender; + webrtc::ViERTP_RTCP* ptrViERtpRtcp; + webrtc::ViECodec* ptrViECodec; + webrtc::ViENetwork* ptrViENetwork; + webrtc::ViEImageProcess* ptrViEImageProcess; + webrtc::ViEEncryption* ptrViEEncryption; + + int LastError() + { + return ptrViEBase->LastError(); + } + +private: + int& numberOfErrors; +}; + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_TB_INTERFACES_H_ diff --git a/video_engine/main/test/AutoTest/interface/tb_video_channel.h b/video_engine/main/test/AutoTest/interface/tb_video_channel.h new file mode 100644 index 0000000000..5c641444ab --- /dev/null +++ b/video_engine/main/test/AutoTest/interface/tb_video_channel.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_TB_VIDEO_CHANNEL_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_TB_VIDEO_CHANNEL_H_ + +#include "tb_interfaces.h" +class tbVideoChannel +{ +public: + tbVideoChannel(tbInterfaces& Engine, int& nrOfErrors, + webrtc::VideoCodecType sendCodec = webrtc::kVideoCodecVP8, + int width = 352, int height = 288, int frameRate = 30, + int startBitrate = 300); + + ~tbVideoChannel(void); + + void SetFrameSettings(int width, int height, int frameRate); + + void StartSend(const unsigned short rtpPort = 11000, + const char* ipAddress = "127.0.0.1"); + + void StopSend(); + + void StartReceive(const unsigned short rtpPort = 11000); + + void StopReceive(); + + int videoChannel; +private: + int& numberOfErrors; + tbInterfaces& ViE; +}; + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_TB_VIDEO_CHANNEL_H_ diff --git a/video_engine/main/test/AutoTest/interface/vie_autotest.h b/video_engine/main/test/AutoTest/interface/vie_autotest.h new file mode 100644 index 0000000000..96d2535a95 --- /dev/null +++ b/video_engine/main/test/AutoTest/interface/vie_autotest.h @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// +// vie_autotest.h +// + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_H_ + +#include "common_types.h" + +#include "voe_base.h" +#include "voe_codec.h" +#include "voe_hardware.h" +#include "voe_audio_processing.h" + +#include "vie_base.h" +#include "vie_capture.h" +#include "vie_codec.h" +#include "vie_file.h" +#include "vie_network.h" +#include "vie_render.h" +#include "vie_rtp_rtcp.h" +#include "vie_defines.h" +#include "vie_errors.h" +#include "video_render_defines.h" + +#ifndef ANDROID +#include +#endif + +using namespace webrtc; + +class ViEAutoTest +{ +public: + ViEAutoTest(void* window1, void* window2); + ~ViEAutoTest(); + + int ViEStandardTest(); + int ViEExtendedTest(); + int ViEAPITest(); + int ViELoopbackCall(); + + // custom call and helper functions + int ViECustomCall(); + + // general settings functions + bool GetVideoDevice(ViEBase* ptrViEBase, ViECapture* ptrViECapture, + char* captureDeviceName, char* captureDeviceUniqueId); + bool GetIPAddress(char* IP); +#ifndef ANDROID + bool ValidateIP(std::string iStr); +#endif + void PrintCallInformation(char* IP, char* videoCaptureDeviceName, + char* videoCaptureUniqueId, + webrtc::VideoCodec videoCodec, int videoTxPort, + int videoRxPort, char* audioCaptureDeviceName, + char* audioPlaybackDeviceName, + webrtc::CodecInst audioCodec, int audioTxPort, + int audioRxPort); + + // video settings functions + bool GetVideoPorts(int* txPort, int* rxPort); + bool GetVideoCodec(ViECodec* ptrViECodec, webrtc::VideoCodec& videoCodec); + + // audio settings functions + bool GetAudioDevices(VoEBase* ptrVEBase, VoEHardware* ptrVEHardware, + char* recordingDeviceName, int& recordingDeviceIndex, + char* playbackDeviceName, int& playbackDeviceIndex); + bool GetAudioDevices(VoEBase* ptrVEBase, VoEHardware* ptrVEHardware, + int& recordingDeviceIndex, int& playbackDeviceIndex); + bool GetAudioPorts(int* txPort, int* rxPort); + bool GetAudioCodec(VoECodec* ptrVeCodec, CodecInst& audioCodec); + + // vie_autotest_base.cc + int ViEBaseStandardTest(); + int ViEBaseExtendedTest(); + int ViEBaseAPITest(); + + // vie_autotest_capture.cc + int ViECaptureStandardTest(); + int ViECaptureExtendedTest(); + int ViECaptureAPITest(); + int ViECaptureExternalCaptureTest(); + + // vie_autotest_codec.cc + int ViECodecStandardTest(); + int ViECodecExtendedTest(); + int ViECodecExternalCodecTest(); + int ViECodecAPITest(); + + // vie_autotest_encryption.cc + int ViEEncryptionStandardTest(); + int ViEEncryptionExtendedTest(); + int ViEEncryptionAPITest(); + + // vie_autotest_file.ccs + int ViEFileStandardTest(); + int ViEFileExtendedTest(); + int ViEFileAPITest(); + + // vie_autotest_image_process.cc + int ViEImageProcessStandardTest(); + int ViEImageProcessExtendedTest(); + int ViEImageProcessAPITest(); + + // vie_autotest_network.cc + int ViENetworkStandardTest(); + int ViENetworkExtendedTest(); + int ViENetworkAPITest(); + + // vie_autotest_render.cc + int ViERenderStandardTest(); + int ViERenderExtendedTest(); + int ViERenderAPITest(); + + // vie_autotest_rtp_rtcp.cc + int ViERtpRtcpStandardTest(); + int ViERtpRtcpExtendedTest(); + int ViERtpRtcpAPITest(); + +private: + void PrintAudioCodec(const webrtc::CodecInst audioCodec); + void PrintVideoCodec(const webrtc::VideoCodec videoCodec); + + void* _window1; + void* _window2; + + VideoRenderType _renderType; + VideoRender* _vrm1; + VideoRender* _vrm2; +}; + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_H_ diff --git a/video_engine/main/test/AutoTest/interface/vie_autotest_android.h b/video_engine/main/test/AutoTest/interface/vie_autotest_android.h new file mode 100644 index 0000000000..53b8cc31bf --- /dev/null +++ b/video_engine/main/test/AutoTest/interface/vie_autotest_android.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_ANDROID_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_ANDROID_H_ + +class ViEAutoTestAndroid +{ +public: + static int RunAutotest(int testSelection, + int subTestSelection, + void* window1, + void* window2, + void* javaVM, + void* env, + void* context); +}; + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_ANDROID_H_ diff --git a/video_engine/main/test/AutoTest/interface/vie_autotest_defines.h b/video_engine/main/test/AutoTest/interface/vie_autotest_defines.h new file mode 100644 index 0000000000..b8ff7eae92 --- /dev/null +++ b/video_engine/main/test/AutoTest/interface/vie_autotest_defines.h @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// +// vie_autotest_defines.h +// + + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_DEFINES_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_DEFINES_H_ + +#include +#include +#include + +#include "engine_configurations.h" + +#if defined(_WIN32) +#include +#elif defined (ANDROID) +#include +#include +#elif defined(WEBRTC_LINUX) +#include +#include +#include +#include +#elif defined(WEBRTC_MAC_INTEL) +#import +#endif + +// Choose how to log +//#define VIE_LOG_TO_FILE +#define VIE_LOG_TO_STDOUT + +// Choose one way to test error +#define VIE_ASSERT_ERROR + +#define VIE_LOG_FILE_NAME "ViEAutotestLog.txt" + +#undef RGB +#define RGB(r,g,b) r|g<<8|b<<16 + +// Default values for custom call +#define DEFAULT_SEND_IP "127.0.0.1" +#define DEFAULT_VIDEO_PORT 9000 +#define DEFAULT_VIDEO_CODEC "vp8" +#define DEFAULT_VIDEO_CODEC_WIDTH 352 +#define DEFAULT_VIDEO_CODEC_HEIGHT 288 +#define DEFAULT_AUDIO_PORT 8000 +#define DEFAULT_AUDIO_CODEC "isac" + +enum +{ + KAutoTestSleepTimeMs = 5000 +}; + +struct AutoTestSize +{ + unsigned int width; + unsigned int height; + AutoTestSize() : + width(0), + height(0) + {} + AutoTestSize(unsigned int iWidth, unsigned int iHeight) : + width(iWidth), + height(iHeight) + {} +}; + +struct AutoTestOrigin +{ + unsigned int x; + unsigned int y; + AutoTestOrigin() : + x(0), + y(0) + {} + AutoTestOrigin(unsigned int iX, unsigned int iY) : + x(iX), + y(iY) + {} +}; + +struct AutoTestRect +{ + AutoTestSize size; + AutoTestOrigin origin; + AutoTestRect() : + size(), + origin() + {} + + AutoTestRect(unsigned int iX, unsigned int iY, unsigned int iWidth, + unsigned int iHeight) : + size(iX, iY), + origin(iWidth, iHeight) + {} + + void Copy(AutoTestRect iRect) + { + origin.x = iRect.origin.x; + origin.y = iRect.origin.y; + size.width = iRect.size.width; + size.height = iRect.size.height; + } +}; + +// ============================================ + +class ViETest +{ +protected: + static FILE* _logFile; + enum + { + KMaxLogSize = 512 + }; + static char* _logStr; +public: + + static int Init() + { +#ifdef VIE_LOG_TO_FILE + _logFile = fopen(VIE_LOG_FILE_NAME, "w+t"); +#else + _logFile = NULL; +#endif + _logStr = new char[KMaxLogSize]; + memset(_logStr, 0, KMaxLogSize); + return 0; + } + + static int Terminate() + { + if (_logFile) + { + fclose(_logFile); + _logFile = NULL; + } + if (_logStr) + { + delete[] _logStr; + _logStr = NULL; + } + return 0; + } + + static void Log(char* fmt, ...) + { + va_list va; + va_start(va, fmt); + memset(_logStr, 0, KMaxLogSize); + vsprintf(_logStr, fmt, va); + va_end(va); + +#ifdef VIE_LOG_TO_FILE + if (_logFile) + { + fwrite(_logStr, 1, strlen(_logStr), _logFile); + fwrite("\n", 1, 1, _logFile); + fflush(_logFile); + } +#endif +#ifdef VIE_LOG_TO_STDOUT +#if ANDROID + __android_log_write(ANDROID_LOG_DEBUG, "*WebRTCN*", _logStr); +#else + printf(_logStr); + printf("\n"); +#endif +#endif + } + + static int TestError(bool expr) + { + if (!expr) + { +#ifdef VIE_ASSERT_ERROR + assert(expr); +#endif + return 1; + } + return 0; + } + + static int TestError(bool expr, char* fmt, ...) + { + + if (!expr) + { + va_list va; + va_start(va, fmt); + memset(_logStr, 0, KMaxLogSize); + vsprintf(_logStr, fmt, va); +#ifdef ANDROID + __android_log_write(ANDROID_LOG_ERROR, "*WebRTCN*", _logStr); +#endif + Log(_logStr); + va_end(va); + +#ifdef VIE_ASSERT_ERROR + assert(false); +#endif + return 1; + } + return 0; + } +}; + +// milliseconds +#if defined(_WIN32) +#define AutoTestSleep ::Sleep +#elif defined(WEBRTC_MAC_INTEL) +#define AutoTestSleep(x) usleep(x * 1000) +#elif defined(WEBRTC_LINUX) +namespace +{ + void Sleep(unsigned long x) + { + timespec t; + t.tv_sec = x/1000; + t.tv_nsec = (x-(x/1000)*1000)*1000000; + nanosleep(&t,NULL); + } +} +#define AutoTestSleep ::Sleep +#endif + +#ifdef ANDROID +#define VIE_TEST_FILES_ROOT "/sdcard/vie_auto_test/" +#else +#define VIE_TEST_FILES_ROOT "/tmp/vie_auto_test/" +#endif + +namespace +{ +FILE* OpenTestFile(char* fileName) +{ + char filePath[256]; + sprintf(filePath,"%s%s",VIE_TEST_FILES_ROOT,fileName); + return fopen(filePath,"rb"); +} +} +#endif // WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_DEFINES_H_ diff --git a/video_engine/main/test/AutoTest/interface/vie_autotest_linux.h b/video_engine/main/test/AutoTest/interface/vie_autotest_linux.h new file mode 100644 index 0000000000..5343eb6a19 --- /dev/null +++ b/video_engine/main/test/AutoTest/interface/vie_autotest_linux.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_LINUX_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_LINUX_H_ + +#include "vie_autotest_window_manager_interface.h" +#include +#include + +// Forward declaration + +class ViEAutoTestWindowManager: public ViEAutoTestWindowManagerInterface +{ +public: + ViEAutoTestWindowManager(); + ~ViEAutoTestWindowManager(); + virtual void* GetWindow1(); + virtual void* GetWindow2(); + virtual int TerminateWindows(); + virtual int CreateWindows(AutoTestRect window1Size, + AutoTestRect window2Size, void* window1Title, + void* window2Title); + virtual bool SetTopmostWindow(); + +private: + int ViECreateWindow(Window *outWindow, Display **outDisplay, int xpos, + int ypos, int width, int height, char* title); + int ViEDestroyWindow(Window *window, Display *display); + + Window _hwnd1; + Window _hwnd2; + Display* _hdsp1; + Display* _hdsp2; +}; + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_LINUX_H_ diff --git a/video_engine/main/test/AutoTest/interface/vie_autotest_mac_carbon.h b/video_engine/main/test/AutoTest/interface/vie_autotest_mac_carbon.h new file mode 100644 index 0000000000..a67806a267 --- /dev/null +++ b/video_engine/main/test/AutoTest/interface/vie_autotest_mac_carbon.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "EngineConfigurations.h" + +#if defined(CARBON_RENDERING) +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_MAC_CARBON_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_MAC_CARBON_H_ + +#include "vie_autotest_window_manager_interface.h" + +// #define HIVIEWREF_MODE 1 + +#include +#import + +class ViEAutoTestWindowManager: public ViEAutoTestWindowManagerInterface +{ +public: + ViEAutoTestWindowManager(); + ~ViEAutoTestWindowManager(); + virtual void* GetWindow1(); + virtual void* GetWindow2(); + virtual int CreateWindows(AutoTestRect window1Size, + AutoTestRect window2Size, char* window1Title, + char* window2Title); + virtual int TerminateWindows(); + virtual bool SetTopmostWindow(); + + // event handler static methods +static pascal OSStatus HandleWindowEvent (EventHandlerCallRef nextHandler, + EventRef theEvent, void* userData); +static pascal OSStatus HandleHIViewEvent (EventHandlerCallRef nextHandler, + EventRef theEvent, void* userData); +private: + WindowRef* _carbonWindow1; + WindowRef* _carbonWindow2; + HIViewRef* _hiView1; + HIViewRef* _hiView2; + + EventHandlerRef _carbonWindow1EventHandlerRef; + EventHandlerRef _carbonWindow2EventHandlerRef; + EventHandlerRef _carbonHIView1EventHandlerRef; + EventHandlerRef _carbonHIView2EventHandlerRef; + +}; + +@interface AutoTestClass : NSObject +{ +} + +-(void)autoTestWithArg:(NSString*)answerFile; +@end + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_MAC_CARBON_H_ +#endif CARBON_RENDERING diff --git a/video_engine/main/test/AutoTest/interface/vie_autotest_mac_cocoa.h b/video_engine/main/test/AutoTest/interface/vie_autotest_mac_cocoa.h new file mode 100644 index 0000000000..c131649b40 --- /dev/null +++ b/video_engine/main/test/AutoTest/interface/vie_autotest_mac_cocoa.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "engine_configurations.h" + +#if defined(COCOA_RENDERING) + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_MAC_COCOA_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_MAC_COCOA_H_ + +#include "vie_autotest_window_manager_interface.h" +#define MAC_COCOA_USE_NSRUNLOOP 1 + +@class CocoaRenderView; + +#import + +class ViEAutoTestWindowManager: public ViEAutoTestWindowManagerInterface +{ +public: + ViEAutoTestWindowManager(); + ~ViEAutoTestWindowManager(); + virtual void* GetWindow1(); + virtual void* GetWindow2(); + virtual int CreateWindows(AutoTestRect window1Size, + AutoTestRect window2Size, void* window1Title, + void* window2Title); + virtual int TerminateWindows(); + virtual bool SetTopmostWindow(); + +private: + CocoaRenderView* _cocoaRenderView1; + CocoaRenderView* _cocoaRenderView2; +}; + +@interface AutoTestClass : NSObject +{ +} + +-(void)autoTestWithArg:(NSString*)answerFile; + +@end + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_MAC_COCOA_H_ +#endif // COCOA_RENDERING diff --git a/video_engine/main/test/AutoTest/interface/vie_autotest_main.h b/video_engine/main/test/AutoTest/interface/vie_autotest_main.h new file mode 100644 index 0000000000..43ce358159 --- /dev/null +++ b/video_engine/main/test/AutoTest/interface/vie_autotest_main.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_MAIN_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_MAIN_H_ + +#include + +using namespace std; + +class ViEAutoTestMain +{ +public: + ViEAutoTestMain(); + bool BeginOSIndependentTesting(); + bool GetAnswer(int index, string& answer); + int GetClassTestSelection(); + bool GetNextAnswer(string& answer); + bool IsUsingAnswerFile(); + bool UseAnswerFile(const char* fileName); + +private: + + string _answers[1024]; + int _answersCount; + int _answersIndex; + bool _useAnswerFile; +}; + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_MAIN_H_ diff --git a/video_engine/main/test/AutoTest/interface/vie_autotest_window_manager_interface.h b/video_engine/main/test/AutoTest/interface/vie_autotest_window_manager_interface.h new file mode 100644 index 0000000000..04a3676751 --- /dev/null +++ b/video_engine/main/test/AutoTest/interface/vie_autotest_window_manager_interface.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_autotest_window_manager_interface.h + */ + +#include "vie_autotest_defines.h" + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_WINDOW_MANAGER_INTERFACE_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_WINDOW_MANAGER_INTERFACE_H_ + +class ViEAutoTestWindowManagerInterface +{ +public: + virtual int CreateWindows(AutoTestRect window1Size, + AutoTestRect window2Size, void* window1Title, + void* window2Title) = 0; + virtual int TerminateWindows() = 0; + virtual void* GetWindow1() = 0; + virtual void* GetWindow2() = 0; + virtual bool SetTopmostWindow() = 0; +}; + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_WINDOW_MANAGER_INTERFACE_H_ diff --git a/video_engine/main/test/AutoTest/interface/vie_autotest_windows.h b/video_engine/main/test/AutoTest/interface/vie_autotest_windows.h new file mode 100644 index 0000000000..584e75c2ee --- /dev/null +++ b/video_engine/main/test/AutoTest/interface/vie_autotest_windows.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_WINDOWS_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_WINDOWS_H_ + +#include "vie_autotest_window_manager_interface.h" +#include "engine_configurations.h" + +#include +#define TITLE_LENGTH 1024 + +// Forward declaration +namespace webrtc { +class ThreadWrapper; +class CriticalSectionWrapper; +} + +class ViEAutoTestWindowManager: public ViEAutoTestWindowManagerInterface +{ +public: + ViEAutoTestWindowManager(); + ~ViEAutoTestWindowManager(); + virtual void* GetWindow1(); + virtual void* GetWindow2(); + virtual int CreateWindows(AutoTestRect window1Size, + AutoTestRect window2Size, void* window1Title, + void* window2Title); + virtual int TerminateWindows(); + virtual bool SetTopmostWindow(); +protected: + static bool EventProcess(void* obj); + bool EventLoop(); + +private: + int ViECreateWindow(HWND &hwndMain, int xPos, int yPos, int width, + int height, TCHAR* className); + int ViEDestroyWindow(HWND& hwnd); + + void* _window1; + void* _window2; + + bool _terminate; + webrtc::ThreadWrapper& _eventThread; + webrtc::CriticalSectionWrapper& _crit; + HWND _hwndMain; + HWND _hwnd1; + HWND _hwnd2; + + AutoTestRect _hwnd1Size; + AutoTestRect _hwnd2Size; + TCHAR _hwnd1Title[TITLE_LENGTH]; + TCHAR _hwnd2Title[TITLE_LENGTH]; + +}; + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_AUTOTEST_WINDOWS_H_ diff --git a/video_engine/main/test/AutoTest/media/captureDeviceImage.bmp b/video_engine/main/test/AutoTest/media/captureDeviceImage.bmp new file mode 100644 index 0000000000000000000000000000000000000000..6cd34ba97e8216d666c9fc01ccc92bded8255f7c GIT binary patch literal 304182 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%0A6Vnl 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 literal 0 HcmV?d00001 diff --git a/video_engine/main/test/AutoTest/media/captureDeviceImage.jpg b/video_engine/main/test/AutoTest/media/captureDeviceImage.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3bb3ba48bbdaab19f788179d627b6d83304e2c45 GIT binary patch literal 10742 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

#2im%gf=K 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 literal 0 HcmV?d00001 diff --git a/video_engine/main/test/AutoTest/media/renderStartImage.bmp b/video_engine/main/test/AutoTest/media/renderStartImage.bmp new file mode 100644 index 0000000000000000000000000000000000000000..c443a58f6cb2a7b13066f6db91819cbc54ac4cc8 GIT binary patch literal 304182 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 literal 0 HcmV?d00001 diff --git a/video_engine/main/test/AutoTest/media/renderTimeoutImage.bmp b/video_engine/main/test/AutoTest/media/renderTimeoutImage.bmp new file mode 100644 index 0000000000000000000000000000000000000000..8159badeb22a265bcb4d60e549a2dba049e21038 GIT binary patch literal 304182 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 z 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 +#include +#include + +tbI420Encoder::tbI420Encoder() : + _inited(false), _encodedImage(), _encodedCompleteCallback(NULL) +{ + // + memset(&_functionCalls, 0, sizeof(_functionCalls)); +} + +tbI420Encoder::~tbI420Encoder() +{ + _inited = false; + if (_encodedImage._buffer != NULL) + { + delete[] _encodedImage._buffer; + _encodedImage._buffer = NULL; + } +} + +WebRtc_Word32 tbI420Encoder::VersionStatic(WebRtc_Word8* version, + WebRtc_Word32 length) +{ + const WebRtc_Word8* str = "I420 version 1.0.0\n"; + WebRtc_Word32 verLen = (WebRtc_Word32) strlen(str); + if (verLen > length) + { + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + strncpy(version, str, length); + return verLen; +} + +WebRtc_Word32 tbI420Encoder::Version(WebRtc_Word8 *version, + WebRtc_Word32 length) const +{ + return VersionStatic(version, length); +} + +WebRtc_Word32 tbI420Encoder::Release() +{ + _functionCalls.Release++; + // should allocate an encoded frame and then release it here, for that we + // actaully need an init flag + if (_encodedImage._buffer != NULL) + { + delete[] _encodedImage._buffer; + _encodedImage._buffer = NULL; + } + _inited = false; + return WEBRTC_VIDEO_CODEC_OK; +} + +WebRtc_Word32 tbI420Encoder::Reset() +{ + _functionCalls.Reset++; + if (!_inited) + { + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + } + return WEBRTC_VIDEO_CODEC_OK; + +} + +WebRtc_Word32 tbI420Encoder::InitEncode(const webrtc::VideoCodec* inst, + WebRtc_Word32 /*numberOfCores*/, + WebRtc_UWord32 /*maxPayloadSize */) +{ + _functionCalls.InitEncode++; + if (inst == NULL) + { + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + if (inst->width < 1 || inst->height < 1) + { + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + + // allocating encoded memory + if (_encodedImage._buffer != NULL) + { + delete[] _encodedImage._buffer; + _encodedImage._buffer = NULL; + _encodedImage._size = 0; + } + const WebRtc_UWord32 newSize = (3 * inst->width * inst->height) >> 1; + WebRtc_UWord8* newBuffer = new WebRtc_UWord8[newSize]; + if (newBuffer == NULL) + { + return WEBRTC_VIDEO_CODEC_MEMORY; + } + _encodedImage._size = newSize; + _encodedImage._buffer = newBuffer; + + // if no memeory allocation, no point to init + _inited = true; + return WEBRTC_VIDEO_CODEC_OK; +} + +WebRtc_Word32 tbI420Encoder::Encode(const webrtc::RawImage& inputImage, + const void* codecSpecificInfo /*= NULL*/, + webrtc::VideoFrameType frameType + /*= webrtc::kDeltaFrame*/) +{ + _functionCalls.Encode++; + if (!_inited) + { + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + } + if (_encodedCompleteCallback == NULL) + { + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + } + + _encodedImage._frameType = webrtc::kKeyFrame; // no coding + _encodedImage._timeStamp = inputImage._timeStamp; + _encodedImage._encodedHeight = inputImage._height; + _encodedImage._encodedWidth = inputImage._width; + if (inputImage._length > _encodedImage._size) + { + + // allocating encoded memory + if (_encodedImage._buffer != NULL) + { + delete[] _encodedImage._buffer; + _encodedImage._buffer = NULL; + _encodedImage._size = 0; + } + const WebRtc_UWord32 newSize = (3 * _encodedImage._encodedWidth + * _encodedImage._encodedHeight) >> 1; + WebRtc_UWord8* newBuffer = new WebRtc_UWord8[newSize]; + if (newBuffer == NULL) + { + return WEBRTC_VIDEO_CODEC_MEMORY; + } + _encodedImage._size = newSize; + _encodedImage._buffer = newBuffer; + } + assert(_encodedImage._size >= inputImage._length); + memcpy(_encodedImage._buffer, inputImage._buffer, inputImage._length); + _encodedImage._length = inputImage._length; + _encodedCompleteCallback->Encoded(_encodedImage); + return WEBRTC_VIDEO_CODEC_OK; +} + +WebRtc_Word32 tbI420Encoder::RegisterEncodeCompleteCallback( + webrtc::EncodedImageCallback* callback) +{ + _functionCalls.RegisterEncodeCompleteCallback++; + _encodedCompleteCallback = callback; + return WEBRTC_VIDEO_CODEC_OK; +} + +WebRtc_Word32 tbI420Encoder::SetPacketLoss(WebRtc_UWord32 packetLoss) +{ + _functionCalls.SetPacketLoss++; + return WEBRTC_VIDEO_CODEC_OK; +} + +WebRtc_Word32 tbI420Encoder::SetRates(WebRtc_UWord32 newBitRate, + WebRtc_UWord32 frameRate) +{ + _functionCalls.SetRates++; + return WEBRTC_VIDEO_CODEC_OK; +} + +WebRtc_Word32 tbI420Encoder::SetPeriodicKeyFrames(bool enable) +{ + _functionCalls.SetPeriodicKeyFrames++; + return WEBRTC_VIDEO_CODEC_ERROR; +} + +WebRtc_Word32 tbI420Encoder::CodecConfigParameters(WebRtc_UWord8* /*buffer*/, + WebRtc_Word32 /*size*/) +{ + _functionCalls.CodecConfigParameters++; + return WEBRTC_VIDEO_CODEC_ERROR; +} +tbI420Encoder::FunctionCalls tbI420Encoder::GetFunctionCalls() +{ + return _functionCalls; +} + +tbI420Decoder::tbI420Decoder(): + _decodedImage(), _width(0), _height(0), _inited(false), + _decodeCompleteCallback(NULL) +{ + memset(&_functionCalls, 0, sizeof(_functionCalls)); +} + +tbI420Decoder::~tbI420Decoder() +{ + Release(); +} + +WebRtc_Word32 tbI420Decoder::Reset() +{ + _functionCalls.Reset++; + return WEBRTC_VIDEO_CODEC_OK; +} + +WebRtc_Word32 tbI420Decoder::InitDecode(const webrtc::VideoCodec* inst, + WebRtc_Word32 /*numberOfCores */) +{ + _functionCalls.InitDecode++; + if (inst == NULL) + { + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + else if (inst->width < 1 || inst->height < 1) + { + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + _width = inst->width; + _height = inst->height; + _inited = true; + return WEBRTC_VIDEO_CODEC_OK; +} + +WebRtc_Word32 tbI420Decoder::Decode(const webrtc::EncodedImage& inputImage, + bool /*missingFrames*/, + const void* /*codecSpecificInfWord64o*/, + WebRtc_Word64 /*renderTimeMs*/) +{ + _functionCalls.Decode++; + if (inputImage._buffer == NULL) + { + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + if (_decodeCompleteCallback == NULL) + { + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + } + if (inputImage._length <= 0) + { + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + if (!_inited) + { + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + } + + // Allocate memory for decoded image + + if (_decodedImage._buffer != NULL) + { + delete[] _decodedImage._buffer; + _decodedImage._buffer = NULL; + _decodedImage._size = 0; + } + if (_decodedImage._buffer == NULL) + { + const WebRtc_UWord32 newSize = (3 * _width * _height) >> 1; + WebRtc_UWord8* newBuffer = new WebRtc_UWord8[newSize]; + if (newBuffer == NULL) + { + return WEBRTC_VIDEO_CODEC_MEMORY; + } + _decodedImage._size = newSize; + _decodedImage._buffer = newBuffer; + } + + // Set decoded image parameters + _decodedImage._height = _height; + _decodedImage._width = _width; + _decodedImage._timeStamp = inputImage._timeStamp; + assert(_decodedImage._size >= inputImage._length); + memcpy(_decodedImage._buffer, inputImage._buffer, inputImage._length); + _decodedImage._length = inputImage._length; + //_decodedImage._buffer = inputImage._buffer; + + _decodeCompleteCallback->Decoded(_decodedImage); + return WEBRTC_VIDEO_CODEC_OK; +} + +WebRtc_Word32 tbI420Decoder::RegisterDecodeCompleteCallback( + webrtc::DecodedImageCallback* callback) +{ + _functionCalls.RegisterDecodeCompleteCallback++; + _decodeCompleteCallback = callback; + return WEBRTC_VIDEO_CODEC_OK; +} + +WebRtc_Word32 tbI420Decoder::Release() +{ + _functionCalls.Release++; + if (_decodedImage._buffer != NULL) + { + delete[] _decodedImage._buffer; + _decodedImage._buffer = NULL; + } + _inited = false; + return WEBRTC_VIDEO_CODEC_OK; +} + +tbI420Decoder::FunctionCalls tbI420Decoder::GetFunctionCalls() +{ + return _functionCalls; +} + diff --git a/video_engine/main/test/AutoTest/source/tb_capture_device.cc b/video_engine/main/test/AutoTest/source/tb_capture_device.cc new file mode 100644 index 0000000000..297a00b23f --- /dev/null +++ b/video_engine/main/test/AutoTest/source/tb_capture_device.cc @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "tb_capture_device.h" + +tbCaptureDevice::tbCaptureDevice(tbInterfaces& Engine, int& nrOfErrors) : + ViE(Engine), + numberOfErrors(nrOfErrors), + captureId(-1), + vcpm_(NULL) +{ + const unsigned int KMaxDeviceNameLength = 128; + const unsigned int KMaxUniqueIdLength = 256; + WebRtc_UWord8 deviceName[KMaxDeviceNameLength]; + memset(deviceName, 0, KMaxDeviceNameLength); + WebRtc_UWord8 uniqueId[KMaxUniqueIdLength]; + memset(uniqueId, 0, KMaxUniqueIdLength); + + int error; + bool captureDeviceSet = false; + + webrtc::VideoCaptureModule::DeviceInfo* devInfo = + webrtc::VideoCaptureModule::CreateDeviceInfo(0); + for (size_t captureIdx = 0; + captureIdx < devInfo->NumberOfDevices(); + captureIdx++) + { + error = devInfo->GetDeviceName(captureIdx, deviceName, + KMaxDeviceNameLength, uniqueId, + KMaxUniqueIdLength); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + vcpm_ = webrtc::VideoCaptureModule::Create(captureIdx, uniqueId); + if (vcpm_ == NULL) // Failed to open this device. Try next. + { + continue; + } + + error = ViE.ptrViECapture->AllocateCaptureDevice(*vcpm_, captureId); + if (error == 0) + { + ViETest::Log("Using capture device: %s, captureId: %d", deviceName, + captureId); + captureDeviceSet = true; + break; + } + } + webrtc::VideoCaptureModule::DestroyDeviceInfo(devInfo); + numberOfErrors += ViETest::TestError( + captureDeviceSet, "ERROR: %s at line %d - Could not set capture device", + __FUNCTION__, __LINE__); + + ViETest::Log("Starting capture device %s with captureId %d\n", deviceName, + captureId); + + error = ViE.ptrViECapture->StartCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +} + +tbCaptureDevice::~tbCaptureDevice(void) +{ + ViETest::Log("Stopping capture device with id %d\n", captureId); + int error; + error = ViE.ptrViECapture->StopCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViECapture->ReleaseCaptureDevice(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + webrtc::VideoCaptureModule::Destroy(vcpm_); + +} + +void tbCaptureDevice::ConnectTo(int videoChannel) +{ + int error; + error = ViE.ptrViECapture->ConnectCaptureDevice(captureId, videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +} + +void tbCaptureDevice::Disconnect(int videoChannel) +{ + int error = 0; + error = ViE.ptrViECapture->DisconnectCaptureDevice(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +} diff --git a/video_engine/main/test/AutoTest/source/tb_external_transport.cc b/video_engine/main/test/AutoTest/source/tb_external_transport.cc new file mode 100644 index 0000000000..35d7a95ac9 --- /dev/null +++ b/video_engine/main/test/AutoTest/source/tb_external_transport.cc @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// +// tb_external_transport.cc +// + +#include // rand +#include "tb_external_transport.h" + +#include "critical_section_wrapper.h" +#include "event_wrapper.h" +#include "thread_wrapper.h" +#include "tick_util.h" +#include "vie_network.h" +#include "tick_util.h" + +#if defined(WEBRTC_LINUX) || defined(__linux__) +#include +#include +#endif + +#if defined(WEBRTC_MAC) +#include +#endif + +#if defined(_WIN32) +#pragma warning(disable: 4355) // 'this' : used in base member initializer list +#endif + +using namespace webrtc; + +tbExternalTransport::tbExternalTransport(ViENetwork& vieNetwork) : + _vieNetwork(vieNetwork), + _thread(*ThreadWrapper::CreateThread(ViEExternalTransportRun, this, + kHighPriority, + "AutotestTransport")), + _event(*EventWrapper::Create()), + _crit(*CriticalSectionWrapper::CreateCriticalSection()), + _statCrit(*CriticalSectionWrapper::CreateCriticalSection()), + _lossRate(0), _networkDelayMs(0), _rtpCount(0), _rtcpCount(0), + _dropCount(0), _rtpPackets(), _rtcpPackets(), _checkSSRC(false), + _lastSSRC(0), _checkSequenceNumber(0), _firstSequenceNumber(0) +{ + srand((int) TickTime::MicrosecondTimestamp()); + unsigned int tId = 0; + _thread.Start(tId); +} + +tbExternalTransport::~tbExternalTransport() +{ + _thread.SetNotAlive(); + _event.Set(); + if (_thread.Stop()) + { + delete &_thread; + delete &_event; + } + delete &_crit; + delete &_statCrit; +} + +int tbExternalTransport::SendPacket(int channel, const void *data, int len) +{ + _statCrit.Enter(); + _rtpCount++; + _statCrit.Leave(); + + // Packet loss + int dropThis = rand() % 100; + if (dropThis < _lossRate) + { + _statCrit.Enter(); + _dropCount++; + _statCrit.Leave(); + return 0; + } + + VideoPacket* newPacket = new VideoPacket(); + memcpy(newPacket->packetBuffer, data, len); + newPacket->length = len; + newPacket->channel = channel; + + _crit.Enter(); + newPacket->receiveTime = NowMs() + _networkDelayMs; + _rtpPackets.PushBack(newPacket); + _event.Set(); + _crit.Leave(); + return len; +} + +int tbExternalTransport::SendRTCPPacket(int channel, const void *data, int len) +{ + _statCrit.Enter(); + _rtcpCount++; + _statCrit.Leave(); + + VideoPacket* newPacket = new VideoPacket(); + memcpy(newPacket->packetBuffer, data, len); + newPacket->length = len; + newPacket->channel = channel; + + _crit.Enter(); + newPacket->receiveTime = NowMs() + _networkDelayMs; + _rtcpPackets.PushBack(newPacket); + _event.Set(); + _crit.Leave(); + return len; +} + +WebRtc_Word32 tbExternalTransport::SetPacketLoss(WebRtc_Word32 lossRate) +{ + CriticalSectionScoped cs(_statCrit); + _lossRate = lossRate; + return 0; +} + +void tbExternalTransport::SetNetworkDelay(WebRtc_Word64 delayMs) +{ + CriticalSectionScoped cs(_crit); + _networkDelayMs = delayMs; + return; +} + +void tbExternalTransport::ClearStats() +{ + CriticalSectionScoped cs(_statCrit); + _rtpCount = 0; + _dropCount = 0; + _rtcpCount = 0; + return; +} + +void tbExternalTransport::GetStats(WebRtc_Word32& numRtpPackets, + WebRtc_Word32& numDroppedPackets, + WebRtc_Word32& numRtcpPackets) +{ + CriticalSectionScoped cs(_statCrit); + numRtpPackets = _rtpCount; + numDroppedPackets = _dropCount; + numRtcpPackets = _rtcpCount; + return; +} + +void tbExternalTransport::EnableSSRCCheck() +{ + CriticalSectionScoped cs(_statCrit); + _checkSSRC = true; +} +unsigned int tbExternalTransport::ReceivedSSRC() +{ + CriticalSectionScoped cs(_statCrit); + return _lastSSRC; +} + +void tbExternalTransport::EnableSequenceNumberCheck() +{ + CriticalSectionScoped cs(_statCrit); + _checkSequenceNumber = true; +} + +unsigned short tbExternalTransport::GetFirstSequenceNumber() +{ + CriticalSectionScoped cs(_statCrit); + return _firstSequenceNumber; +} + +bool tbExternalTransport::ViEExternalTransportRun(void* object) +{ + return static_cast + (object)->ViEExternalTransportProcess(); +} +bool tbExternalTransport::ViEExternalTransportProcess() +{ + unsigned int waitTime = KMaxWaitTimeMs; + + VideoPacket* packet = NULL; + + while (!_rtpPackets.Empty()) + { + // Take first packet in queue + _crit.Enter(); + packet = static_cast ((_rtpPackets.First())->GetItem()); + WebRtc_Word64 timeToReceive = packet->receiveTime - NowMs(); + if (timeToReceive > 0) + { + // No packets to receive yet + if (timeToReceive < waitTime && timeToReceive > 0) + { + waitTime = (unsigned int) timeToReceive; + } + _crit.Leave(); + break; + } + _rtpPackets.PopFront(); + _crit.Leave(); + + // Send to ViE + if (packet) + { + { + CriticalSectionScoped cs(_statCrit); + if (_checkSSRC) + { + _lastSSRC = ((packet->packetBuffer[8]) << 24); + _lastSSRC += (packet->packetBuffer[9] << 16); + _lastSSRC += (packet->packetBuffer[10] << 8); + _lastSSRC += packet->packetBuffer[11]; + _checkSSRC = false; + } + if (_checkSequenceNumber) + { + _firstSequenceNumber + = (unsigned char) packet->packetBuffer[2] << 8; + _firstSequenceNumber + += (unsigned char) packet->packetBuffer[3]; + _checkSequenceNumber = false; + } + } + _vieNetwork.ReceivedRTPPacket(packet->channel, + packet->packetBuffer, packet->length); + delete packet; + packet = NULL; + } + } + while (!_rtcpPackets.Empty()) + { + // Take first packet in queue + _crit.Enter(); + packet = static_cast ((_rtcpPackets.First())->GetItem()); + WebRtc_Word64 timeToReceive = packet->receiveTime - NowMs(); + if (timeToReceive > 0) + { + // No packets to receive yet + if (timeToReceive < waitTime && timeToReceive > 0) + { + waitTime = (unsigned int) timeToReceive; + } + _crit.Leave(); + break; + } + _rtcpPackets.PopFront(); + _crit.Leave(); + + // Send to ViE + if (packet) + { + _vieNetwork.ReceivedRTPPacket(packet->channel, + packet->packetBuffer, packet->length); + delete packet; + packet = NULL; + } + } + _event.Wait(waitTime + 1); // Add 1 ms to not call to early... + return true; +} + +WebRtc_Word64 tbExternalTransport::NowMs() +{ + return TickTime::MillisecondTimestamp(); +} diff --git a/video_engine/main/test/AutoTest/source/tb_interfaces.cc b/video_engine/main/test/AutoTest/source/tb_interfaces.cc new file mode 100644 index 0000000000..3c3483da5e --- /dev/null +++ b/video_engine/main/test/AutoTest/source/tb_interfaces.cc @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "tb_interfaces.h" + +tbInterfaces::tbInterfaces(const char* testName, int& nrOfErrors) : + numberOfErrors(nrOfErrors) +{ + char traceFile[256] = ""; + char traceFileEnc[256] = ""; + +#ifdef ANDROID + strcat(traceFile,"/sdcard/"); +#endif + strcat(traceFile, testName); + strcat(traceFileEnc, traceFile); + strcat(traceFileEnc, "_encrypted"); + + ViETest::Log("Creating ViE Interfaces for test %s\n", testName); + + ptrViE = webrtc::VideoEngine::Create(); + numberOfErrors += ViETest::TestError(ptrViE != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + int error = ptrViE->SetTraceFile(traceFile); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViE->SetTraceFilter(webrtc::kTraceAll); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ptrViEBase = webrtc::ViEBase::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViEBase != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + error = ptrViEBase->Init(); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ptrViECapture = webrtc::ViECapture::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViECapture != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + ptrViERtpRtcp = webrtc::ViERTP_RTCP::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViERtpRtcp != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + ptrViERender = webrtc::ViERender::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViERender != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + ptrViECodec = webrtc::ViECodec::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViECodec != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + ptrViENetwork = webrtc::ViENetwork::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViENetwork != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + ptrViEImageProcess = webrtc::ViEImageProcess::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViEImageProcess != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + ptrViEEncryption = webrtc::ViEEncryption::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViEEncryption != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); +} + +tbInterfaces::~tbInterfaces(void) +{ + int numberOfErrors = 0; + int remainingInterfaces = 0; + + remainingInterfaces = ptrViEEncryption->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + remainingInterfaces = ptrViEImageProcess->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + remainingInterfaces = ptrViECodec->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + remainingInterfaces = ptrViECapture->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + remainingInterfaces = ptrViERender->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + remainingInterfaces = ptrViERtpRtcp->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + remainingInterfaces = ptrViENetwork->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + remainingInterfaces = ptrViEBase->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + bool deleted = webrtc::VideoEngine::Delete(ptrViE); + numberOfErrors += ViETest::TestError(deleted == true, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + +} diff --git a/video_engine/main/test/AutoTest/source/tb_video_channel.cc b/video_engine/main/test/AutoTest/source/tb_video_channel.cc new file mode 100644 index 0000000000..8ceb39857a --- /dev/null +++ b/video_engine/main/test/AutoTest/source/tb_video_channel.cc @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "tb_video_channel.h" + +tbVideoChannel::tbVideoChannel(tbInterfaces& Engine, int& nrOfErrors, + webrtc::VideoCodecType sendCodec, int width, + int height, int frameRate, int startBitrate) : + ViE(Engine), numberOfErrors(nrOfErrors), videoChannel(-1) +{ + int error; + error = ViE.ptrViEBase->CreateChannel(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + webrtc::VideoCodec videoCodec; + memset(&videoCodec, 0, sizeof(webrtc::VideoCodec)); + bool sendCodecSet = false; + for (int idx = 0; idx < ViE.ptrViECodec->NumberOfCodecs(); idx++) + { + error = ViE.ptrViECodec->GetCodec(idx, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + videoCodec.width = width; + videoCodec.height = height; + videoCodec.maxFramerate = frameRate; + + if (videoCodec.codecType == sendCodec && sendCodecSet == false) + { + if(videoCodec.codecType != webrtc::kVideoCodecI420 ) + { + videoCodec.startBitrate = startBitrate; + videoCodec.maxBitrate = startBitrate * 3; + } + error = ViE.ptrViECodec->SetSendCodec(videoChannel, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + sendCodecSet = true; + } + if (videoCodec.codecType == webrtc::kVideoCodecVP8) + { + videoCodec.width = 352; + videoCodec.height = 288; + } + error = ViE.ptrViECodec->SetReceiveCodec(videoChannel, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } + numberOfErrors += ViETest::TestError(sendCodecSet == true, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + +} + +tbVideoChannel::~tbVideoChannel(void) +{ + int error; + error = ViE.ptrViEBase->DeleteChannel(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +} + +void tbVideoChannel::StartSend(const unsigned short rtpPort /*= 11000*/, + const char* ipAddress /*= "127.0.0.1"*/) +{ + int error; + error = ViE.ptrViENetwork->SetSendDestination(videoChannel, ipAddress, + rtpPort); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEBase->StartSend(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +} + +void tbVideoChannel::SetFrameSettings(int width, int height, int frameRate) +{ + int error; + webrtc::VideoCodec videoCodec; + error = ViE.ptrViECodec->GetSendCodec(videoChannel, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + videoCodec.width = width; + videoCodec.height = height; + videoCodec.maxFramerate = frameRate; + + error = ViE.ptrViECodec->SetSendCodec(videoChannel, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViECodec->SetReceiveCodec(videoChannel, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + +} +void tbVideoChannel::StopSend() +{ + int error; + error = ViE.ptrViEBase->StopSend(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +} + +void tbVideoChannel::StartReceive(const unsigned short rtpPort /*= 11000*/) +{ + int error; + + error = ViE.ptrViENetwork->SetLocalReceiver(videoChannel, rtpPort); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEBase->StartReceive(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +} + +void tbVideoChannel::StopReceive() +{ + int error; + error = ViE.ptrViEBase->StopReceive(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +} + diff --git a/video_engine/main/test/AutoTest/source/vie_autotest.cc b/video_engine/main/test/AutoTest/source/vie_autotest.cc new file mode 100644 index 0000000000..9b966f4cfc --- /dev/null +++ b/video_engine/main/test/AutoTest/source/vie_autotest.cc @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// +// vie_autotest.cc +// + + +#include "vie_autotest_defines.h" +#include "vie_autotest.h" + +#include +#include "video_render.h" + +FILE* ViETest::_logFile = NULL; +char* ViETest::_logStr = NULL; + +ViEAutoTest::ViEAutoTest(void* window1, void* window2) : + _window1(window1), + _window2(window2), + _renderType(kRenderDefault), + _vrm1(VideoRender::CreateVideoRender(4561, window1, false, _renderType)), + _vrm2(VideoRender::CreateVideoRender(4562, window2, false, _renderType)) +{ + assert(_vrm1); + assert(_vrm2); + + ViETest::Init(); +} + +ViEAutoTest::~ViEAutoTest() +{ + VideoRender::DestroyVideoRender(_vrm1); + _vrm1 = NULL; + VideoRender::DestroyVideoRender(_vrm2); + _vrm2 = NULL; + + ViETest::Terminate(); +} + +int ViEAutoTest::ViEStandardTest() +{ + int numErrors = 0; + numErrors += ViEBaseStandardTest(); + numErrors += ViECaptureStandardTest(); + numErrors += ViECodecStandardTest(); + numErrors += ViEEncryptionStandardTest(); + numErrors += ViEFileStandardTest(); + numErrors += ViEImageProcessStandardTest(); + numErrors += ViENetworkStandardTest(); + numErrors += ViERenderStandardTest(); + numErrors += ViERtpRtcpStandardTest(); + + if (numErrors > 0) + { + ViETest::Log("Standard Test Failed, with %d erros\n", numErrors); + return numErrors; + } + return numErrors; +} + +int ViEAutoTest::ViEExtendedTest() +{ + int numErrors = 0; + numErrors += ViEBaseExtendedTest(); + numErrors += ViECaptureExtendedTest(); + numErrors += ViECodecExtendedTest(); + numErrors += ViEEncryptionExtendedTest(); + numErrors += ViEFileExtendedTest(); + numErrors += ViEImageProcessExtendedTest(); + numErrors += ViENetworkExtendedTest(); + numErrors += ViERenderExtendedTest(); + numErrors += ViERtpRtcpExtendedTest(); + + if (numErrors > 0) + { + ViETest::Log("Extended Test Failed, with %d erros\n", numErrors); + return numErrors; + } + return numErrors; +} + +int ViEAutoTest::ViEAPITest() +{ + int numErrors = 0; + numErrors += ViEBaseAPITest(); + numErrors += ViECaptureAPITest(); + numErrors += ViECodecAPITest(); + numErrors += ViEEncryptionAPITest(); + numErrors += ViEFileAPITest(); + numErrors += ViEImageProcessAPITest(); + numErrors += ViENetworkAPITest(); + numErrors += ViERenderAPITest(); + numErrors += ViERtpRtcpAPITest(); + + if (numErrors > 0) + { + ViETest::Log("API Test Failed, with %d erros\n", numErrors); + return numErrors; + } + return 0; +} + +void ViEAutoTest::PrintVideoCodec(const webrtc::VideoCodec videoCodec) +{ + using namespace std; + ViETest::Log("Video Codec Information:"); + + switch (videoCodec.codecType) + { + case webrtc::kVideoCodecH263: + ViETest::Log("\tcodecType: H263"); + break; + case webrtc::kVideoCodecVP8: + ViETest::Log("\tcodecType: VP8"); + break; + // TODO(sh): keep or remove MPEG4? + // case webrtc::kVideoCodecMPEG4: + // ViETest::Log("\tcodecType: MPEG4"); + // break; + case webrtc::kVideoCodecI420: + ViETest::Log("\tcodecType: I420"); + break; + case webrtc::kVideoCodecRED: + ViETest::Log("\tcodecType: RED"); + break; + case webrtc::kVideoCodecULPFEC: + ViETest::Log("\tcodecType: ULPFEC"); + break; + case webrtc::kVideoCodecUnknown: + ViETest::Log("\tcodecType: ????"); + break; + default: + ViETest::Log("\tcodecType: ????"); + break; + } + + //cout << "\tcodecSpecific: " << videoCodec.codecSpecific << endl; + //switch(videoCodec.codecSpecific) + //{ + // webrtc::VideoCodecH263 H263; + // webrtc::VideoCodecVP8 VP8; + // webrtc::VideoCodecMPEG4 MPEG4; + // webrtc::VideoCodecGeneric Generic; + //} + + ViETest::Log("\theight: %u", videoCodec.height); + ViETest::Log("\tmaxBitrate: %u", videoCodec.maxBitrate); + ViETest::Log("\tmaxFramerate: %u", videoCodec.maxFramerate); + ViETest::Log("\tminBitrate: %u", videoCodec.minBitrate); + ViETest::Log("\tplName: %s", videoCodec.plName); + ViETest::Log("\tplType: %u", videoCodec.plType); + ViETest::Log("\tstartBitrate: %u", videoCodec.startBitrate); + ViETest::Log("\twidth: %u", videoCodec.width); + ViETest::Log(""); +} + +void ViEAutoTest::PrintAudioCodec(const webrtc::CodecInst audioCodec) +{ + using namespace std; + ViETest::Log("Audio Codec Information:"); + ViETest::Log("\tchannels: %u", audioCodec.channels); + ViETest::Log("\t: %u", audioCodec.pacsize); + ViETest::Log("\t: %u", audioCodec.plfreq); + ViETest::Log("\t: %s", audioCodec.plname); + ViETest::Log("\t: %u", audioCodec.pltype); + ViETest::Log("\t: %u", audioCodec.rate); + ViETest::Log(""); +} diff --git a/video_engine/main/test/AutoTest/source/vie_autotest_android.cc b/video_engine/main/test/AutoTest/source/vie_autotest_android.cc new file mode 100644 index 0000000000..cd206f4177 --- /dev/null +++ b/video_engine/main/test/AutoTest/source/vie_autotest_android.cc @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "../interface/vie_autotest_android.h" + +#include +#include + +#include "vie_autotest.h" +#include "vie_autotest_defines.h" + +int ViEAutoTestAndroid::RunAutotest(int testSelection, int subTestSelection, + void* window1, void* window2, void* javaVM, + void* env, void* context) +{ + ViEAutoTest vieAutoTest(window1, window2); + ViETest::Log("RunAutoTest(%d, %d)", testSelection, subTestSelection); + VideoEngine::SetAndroidObjects(javaVM, context); + VoiceEngine::SetAndroidObjects(javaVM, env, context); + int testErrors = 0; + + if (subTestSelection == 0) + { + // Run all selected test + switch (testSelection) + { + case 0: + testErrors += vieAutoTest.ViEStandardTest(); + if (testErrors == 0) + { + // No errors found in delivery test, create delivery + ViETest::Log("Standard/delivery passed. "); + } + else + { + // Didn't pass + ViETest::Log("\nStandard/delivery test failed."); + } + break; + case 1: + testErrors += vieAutoTest.ViEAPITest(); + break; + case 2: + testErrors += vieAutoTest.ViEExtendedTest(); + break; + case 3: + testErrors += vieAutoTest.ViELoopbackCall(); + break; + default: + break; + } + } + + switch (testSelection) + { + case 0: // Specific standard test + switch (subTestSelection) + { + case 1: // base + testErrors += vieAutoTest.ViEBaseStandardTest(); + break; + + case 2: // capture + testErrors += vieAutoTest.ViECaptureStandardTest(); + break; + + case 3: // codec + testErrors += vieAutoTest.ViECodecStandardTest(); + break; + + case 5: //encryption + testErrors += vieAutoTest.ViEEncryptionStandardTest(); + break; + + case 6: // file + testErrors += vieAutoTest.ViEFileStandardTest(); + break; + + case 7: // image process + testErrors += vieAutoTest.ViEImageProcessStandardTest(); + break; + + case 8: // network + testErrors += vieAutoTest.ViENetworkStandardTest(); + break; + + case 9: // Render + testErrors += vieAutoTest.ViERenderStandardTest(); + break; + + case 10: // RTP/RTCP + testErrors += vieAutoTest.ViERtpRtcpStandardTest(); + break; + + default: + break; + } + break; + + case 1:// specific API + switch (subTestSelection) + { + case 1: // base + testErrors += vieAutoTest.ViEBaseAPITest(); + break; + + case 2: // capture + testErrors += vieAutoTest.ViECaptureAPITest(); + break; + + case 3: // codec + testErrors += vieAutoTest.ViECodecAPITest(); + break; + + case 5: //encryption + testErrors += vieAutoTest.ViEEncryptionAPITest(); + break; + + case 6: // file + testErrors += vieAutoTest.ViEFileAPITest(); + break; + + case 7: // image process + testErrors += vieAutoTest.ViEImageProcessAPITest(); + break; + + case 8: // network + testErrors += vieAutoTest.ViENetworkAPITest(); + break; + + case 9: // Render + testErrors += vieAutoTest.ViERenderAPITest(); + break; + + case 10: // RTP/RTCP + testErrors += vieAutoTest.ViERtpRtcpAPITest(); + break; + case 11: + break; + + default: + break; + } + break; + + case 2:// specific extended + + switch (subTestSelection) + { + case 1: // base + testErrors += vieAutoTest.ViEBaseExtendedTest(); + break; + + case 2: // capture + testErrors += vieAutoTest.ViECaptureExtendedTest(); + break; + + case 3: // codec + testErrors += vieAutoTest.ViECodecExtendedTest(); + break; + + case 5: //encryption + testErrors += vieAutoTest.ViEEncryptionExtendedTest(); + break; + + case 6: // file + testErrors += vieAutoTest.ViEFileExtendedTest(); + break; + + case 7: // image process + testErrors += vieAutoTest.ViEImageProcessExtendedTest(); + break; + + case 8: // network + testErrors += vieAutoTest.ViENetworkExtendedTest(); + break; + + case 9: // Render + testErrors += vieAutoTest.ViERenderExtendedTest(); + break; + + case 10: // RTP/RTCP + testErrors += vieAutoTest.ViERtpRtcpExtendedTest(); + break; + case 11: + break; + + default: + break; + } + break; + case 3: + testErrors += vieAutoTest.ViELoopbackCall(); + break; + default: + break; + } + + if (testErrors) + { + ViETest::Log("Test done with %d errors!\n", testErrors); + } + else + { + ViETest::Log("Test passed!\n"); + } + return testErrors; +} diff --git a/video_engine/main/test/AutoTest/source/vie_autotest_base.cc b/video_engine/main/test/AutoTest/source/vie_autotest_base.cc new file mode 100644 index 0000000000..81a8a4ea99 --- /dev/null +++ b/video_engine/main/test/AutoTest/source/vie_autotest_base.cc @@ -0,0 +1,578 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// +// vie_autotest_base.cc +// + +#include "vie_autotest_defines.h" +#include "vie_autotest.h" +#include "engine_configurations.h" +#include "video_capture.h" + +int ViEAutoTest::ViEBaseStandardTest() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViEBase Standard Test"); + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + + ViETest::Log("Starting a loopback call..."); + + VideoEngine* ptrViE = NULL; + ptrViE = VideoEngine::Create(); + numberOfErrors += ViETest::TestError(ptrViE != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); +#ifdef ANDROID + error = ptrViE->SetTraceFile("/sdcard/ViEBaseStandardTest.txt"); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); +#else + error = ptrViE->SetTraceFile("ViEBaseStandardTest.txt"); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +#endif + ViEBase* ptrViEBase = ViEBase::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViEBase != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + error = ptrViEBase->Init(); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + //*************************************************************** + // Engine ready. Begin testing class + //*************************************************************** + + int videoChannel = -1; + error = ptrViEBase->CreateChannel(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViECapture* ptrViECapture = ViECapture::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViECapture != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + VideoCaptureModule* vcpm(NULL); + + const unsigned int KMaxDeviceNameLength = 128; + const unsigned int KMaxUniqueIdLength = 256; + WebRtc_UWord8 deviceName[KMaxDeviceNameLength]; + memset(deviceName, 0, KMaxDeviceNameLength); + WebRtc_UWord8 uniqueId[KMaxUniqueIdLength]; + memset(uniqueId, 0, KMaxUniqueIdLength); + + bool captureDeviceSet = false; + int captureId = 0; + VideoCaptureModule::DeviceInfo* devInfo = + VideoCaptureModule::CreateDeviceInfo(0); + + for (unsigned int captureIdx = 0; + captureIdx < devInfo->NumberOfDevices(); + captureIdx++) + { + error = devInfo->GetDeviceName(captureIdx, deviceName, + KMaxDeviceNameLength, uniqueId, + KMaxUniqueIdLength); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + vcpm = VideoCaptureModule::Create(4571, uniqueId); + numberOfErrors += ViETest::TestError(vcpm != NULL, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViECapture->AllocateCaptureDevice(*vcpm, captureId); + if (error == 0) + { + ViETest::Log("Using capture device: %s, captureId: %d.", + deviceName, captureId); + captureDeviceSet = true; + break; + } + else + { + VideoCaptureModule::Destroy(vcpm); + vcpm = NULL; + } + } + VideoCaptureModule::DestroyDeviceInfo(devInfo); + + numberOfErrors+= ViETest::TestError( + captureDeviceSet, + "ERROR: %s at line %d - Could not set capture device", + __FUNCTION__, __LINE__); + + error = ptrViECapture->ConnectCaptureDevice(captureId, videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViECapture->StartCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViERTP_RTCP* ptrViERtpRtcp = ViERTP_RTCP::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViE != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + error = ptrViERtpRtcp->SetRTCPStatus(videoChannel, kRtcpCompound_RFC4585); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERtpRtcp->SetKeyFrameRequestMethod(videoChannel, + kViEKeyFrameRequestPliRtcp); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERtpRtcp->SetTMMBRStatus(videoChannel, true); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViERender* ptrViERender = ViERender::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViERender != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + error = ptrViERender->RegisterVideoRenderModule(*_vrm1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->AddRenderer(captureId, _window1, 0, 0.0, 0.0, 1.0, + 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->StartRender(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + + error = ptrViERender->RegisterVideoRenderModule(*_vrm2); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->AddRenderer(videoChannel, _window2, 1, 0.0, 0.0, 1.0, + 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->StartRender(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViECodec* ptrViECodec = ViECodec::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViECodec != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + webrtc::VideoCodec videoCodec; + memset(&videoCodec, 0, sizeof(webrtc::VideoCodec)); + for (int idx = 0; idx < ptrViECodec->NumberOfCodecs(); idx++) + { + error = ptrViECodec->GetCodec(idx, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // try to keep the test frame size small when I420 + if (videoCodec.codecType == webrtc::kVideoCodecI420) + { + videoCodec.width = 176; + videoCodec.height = 144; + error = ptrViECodec->SetSendCodec(videoChannel, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } + + error = ptrViECodec->SetReceiveCodec(videoChannel, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } + + ViENetwork* ptrViENetwork = ViENetwork::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViENetwork != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + char version[1024] = ""; + int versionLength = 1024; + error = ptrViEBase->GetVersion(version); + + ViETest::Log("\nUsing WebRTC Video Engine version: %s", version); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + const char* ipAddress = "127.0.0.1"; + unsigned short rtpPortListen = 6000; + unsigned short rtpPortSend = 6000; + + + rtpPortListen = 6100; + rtpPortSend = 6100; + + error = ptrViENetwork->SetLocalReceiver(videoChannel, rtpPortListen); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViEBase->StartReceive(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViENetwork->SetSendDestination(videoChannel, ipAddress, + rtpPortSend); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViEBase->StartSend(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + + error = ptrViERender->MirrorRenderStream(captureId, true, false, true); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Call started + ViETest::Log("Call started"); + ViETest::Log("You should see a mirrored local preview from camera %s in " + "window 1 and the remote video in window 2.", + deviceName); + + //*************************************************************** + // Finished initializing engine. Begin testing + //*************************************************************** + + AutoTestSleep(KAutoTestSleepTimeMs); + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + // Shut down + error = ptrViEBase->StopReceive(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->StopRender(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->RemoveRenderer(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->DeRegisterVideoRenderModule(*_vrm2); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + int remainingInterfaces = 0; + + error = ptrViEBase->StopSend(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->RemoveRenderer(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->DeRegisterVideoRenderModule(*_vrm1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViECapture->StopCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViECapture->DisconnectCaptureDevice(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViECapture->ReleaseCaptureDevice(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + VideoCaptureModule::Destroy(vcpm); + vcpm = NULL; + + remainingInterfaces = ptrViECapture->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + error = ptrViEBase->DeleteChannel(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + remainingInterfaces = ptrViECodec->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + remainingInterfaces = ptrViERtpRtcp->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + remainingInterfaces = ptrViERender->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + remainingInterfaces = ptrViENetwork->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + remainingInterfaces = ptrViEBase->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + bool deleted = VideoEngine::Delete(ptrViE); + numberOfErrors += ViETest::TestError(deleted == true, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViEBase Standard Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViEBase Standard Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + return 0; +} + +int ViEAutoTest::ViEBaseExtendedTest() +{ + // Start with standard test + ViEBaseAPITest(); + ViEBaseStandardTest(); + + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViEBase Extended Test"); + + ViETest::Log(" "); + ViETest::Log(" ViEBase Extended Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + + return 0; +} + +int ViEAutoTest::ViEBaseAPITest() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViEBase API Test"); + + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + + VideoEngine* ptrViE = NULL; + ViEBase* ptrViEBase = NULL; + + // Get the ViEBase API + ptrViEBase = ViEBase::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViEBase == NULL); + + ptrViE = VideoEngine::Create(); + numberOfErrors += ViETest::TestError(ptrViE != NULL, "VideoEngine::Create"); + +#ifdef ANDROID + error = ptrViE->SetTraceFile("/sdcard/WebRTC/ViESampleCodeTrace.txt"); + numberOfErrors += ViETest::TestError(error == 0, "SetTraceFile error"); + + error = ptrViE->SetTraceFile( + "/sdcard/WebRTC/ViESampleCodeTraceEncrypted.txt"); + numberOfErrors += ViETest::TestError(error == 0, "SetTraceFile"); +#else + error = ptrViE->SetTraceFile("WebRTCViESampleCodeTrace.txt"); + numberOfErrors += ViETest::TestError(error == 0, "SetTraceFile error"); +#endif + + ptrViEBase = ViEBase::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViEBase != NULL); + + //*************************************************************** + // Engine ready. Begin testing class + //*************************************************************** + + char version[1024] = ""; + error = ptrViEBase->GetVersion(version); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViEBase->LastError(); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Create without init + int videoChannel = -1; + error = ptrViEBase->CreateChannel(videoChannel); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViEBase->Init(); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViEBase->CreateChannel(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + int videoChannel2 = -1; + error = ptrViEBase->CreateChannel(videoChannel2); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(videoChannel != videoChannel2, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + error = ptrViEBase->DeleteChannel(videoChannel2); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Channel doesn't exist + error = ptrViEBase->CreateChannel(videoChannel2, videoChannel + 1); + numberOfErrors += ViETest::TestError(error == -1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Channel doesn't exist + error = ptrViEBase->CreateChannel(videoChannel2, videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // VoiceEngine + VoiceEngine* ptrVoE = NULL; + VoEBase* ptrVoEBase = NULL; + int audioChannel = -1; + + ptrVoE = VoiceEngine::Create(); + numberOfErrors += ViETest::TestError(ptrVoE != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + ptrVoEBase = VoEBase::GetInterface(ptrVoE); + numberOfErrors += ViETest::TestError(ptrVoEBase != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + error = ptrVoEBase->Init(); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + audioChannel = ptrVoEBase->CreateChannel(); + numberOfErrors += ViETest::TestError(audioChannel != -1, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + // Connect before setting VoE + error = ptrViEBase->ConnectAudioChannel(videoChannel, audioChannel); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViEBase->SetVoiceEngine(ptrVoE); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViEBase->ConnectAudioChannel(videoChannel, audioChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + error = ptrViEBase->DisconnectAudioChannel(videoChannel + 5); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViEBase->DisconnectAudioChannel(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViEBase->SetVoiceEngine(NULL); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViEBase* ptrViEBase2 = ViEBase::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViEBase != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + int remainingInterfaces = ptrViEBase->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 1, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + bool vieDeleted = VideoEngine::Delete(ptrViE); + numberOfErrors += ViETest::TestError(vieDeleted == false, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + remainingInterfaces = ptrViEBase->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + vieDeleted = VideoEngine::Delete(ptrViE); + numberOfErrors += ViETest::TestError(vieDeleted == true, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + if (numberOfErrors > 0) + { + ViETest::Log(" "); + ViETest::Log(" ERROR ViEBase API Test FAILED! "); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViEBase API Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + + return 0; +} diff --git a/video_engine/main/test/AutoTest/source/vie_autotest_capture.cc b/video_engine/main/test/AutoTest/source/vie_autotest_capture.cc new file mode 100644 index 0000000000..c5054ab24c --- /dev/null +++ b/video_engine/main/test/AutoTest/source/vie_autotest_capture.cc @@ -0,0 +1,808 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_autotest_capture.cc + */ + +#include "vie_autotest_defines.h" +#include "vie_autotest.h" +#include "engine_configurations.h" + +#include "common_types.h" +#include "voe_base.h" +#include "vie_base.h" +#include "vie_capture.h" +#include "vie_codec.h" +#include "vie_network.h" +#include "vie_render.h" +#include "vie_rtp_rtcp.h" +#include "tick_util.h" + +#include "tb_interfaces.h" +#include "tb_video_channel.h" +#include "video_capture.h" + +class CaptureObserver: public ViECaptureObserver +{ +public: + CaptureObserver() : + _brightness(Normal), _alarm(AlarmCleared), _frameRate(0) + {} + + virtual void BrightnessAlarm(const int captureId, + const Brightness brightness) + { + _brightness = brightness; + switch (brightness) + { + case Normal: + ViETest::Log(" BrightnessAlarm Normal"); + break; + case Bright: + ViETest::Log(" BrightnessAlarm Bright"); + break; + case Dark: + ViETest::Log(" BrightnessAlarm Dark"); + break; + default: + assert(!"Unknown brightness alarm"); + } + } + + virtual void CapturedFrameRate(const int captureId, + const unsigned char frameRate) + { + ViETest::Log(" CapturedFrameRate %u", frameRate); + _frameRate = frameRate; + } + + virtual void NoPictureAlarm(const int captureId, const CaptureAlarm alarm) + { + _alarm = alarm; + if (alarm == AlarmRaised) + { + ViETest::Log("NoPictureAlarm CARaised."); + } + else + { + ViETest::Log("NoPictureAlarm CACleared."); + } + } + + Brightness _brightness; + CaptureAlarm _alarm; + unsigned char _frameRate; +}; + +class CaptureEffectFilter: public ViEEffectFilter +{ +public: + CaptureEffectFilter(int reqWidth, int reqHeight, int& numberOfErrors) : + _numberOfCapturedFrames(0), + _reqWidth(reqWidth), + _reqHeight(reqHeight), + _numberOfErrors(numberOfErrors) + { + } + // Implements ViEEffectFilter + virtual int Transform(int size, unsigned char* frameBuffer, + unsigned int timeStamp90KHz, unsigned int width, + unsigned int height) + { + _numberOfErrors += ViETest::TestError( + frameBuffer != 0 + && width == _reqWidth + && height == _reqHeight, + "ERROR: %s at line %d", __FUNCTION__, __LINE__); + ++_numberOfCapturedFrames; + return 0; + } + + int _numberOfCapturedFrames; + +protected: + int _reqWidth; + int _reqHeight; + int& _numberOfErrors; +}; + +int ViEAutoTest::ViECaptureStandardTest() +{ + int numberOfErrors = 0; + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViECapture StandardTest Test\n"); + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + + //*************************************************************** + // Engine ready. Begin testing class + //*************************************************************** + + + int error = 0; + tbInterfaces ViE("WebRTCViECapture_Standard", numberOfErrors); + + VideoCaptureModule::DeviceInfo* devInfo = + VideoCaptureModule::CreateDeviceInfo(0); + + int numberOfCaptureDevices = devInfo->NumberOfDevices(); + ViETest::Log("Number of capture devices %d", numberOfCaptureDevices); + numberOfErrors += ViETest::TestError(numberOfCaptureDevices > 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + int captureDeviceId[10]; + VideoCaptureModule* vcpms[10]; + memset(vcpms, 0, sizeof(vcpms)); + + //Check capabilities + for (int deviceIndex = 0; + deviceIndex < numberOfCaptureDevices; + ++deviceIndex) + { + WebRtc_UWord8 deviceName[128]; + WebRtc_UWord8 deviceUniqueName[512]; + + error = devInfo->GetDeviceName(deviceIndex, deviceName, + sizeof(deviceName), deviceUniqueName, + sizeof(deviceUniqueName)); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log("Found capture device %s\nUnique name %s", deviceName, + deviceUniqueName); + +// not supported on MAC (is part of capture capabilites +#if !defined(WEBRTC_LINUX) && !defined(WEBRTC_MAC_INTEL) + error = ViE.ptrViECapture->ShowCaptureSettingsDialogBox( + (char*) deviceUniqueName, + (unsigned int) (strlen((char*) deviceUniqueName)), + "WebRTCViECapture StandardTest"); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +#endif + +#if !defined(WEBRTC_MAC_INTEL) // these functions will return -1 + unsigned int numberOfCapabilities = + devInfo->NumberOfCapabilities(deviceUniqueName); + numberOfErrors += ViETest::TestError(numberOfCapabilities > 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + for (unsigned int capIndex = 0; + capIndex < numberOfCapabilities; + ++capIndex) + { + VideoCaptureCapability capability; + error = devInfo->GetCapability(deviceUniqueName, capIndex, + capability); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log("Capture capability %d (of %u)", capIndex + 1, + numberOfCapabilities); + ViETest::Log("witdh %d, height %d, frame rate %d", + capability.width, capability.height, capability.maxFPS); + ViETest::Log("expected delay %d, color type %d, encoding %d", + capability.expectedCaptureDelay, capability.rawType, + capability.codecType); + numberOfErrors += ViETest::TestError( + capability.width > 0 + && capability.height > 0 + && capability.maxFPS >= 0 + && capability.expectedCaptureDelay > 0, + "ERROR: %s at line %d", __FUNCTION__, __LINE__); + } +#endif + } +#if !defined(WEBRTC_MAC_INTEL) + // "capture capability functios" are not supported on WEBRTC_MAC_INTEL + //Check allocation. Try to allocate them all after each other. + + for (int deviceIndex = 0; + deviceIndex < numberOfCaptureDevices; + ++deviceIndex) + { + WebRtc_UWord8 deviceName[128]; + WebRtc_UWord8 deviceUniqueName[512]; + + error = devInfo->GetDeviceName(deviceIndex, deviceName, + sizeof(deviceName), deviceUniqueName, + sizeof(deviceUniqueName)); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + VideoCaptureModule* vcpm = VideoCaptureModule::Create(deviceIndex, + deviceUniqueName); + numberOfErrors += ViETest::TestError(vcpm != NULL, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + vcpms[deviceIndex] = vcpm; + + error = ViE.ptrViECapture->AllocateCaptureDevice( + *vcpm, captureDeviceId[deviceIndex]); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + VideoCaptureCapability capability; + error = devInfo->GetCapability(deviceUniqueName, 0, capability); + + // Test that the camera select the closest capability to the selected + // widht and height. + CaptureEffectFilter filter(capability.width, capability.height, + numberOfErrors); + error = ViE.ptrViEImageProcess->RegisterCaptureEffectFilter( + captureDeviceId[deviceIndex], filter); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("Testing Device %s capability width %d height %d", + deviceUniqueName, capability.width, capability.height); + capability.height = capability.height - 2; + capability.width = capability.width - 2; + + CaptureCapability vieCapability; + vieCapability.width = capability.width; + vieCapability.height = capability.height; + vieCapability.codecType = capability.codecType; + vieCapability.maxFPS = capability.maxFPS; + vieCapability.rawType = capability.rawType; + + error = ViE.ptrViECapture->StartCapture(captureDeviceId[deviceIndex], + vieCapability); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + TickTime startTime = TickTime::Now(); + + while (filter._numberOfCapturedFrames < 10 + && (TickTime::Now() - startTime).Milliseconds() < 10000) + { + AutoTestSleep(100); + } + numberOfErrors += ViETest::TestError(filter._numberOfCapturedFrames + >= 10, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->DeregisterCaptureEffectFilter( + captureDeviceId[deviceIndex]); + +#ifdef ANDROID // Can only allocate one camera at the time on Android + error = ViE.ptrViECapture->StopCapture(captureDeviceId[deviceIndex]); + numberOfErrors += ViETest::TestError(error==0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViECapture->ReleaseCaptureDevice( + captureDeviceId[deviceIndex]); + numberOfErrors += ViETest::TestError(error==0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +#endif + } + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + + // stop all started capture devices + for (int deviceIndex = 0; deviceIndex < numberOfCaptureDevices; ++deviceIndex) + { + error = ViE.ptrViECapture->StopCapture(captureDeviceId[deviceIndex]); +#ifdef ANDROID + // Camera already stoped on Android since we can only allocate one + // camera at the time. + numberOfErrors += ViETest::TestError(error==-1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +#else + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +#endif + + error = ViE.ptrViECapture->ReleaseCaptureDevice( + captureDeviceId[deviceIndex]); +#ifdef ANDROID + // Camera already stoped on Android since we can only allocate one + // camera at the time + numberOfErrors += ViETest::TestError(error==-1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +#else + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +#endif + VideoCaptureModule::Destroy(vcpms[deviceIndex]); + } +#endif + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViECapture Standard Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + VideoCaptureModule::DestroyDeviceInfo(devInfo); + + ViETest::Log(" "); + ViETest::Log(" ViECapture Standard Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + + return 0; +} + +int ViEAutoTest::ViECaptureExtendedTest() +{ + + // Test + int numberOfErrors = 0; + numberOfErrors += ViECaptureStandardTest(); + numberOfErrors += ViECaptureAPITest(); + numberOfErrors += ViECaptureExternalCaptureTest(); + + return 0; +} + +int ViEAutoTest::ViECaptureAPITest() +{ + int numberOfErrors = 0; + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViECapture API Test\n"); + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + + //*************************************************************** + // Engine ready. Begin testing class + //*************************************************************** + + int error = 0; + tbInterfaces ViE("WebRTCViECapture_API", numberOfErrors); + + ViE.ptrViECapture->NumberOfCaptureDevices(); + + WebRtc_UWord8 deviceName[128]; + WebRtc_UWord8 deviceUniqueName[512]; + int captureId = 0; + int dummy = 0; + + VideoCaptureModule::DeviceInfo* devInfo = + VideoCaptureModule::CreateDeviceInfo(0); + numberOfErrors += ViETest::TestError(devInfo != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + // Get the first capture device + error = devInfo->GetDeviceName(0, deviceName, sizeof(deviceName), + deviceUniqueName, sizeof(deviceUniqueName)); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + VideoCaptureModule* vcpm = VideoCaptureModule::Create(0, deviceUniqueName); + numberOfErrors += ViETest::TestError(vcpm != NULL, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Allocate capture device + error = ViE.ptrViECapture->AllocateCaptureDevice(*vcpm, captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Start the capture device + error = ViE.ptrViECapture->StartCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Start again. Should fail + error = ViE.ptrViECapture->StartCapture(captureId); + numberOfErrors += ViETest::TestError(error == -1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(ViE.LastError() + == kViECaptureDeviceAlreadyStarted, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Start invalid capture device + error = ViE.ptrViECapture->StartCapture(captureId + 1); + numberOfErrors += ViETest::TestError(error == -1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(ViE.LastError() + == kViECaptureDeviceDoesnNotExist, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Stop invalide capture device + error = ViE.ptrViECapture->StopCapture(captureId + 1); + numberOfErrors += ViETest::TestError(error == -1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(ViE.LastError() + == kViECaptureDeviceDoesnNotExist, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Stop the capture device + error = ViE.ptrViECapture->StopCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Stop the capture device again + error = ViE.ptrViECapture->StopCapture(captureId); + numberOfErrors += ViETest::TestError(error == -1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(ViE.LastError() + == kViECaptureDeviceNotStarted, "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + // Connect to invalid channel + error = ViE.ptrViECapture->ConnectCaptureDevice(captureId, 0); + numberOfErrors += ViETest::TestError(error == -1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(ViE.LastError() + == kViECaptureDeviceInvalidChannelId, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + tbVideoChannel channel(ViE, numberOfErrors); + + // Connect invalid captureId + error = ViE.ptrViECapture->ConnectCaptureDevice(captureId + 1, + channel.videoChannel); + numberOfErrors += ViETest::TestError(error == -1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(ViE.LastError() + == kViECaptureDeviceDoesnNotExist, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + // Connect the capture device to the channel + error = ViE.ptrViECapture->ConnectCaptureDevice(captureId, + channel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Connect the channel again + error = ViE.ptrViECapture->ConnectCaptureDevice(captureId, + channel.videoChannel); + numberOfErrors += ViETest::TestError(error == -1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(ViE.LastError() + == kViECaptureDeviceAlreadyConnected, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Start the capture device + error = ViE.ptrViECapture->StartCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Release invalid capture device + error = ViE.ptrViECapture->ReleaseCaptureDevice(captureId + 1); + numberOfErrors += ViETest::TestError(error == -1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(ViE.LastError() + == kViECaptureDeviceDoesnNotExist, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Release the capture device + error = ViE.ptrViECapture->ReleaseCaptureDevice(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Release the capture device again + error = ViE.ptrViECapture->ReleaseCaptureDevice(captureId); + numberOfErrors += ViETest::TestError(error == -1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(ViE.LastError() + == kViECaptureDeviceDoesnNotExist, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Test GetOrientation + VideoCaptureRotation orientation; + WebRtc_UWord8 dummy_name[5]; + error = devInfo->GetOrientation(dummy_name, orientation); + numberOfErrors += ViETest::TestError(error == -1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + //Test SetRotation + error = ViE.ptrViECapture->SetRotateCapturedFrames(captureId, + RotateCapturedFrame_90); + numberOfErrors += ViETest::TestError(error == -1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(ViE.LastError() + == kViECaptureDeviceDoesnNotExist, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Allocate capture device + error = ViE.ptrViECapture->AllocateCaptureDevice(*vcpm, captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViECapture->SetRotateCapturedFrames(captureId, + RotateCapturedFrame_0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViECapture->SetRotateCapturedFrames(captureId, + RotateCapturedFrame_90); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViECapture->SetRotateCapturedFrames(captureId, + RotateCapturedFrame_180); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViECapture->SetRotateCapturedFrames(captureId, + RotateCapturedFrame_270); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Release the capture device + error = ViE.ptrViECapture->ReleaseCaptureDevice(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + VideoCaptureModule::DestroyDeviceInfo(devInfo); + VideoCaptureModule::Destroy(vcpm); + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR WebRTCViECapture API Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" WebRTCViECapture API Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + + return numberOfErrors; +} + +int ViEAutoTest::ViECaptureExternalCaptureTest() +{ + int numberOfErrors = 0; + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" WebRTCViECapture External Capture Test\n"); + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + + int error = 0; + tbInterfaces ViE("WebRTCViECapture_ExternalCapture", numberOfErrors); + tbVideoChannel channel(ViE, numberOfErrors); + channel.StartReceive(); + channel.StartSend(); + + VideoCaptureExternal* externalCapture; + int captureId = 0; + + // Allocate the external capture device + VideoCaptureModule* vcpm = VideoCaptureModule::Create(0, externalCapture); + numberOfErrors += ViETest::TestError(vcpm != NULL, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViECapture->AllocateCaptureDevice(*vcpm, captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(externalCapture != 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + // Connect the capture device to the channel + error = ViE.ptrViECapture->ConnectCaptureDevice(captureId, + channel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Render the local capture + error = ViE.ptrViERender->AddRenderer(captureId, _window1, 1, 0.0, 0.0, + 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Render the remote capture + error = ViE.ptrViERender->AddRenderer(channel.videoChannel, _window2, 1, + 0.0, 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->StartRender(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->StartRender(channel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Register observer + CaptureObserver observer; + error = ViE.ptrViECapture->RegisterObserver(captureId, observer); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Enable brighness alarm + error = ViE.ptrViECapture->EnableBrightnessAlarm(captureId, true); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + CaptureEffectFilter effectFilter(176, 144, numberOfErrors); + error = ViE.ptrViEImageProcess->RegisterCaptureEffectFilter(captureId, + effectFilter); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Call started + + ViETest::Log("You should see local preview from external capture\n" + "in window 1 and the remote video in window 2.\n"); + + //*************************************************************** + // Engine ready. Begin testing class + //*************************************************************** + + const unsigned int videoFrameLength = (176 * 144 * 3) / 2; + unsigned char* videoFrame = new unsigned char[videoFrameLength]; + memset(videoFrame, 128, 176 * 144); + + // TODO: Find a file to use for testing. + // FILE* foreman = OpenTestFile("akiyo_qcif.yuv"); + // if (foreman == NULL) + // { + // ViETest::Log("Failed to open file akiyo_qcif.yuv"); + // } + + int frameCount = 0; + VideoCaptureCapability capability; + capability.width = 176; + capability.height = 144; + capability.rawType = kVideoI420; + + ViETest::Log("Testing external capturing and frame rate callbacks."); + // TODO: Change when using a real file! + // while (fread(videoFrame, videoFrameLength, 1, foreman) == 1) + while (frameCount < 120) + { + + externalCapture->IncomingFrame( + videoFrame, videoFrameLength, capability, + TickTime::Now().MillisecondTimestamp()); + AutoTestSleep(33); + + if (effectFilter._numberOfCapturedFrames > 2) + { + // make sure brigthness or no picture alarm has not been triggered + numberOfErrors += ViETest::TestError(observer._brightness == Normal, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError( + observer._alarm == AlarmCleared, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } + frameCount++; + } + + // Test brightness alarm + // Test bright image + for (int i = 0; i < 176 * 144; ++i) + { + if (videoFrame[i] <= 155) + videoFrame[i] = videoFrame[i] + 100; + else + videoFrame[i] = 255; + } + ViETest::Log("Testing Brighness alarm"); + for (int frame = 0; frame < 30; ++frame) + { + externalCapture->IncomingFrame( + videoFrame, videoFrameLength, capability, + TickTime::Now().MillisecondTimestamp()); + AutoTestSleep(33); + } + numberOfErrors += ViETest::TestError(observer._brightness == Bright, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + // Test Dark image + for (int i = 0; i < 176 * 144; ++i) + { + videoFrame[i] = videoFrame[i] > 200 ? videoFrame[i] - 200 : 0; + } + for (int frame = 0; frame < 30; ++frame) + { + externalCapture->IncomingFrame( + videoFrame, videoFrameLength, capability, + TickTime::Now().MillisecondTimestamp()); + AutoTestSleep(33); + } + numberOfErrors += ViETest::TestError(observer._brightness == Dark, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + // Test that frames were played + numberOfErrors += ViETest::TestError( + effectFilter._numberOfCapturedFrames > 150, + "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + // Test frame rate callback + numberOfErrors += ViETest::TestError(observer._frameRate >= 29 + && observer._frameRate <= 30, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + // Test no picture alarm + ViETest::Log("Testing NoPictureAlarm."); + AutoTestSleep(1050); + numberOfErrors += ViETest::TestError(observer._alarm == AlarmRaised, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + for (int frame = 0; frame < 10; ++frame) + { + externalCapture->IncomingFrame( + videoFrame, videoFrameLength, capability, + TickTime::Now().MillisecondTimestamp()); + AutoTestSleep(33); + } + numberOfErrors += ViETest::TestError(observer._alarm == AlarmCleared, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + delete videoFrame; + + // Release the capture device + error = ViE.ptrViECapture->ReleaseCaptureDevice(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Release the capture device again + error = ViE.ptrViECapture->ReleaseCaptureDevice(captureId); + numberOfErrors += ViETest::TestError(error == -1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError( + ViE.LastError() == kViECaptureDeviceDoesnNotExist, + "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR WebRTCViECapture External Capture Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" WebRTCViECapture External Capture Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + + return numberOfErrors; +} diff --git a/video_engine/main/test/AutoTest/source/vie_autotest_codec.cc b/video_engine/main/test/AutoTest/source/vie_autotest_codec.cc new file mode 100644 index 0000000000..9bbbfd2ea9 --- /dev/null +++ b/video_engine/main/test/AutoTest/source/vie_autotest_codec.cc @@ -0,0 +1,1298 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// +// vie_autotest_codec.cc +// + +#include "vie_autotest_defines.h" +#include "vie_autotest.h" +#include "engine_configurations.h" + +#include "common_types.h" +#include "tb_capture_device.h" +#include "tb_I420_codec.h" +#include "tb_interfaces.h" +#include "tb_video_channel.h" +#include "vie_base.h" +#include "vie_capture.h" +#include "vie_codec.h" +#include "vie_network.h" +#include "vie_render.h" +#include "vie_rtp_rtcp.h" +#include "voe_base.h" + +class ViEAutotestCodecObserever: public ViEEncoderObserver, + public ViEDecoderObserver +{ +public: + int incomingCodecCalled; + int incomingRatecalled; + int outgoingRatecalled; + + unsigned char lastPayloadType; + unsigned short lastWidth; + unsigned short lastHeight; + + unsigned int lastOutgoingFramerate; + unsigned int lastOutgoingBitrate; + unsigned int lastIncomingFramerate; + unsigned int lastIncomingBitrate; + + webrtc::VideoCodec incomingCodec; + + ViEAutotestCodecObserever() + { + incomingCodecCalled = 0; + incomingRatecalled = 0; + outgoingRatecalled = 0; + lastPayloadType = 0; + lastWidth = 0; + lastHeight = 0; + lastOutgoingFramerate = 0; + lastOutgoingBitrate = 0; + lastIncomingFramerate = 0; + lastIncomingBitrate = 0; + memset(&incomingCodec, 0, sizeof(incomingCodec)); + } + virtual void IncomingCodecChanged(const int videoChannel, + const webrtc::VideoCodec& videoCodec) + { + incomingCodecCalled++; + lastPayloadType = videoCodec.plType; + lastWidth = videoCodec.width; + lastHeight = videoCodec.height; + + memcpy(&incomingCodec, &videoCodec, sizeof(videoCodec)); + } + + virtual void IncomingRate(const int videoChannel, + const unsigned int framerate, + const unsigned int bitrate) + { + incomingRatecalled++; + lastIncomingFramerate += framerate; + lastIncomingBitrate += bitrate; + } + + virtual void OutgoingRate(const int videoChannel, + const unsigned int framerate, + const unsigned int bitrate) + { + outgoingRatecalled++; + lastOutgoingFramerate += framerate; + lastOutgoingBitrate += bitrate; + } + + virtual void RequestNewKeyFrame(const int videoChannel) + { + } +}; + +class ViEAutoTestEffectFilter: public ViEEffectFilter +{ +public: + int numFrames; + ViEAutoTestEffectFilter() + { + numFrames = 0; + } + ~ViEAutoTestEffectFilter() + { + } + + virtual int Transform(int size, unsigned char* frameBuffer, + unsigned int timeStamp90KHz, unsigned int width, + unsigned int height) + { + numFrames++; + return 0; + } +}; + +int ViEAutoTest::ViECodecStandardTest() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViECodec Standard Test\n"); + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + + tbInterfaces interfaces = tbInterfaces("ViECodecStandardTest", + numberOfErrors); + + tbCaptureDevice captureDevice = tbCaptureDevice(interfaces, numberOfErrors); + int captureId = captureDevice.captureId; + + VideoEngine* ptrViE = interfaces.ptrViE; + ViEBase* ptrViEBase = interfaces.ptrViEBase; + ViECapture* ptrViECapture = interfaces.ptrViECapture; + ViERender* ptrViERender = interfaces.ptrViERender; + ViECodec* ptrViECodec = interfaces.ptrViECodec; + ViERTP_RTCP* ptrViERtpRtcp = interfaces.ptrViERtpRtcp; + ViENetwork* ptrViENetwork = interfaces.ptrViENetwork; + + int videoChannel = -1; + error = ptrViEBase->CreateChannel(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViECapture->ConnectCaptureDevice(captureId, videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERtpRtcp->SetRTCPStatus(videoChannel, kRtcpCompound_RFC4585); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERtpRtcp->SetKeyFrameRequestMethod(videoChannel, + kViEKeyFrameRequestPliRtcp); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERtpRtcp->SetTMMBRStatus(videoChannel, true); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->AddRenderer(captureId, _window1, 0, 0.0, 0.0, 1.0, + 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->AddRenderer(videoChannel, _window2, 1, 0.0, 0.0, 1.0, + 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->StartRender(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->StartRender(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + //*************************************************************** + // Engine ready. Begin testing class + //*************************************************************** + + + webrtc::VideoCodec videoCodec; + memset(&videoCodec, 0, sizeof(webrtc::VideoCodec)); + for (int idx = 0; idx < ptrViECodec->NumberOfCodecs(); idx++) + { + error = ptrViECodec->GetCodec(idx, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + if (videoCodec.codecType != webrtc::kVideoCodecH263 + && videoCodec.codecType != webrtc::kVideoCodecI420) + { + videoCodec.width = 640; + videoCodec.height = 480; + } + if(videoCodec.codecType==kVideoCodecI420) + { + videoCodec.width=176; + videoCodec.height=144; + } + error = ptrViECodec->SetReceiveCodec(videoChannel, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } + + for (int idx = 0; idx < ptrViECodec->NumberOfCodecs(); idx++) + { + error = ptrViECodec->GetCodec(idx, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + if (videoCodec.codecType == webrtc::kVideoCodecVP8) + { + error = ptrViECodec->SetSendCodec(videoChannel, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + break; + } + } + + const char* ipAddress = "127.0.0.1"; + const unsigned short rtpPort = 6000; + error = ptrViENetwork->SetLocalReceiver(videoChannel, rtpPort); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViEBase->StartReceive(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViENetwork->SetSendDestination(videoChannel, ipAddress, rtpPort); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViEBase->StartSend(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // + // Make sure all codecs runs + // + { + ViEImageProcess* ptrViEImageProcess = ViEImageProcess::GetInterface(ptrViE); + ViEAutotestCodecObserever codecObserver; + error = ptrViECodec->RegisterDecoderObserver(videoChannel, + codecObserver); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("Loop through all codecs for %d seconds", + KAutoTestSleepTimeMs / 1000); + for (int idx = 0; idx < ptrViECodec->NumberOfCodecs() - 2; idx++) + { + error = ptrViECodec->GetCodec(idx, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + if (videoCodec.codecType != webrtc::kVideoCodecMPEG4) + { + if(videoCodec.codecType==kVideoCodecI420) // Lower resolution to sockkets keep up. + { + videoCodec.width=176; + videoCodec.height=144; + videoCodec.maxFramerate=15; + } + error = ptrViECodec->SetSendCodec(videoChannel, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log("\t %d. %s", idx, videoCodec.plName); + + ViEAutoTestEffectFilter frameCounter; + error = ptrViEImageProcess->RegisterRenderEffectFilter( + videoChannel, + frameCounter); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + AutoTestSleep( KAutoTestSleepTimeMs); + + // Verify we've received and decoded correct payload + numberOfErrors += ViETest::TestError( + codecObserver.incomingCodec.codecType + == videoCodec.codecType, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + int maxNumberOfRenderedFrames = videoCodec.maxFramerate + * KAutoTestSleepTimeMs / 1000; + + if(videoCodec.codecType==kVideoCodecI420) + { + // Due to that I420 needs a huge bandwidht- rate control can set frame rate very low. + // This happen since we use the same channel as we just tested with vp8. + numberOfErrors += ViETest::TestError(frameCounter.numFrames>0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + } + else + { + +#ifdef ANDROID + // To get the autotest to pass on some slow devices + numberOfErrors += ViETest::TestError(frameCounter.numFrames + > maxNumberOfRenderedFrames/6, // Safety margin due to bitrate + "ERROR: %s at line %d", __FUNCTION__, __LINE__); +#else + numberOfErrors += ViETest::TestError(frameCounter.numFrames + > maxNumberOfRenderedFrames / 4, // Safety margin due to bitrate + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); +#endif + } + + error = ptrViEImageProcess->DeregisterRenderEffectFilter( + videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } + else + { + ViETest::Log("\t %d. %s not tested", idx, videoCodec.plName); + } + } + ptrViEImageProcess->Release(); + error = ptrViECodec->DeregisterDecoderObserver(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log("Done!"); + } + + // + // Callbacks + // + + ViEAutotestCodecObserever codecObserver; + error = ptrViECodec->RegisterEncoderObserver(videoChannel, codecObserver); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ptrViECodec->RegisterDecoderObserver(videoChannel, codecObserver); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("\nTesting codec callbacks..."); + + for (int idx = 0; idx < ptrViECodec->NumberOfCodecs(); idx++) + { + error = ptrViECodec->GetCodec(idx, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + if (videoCodec.codecType == webrtc::kVideoCodecVP8) + { + error = ptrViECodec->SetSendCodec(videoChannel, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + break; + } + } + AutoTestSleep(KAutoTestSleepTimeMs); + + error = ptrViEBase->StopSend(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViECodec->DeregisterEncoderObserver(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ptrViECodec->DeregisterDecoderObserver(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + numberOfErrors += ViETest::TestError(codecObserver.incomingCodecCalled > 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + numberOfErrors += ViETest::TestError(codecObserver.incomingRatecalled > 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + numberOfErrors += ViETest::TestError(codecObserver.outgoingRatecalled > 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + error = ptrViEBase->StopReceive(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViEBase->StopSend(videoChannel); // Already stopped + numberOfErrors += ViETest::TestError(error == -1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->StopRender(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->RemoveRenderer(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->RemoveRenderer(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViECapture->DisconnectCaptureDevice(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViEBase->DeleteChannel(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + + + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViECodec Standard Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViECodec Standard Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + return 0; +} + +int ViEAutoTest::ViECodecExtendedTest() +{ + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + + { + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViECodec Extended Test\n"); + + numberOfErrors = ViECodecAPITest(); + numberOfErrors += ViECodecStandardTest(); + numberOfErrors += ViECodecExternalCodecTest(); + + tbInterfaces interfaces = tbInterfaces("ViECodecExtendedTest", + numberOfErrors); + VideoEngine* ptrViE = interfaces.ptrViE; + ViEBase* ptrViEBase = interfaces.ptrViEBase; + ViECapture* ptrViECapture = interfaces.ptrViECapture; + ViERender* ptrViERender = interfaces.ptrViERender; + ViECodec* ptrViECodec = interfaces.ptrViECodec; + ViERTP_RTCP* ptrViERtpRtcp = interfaces.ptrViERtpRtcp; + ViENetwork* ptrViENetwork = interfaces.ptrViENetwork; + + tbCaptureDevice captureDevice = tbCaptureDevice(interfaces, + numberOfErrors); + int captureId = captureDevice.captureId; + + int videoChannel = -1; + error = ptrViEBase->CreateChannel(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViECapture->ConnectCaptureDevice(captureId, videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERtpRtcp->SetRTCPStatus(videoChannel, + kRtcpCompound_RFC4585); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error + = ptrViERtpRtcp->SetKeyFrameRequestMethod(videoChannel, + kViEKeyFrameRequestPliRtcp); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERtpRtcp->SetTMMBRStatus(videoChannel, true); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->AddRenderer(captureId, _window1, 0, 0.0, 0.0, + 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->AddRenderer(videoChannel, _window2, 1, 0.0, 0.0, + 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->StartRender(captureId); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->StartRender(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + webrtc::VideoCodec videoCodec; + memset(&videoCodec, 0, sizeof(webrtc::VideoCodec)); + for (int idx = 0; idx < ptrViECodec->NumberOfCodecs(); idx++) + { + error = ptrViECodec->GetCodec(idx, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + if (videoCodec.codecType != webrtc::kVideoCodecH263 + && videoCodec.codecType != webrtc::kVideoCodecI420) + { + videoCodec.width = 640; + videoCodec.height = 480; + } + error = ptrViECodec->SetReceiveCodec(videoChannel, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } + + const char* ipAddress = "127.0.0.1"; + const unsigned short rtpPort = 6000; + error = ptrViENetwork->SetLocalReceiver(videoChannel, rtpPort); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViEBase->StartReceive(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViENetwork->SetSendDestination(videoChannel, ipAddress, + rtpPort); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViEBase->StartSend(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // + // Codec specific tests + // + memset(&videoCodec, 0, sizeof(webrtc::VideoCodec)); + error = ptrViEBase->StopSend(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViEAutotestCodecObserever codecObserver; + error = ptrViECodec->RegisterEncoderObserver(videoChannel, + codecObserver); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ptrViECodec->RegisterDecoderObserver(videoChannel, + codecObserver); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + error = ptrViEBase->StopReceive(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViEBase->StopSend(videoChannel); // Already stopped + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->StopRender(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->RemoveRenderer(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViERender->RemoveRenderer(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViECapture->DisconnectCaptureDevice(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViEBase->DeleteChannel(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } + + // + // Default channel + // + { + // Create VIE + tbInterfaces ViE("ViECodecExtendedTest2", numberOfErrors); + // Create a capture device + tbCaptureDevice tbCapture(ViE, numberOfErrors); + + // Create channel 1 + int videoChannel1 = -1; + error = ViE.ptrViEBase->CreateChannel(videoChannel1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + unsigned short rtpPort1 = 12000; + error = ViE.ptrViENetwork->SetLocalReceiver(videoChannel1, rtpPort1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendDestination(videoChannel1, + "127.0.0.1", rtpPort1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + tbCapture.ConnectTo(videoChannel1); + + error + = ViE.ptrViERtpRtcp->SetKeyFrameRequestMethod( + videoChannel1, kViEKeyFrameRequestPliRtcp); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->AddRenderer(videoChannel1, _window1, 0, 0.0, + 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->StartRender(videoChannel1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViEAutotestCodecObserever codecObserver1; + error = ViE.ptrViECodec->RegisterEncoderObserver(videoChannel1, + codecObserver1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViECodec->RegisterDecoderObserver(videoChannel1, + codecObserver1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Set Send codec + unsigned short codecWidth = 176; + unsigned short codecHeight = 144; + bool codecSet = false; + webrtc::VideoCodec videoCodec; + for (int idx = 0; idx < ViE.ptrViECodec->NumberOfCodecs(); idx++) + { + error = ViE.ptrViECodec->GetCodec(idx, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViECodec->SetReceiveCodec(videoChannel1, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + if (videoCodec.codecType == webrtc::kVideoCodecVP8) + { + videoCodec.width = codecWidth; + videoCodec.height = codecHeight; + videoCodec.startBitrate = 200; + videoCodec.maxBitrate = 300; + error + = ViE.ptrViECodec->SetSendCodec(videoChannel1, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + codecSet = true; + break; + } + } + numberOfErrors += ViETest::TestError(codecSet, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEBase->StartSend(videoChannel1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEBase->StartReceive(videoChannel1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Create channel 2, based on channel 1 + int videoChannel2 = -1; + error = ViE.ptrViEBase->CreateChannel(videoChannel2, videoChannel1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(videoChannel1 != videoChannel2, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERtpRtcp->SetKeyFrameRequestMethod( + videoChannel2, kViEKeyFrameRequestPliRtcp); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Prepare receive codecs + for (int idx = 0; idx < ViE.ptrViECodec->NumberOfCodecs(); idx++) + { + error = ViE.ptrViECodec->GetCodec(idx, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViECodec->SetReceiveCodec(videoChannel2, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } + + ViEAutotestCodecObserever codecObserver2; + error = ViE.ptrViECodec->RegisterDecoderObserver(videoChannel2, + codecObserver2); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->AddRenderer(videoChannel2, _window2, 0, 0.0, + 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->StartRender(videoChannel2); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + unsigned short rtpPort2 = 13000; + error = ViE.ptrViENetwork->SetLocalReceiver(videoChannel2, rtpPort2); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendDestination(videoChannel2, + "127.0.0.1", rtpPort2); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEBase->StartReceive(videoChannel2); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEBase->StartSend(videoChannel2); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("\nTest using one encoder on several channels"); + ViETest::Log( + "Channel 1 is rendered in Window1, channel 2 in Window 2." + "\nSending VP8 on both channels"); + + AutoTestSleep(KAutoTestSleepTimeMs); + + // Check that we received H.263 on both channels + numberOfErrors += ViETest::TestError( + codecObserver1.incomingCodec.codecType == webrtc::kVideoCodecVP8 + && codecObserver1.incomingCodec.width == 176, + "ERROR: %s at line %d", __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError( + codecObserver2.incomingCodec.codecType == + webrtc::kVideoCodecVP8 + && codecObserver2.incomingCodec.width == 176, + "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + // Delete the first channel and keep the second + error = ViE.ptrViEBase->DeleteChannel(videoChannel1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("Channel 1 deleted, you should only see video in Window " + "2"); + + AutoTestSleep(KAutoTestSleepTimeMs); + + // Create another channel + int videoChannel3 = -1; + error = ViE.ptrViEBase->CreateChannel(videoChannel3, videoChannel2); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(videoChannel3 != videoChannel2, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERtpRtcp->SetKeyFrameRequestMethod( + videoChannel3, kViEKeyFrameRequestPliRtcp); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Prepare receive codecs + for (int idx = 0; idx < ViE.ptrViECodec->NumberOfCodecs(); idx++) + { + error = ViE.ptrViECodec->GetCodec(idx, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViECodec->SetReceiveCodec(videoChannel3, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } + + ViEAutotestCodecObserever codecObserver3; + error = ViE.ptrViECodec->RegisterDecoderObserver(videoChannel3, + codecObserver3); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->AddRenderer(videoChannel3, _window1, 0, 0.0, + 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->StartRender(videoChannel3); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + unsigned short rtpPort3 = 14000; + error = ViE.ptrViENetwork->SetLocalReceiver(videoChannel3, rtpPort3); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendDestination(videoChannel3, + "127.0.0.1", rtpPort3); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEBase->StartReceive(videoChannel3); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEBase->StartSend(videoChannel3); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEBase->DeleteChannel(videoChannel2); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("A third channel created and rendered in Window 1,\n" + "channel 2 is deleted and you should only see video in Window 1"); + + AutoTestSleep(KAutoTestSleepTimeMs); + + error = ViE.ptrViEBase->DeleteChannel(videoChannel3); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } + + // SetKeyFrameRequestCallbackStatus + // Check callback + + // SetPacketLossBitrateAdaptationStatus + // Check bitrate changes/doesn't change + + // GetAvailableBandwidth + + // SendKeyFrame + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViECodec Extended Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViECodec Extended Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + return 0; + +} + +int ViEAutoTest::ViECodecAPITest() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViECodec API Test\n"); + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + + VideoEngine* ptrViE = NULL; + ptrViE = VideoEngine::Create(); + numberOfErrors += ViETest::TestError(ptrViE != NULL, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViEBase* ptrViEBase = ViEBase::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViEBase != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + error = ptrViEBase->Init(); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + int videoChannel = -1; + error = ptrViEBase->CreateChannel(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViECodec* ptrViECodec = ViECodec::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViECodec != NULL, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + //*************************************************************** + // Engine ready. Begin testing class + //*************************************************************** + + + // + // SendCodec + // + webrtc::VideoCodec videoCodec; + memset(&videoCodec, 0, sizeof(webrtc::VideoCodec)); + + const int numberOfCodecs = ptrViECodec->NumberOfCodecs(); + numberOfErrors += ViETest::TestError(numberOfCodecs > 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + for (int idx = 0; idx < numberOfCodecs; idx++) + { + error = ptrViECodec->GetCodec(idx, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + if (videoCodec.codecType == webrtc::kVideoCodecVP8) + { + error = ptrViECodec->SetSendCodec(videoChannel, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + break; + } + } + memset(&videoCodec, 0, sizeof(videoCodec)); + error = ptrViECodec->GetSendCodec(videoChannel, videoCodec); + assert(videoCodec.codecType == webrtc::kVideoCodecVP8); + + for (int idx = 0; idx < numberOfCodecs; idx++) + { + error = ptrViECodec->GetCodec(idx, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + if (videoCodec.codecType == webrtc::kVideoCodecI420) + { + error = ptrViECodec->SetSendCodec(videoChannel, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + break; + } + } + memset(&videoCodec, 0, sizeof(videoCodec)); + error = ptrViECodec->GetSendCodec(videoChannel, videoCodec); + assert(videoCodec.codecType == webrtc::kVideoCodecI420); + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + error = ptrViEBase->DeleteChannel(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + int remainingInterfaces = 0; + remainingInterfaces = ptrViECodec->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + remainingInterfaces = ptrViEBase->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + bool deleted = VideoEngine::Delete(ptrViE); + numberOfErrors += ViETest::TestError(deleted == true, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViECodec API Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViECodec API Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + return 0; +} + +#ifdef WEBRTC_VIDEO_ENGINE_EXTERNAL_CODEC_API +#include "vie_external_codec.h" +#endif +int ViEAutoTest::ViECodecExternalCodecTest() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViEExternalCodec Test\n"); + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + + //*************************************************************** + // Engine ready. Begin testing class + //*************************************************************** + +#ifdef WEBRTC_VIDEO_ENGINE_EXTERNAL_CODEC_API + int numberOfErrors=0; + { + int error=0; + tbInterfaces ViE("ViEExternalCodec", numberOfErrors); + tbCaptureDevice captureDevice(ViE, numberOfErrors); + tbVideoChannel channel(ViE, numberOfErrors, webrtc::kVideoCodecI420, + 352,288,30,(352*288*3*8*30)/(2*1000)); + + captureDevice.ConnectTo(channel.videoChannel); + + error = ViE.ptrViERender->AddRenderer(channel.videoChannel, _window1, 0, + 0.0, 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->StartRender(channel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + channel.StartReceive(); + channel.StartSend(); + + ViETest::Log("Using internal I420 codec"); + AutoTestSleep(KAutoTestSleepTimeMs/2); + + ViEExternalCodec* ptrViEExtCodec = + ViEExternalCodec::GetInterface(ViE.ptrViE); + numberOfErrors += ViETest::TestError(ptrViEExtCodec != NULL, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + webrtc::VideoCodec codecStruct; + + error=ViE.ptrViECodec->GetSendCodec(channel.videoChannel,codecStruct); + numberOfErrors += ViETest::TestError(ptrViEExtCodec != NULL, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Use external encoder instead + { + tbI420Encoder extEncoder; + + // Test to register on wrong channel + error = ptrViEExtCodec->RegisterExternalSendCodec( + channel.videoChannel+5,codecStruct.plType,&extEncoder); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError( + ViE.LastError() == kViECodecInvalidArgument, + "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViEExtCodec->RegisterExternalSendCodec( + channel.videoChannel,codecStruct.plType,&extEncoder); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Use new external encoder + error = ViE.ptrViECodec->SetSendCodec(channel.videoChannel,codecStruct); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + tbI420Decoder extDecoder; + error = ptrViEExtCodec->RegisterExternalReceiveCodec( + channel.videoChannel,codecStruct.plType,&extDecoder); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViECodec->SetReceiveCodec(channel.videoChannel, + codecStruct); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("Using external I420 codec"); + AutoTestSleep(KAutoTestSleepTimeMs); + + // Test to deregister on wrong channel + error = ptrViEExtCodec->DeRegisterExternalSendCodec( + channel.videoChannel+5,codecStruct.plType); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError( + ViE.LastError() == kViECodecInvalidArgument, + "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + // Test to deregister wrong payload type. + error = ptrViEExtCodec->DeRegisterExternalSendCodec( + channel.videoChannel,codecStruct.plType-1); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Deregister external send codec + error = ptrViEExtCodec->DeRegisterExternalSendCodec( + channel.videoChannel,codecStruct.plType); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViEExtCodec->DeRegisterExternalReceiveCodec( + channel.videoChannel,codecStruct.plType); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Verify that the encoder and decoder has been used + tbI420Encoder::FunctionCalls encodeCalls = + extEncoder.GetFunctionCalls(); + numberOfErrors += ViETest::TestError(encodeCalls.InitEncode == 1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(encodeCalls.Release == 1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(encodeCalls.Encode > 30, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError( + encodeCalls.RegisterEncodeCompleteCallback ==1, + "ERROR: %s at line %d", __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(encodeCalls.SetRates > 1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(encodeCalls.SetPacketLoss > 1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + tbI420Decoder::FunctionCalls decodeCalls = + extDecoder.GetFunctionCalls(); + numberOfErrors += ViETest::TestError(decodeCalls.InitDecode == 1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(decodeCalls.Release == 1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(decodeCalls.Decode > 30, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError( + decodeCalls.RegisterDecodeCompleteCallback ==1, + "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + ViETest::Log("Changing payload type Using external I420 codec"); + + codecStruct.plType=codecStruct.plType-1; + error = ptrViEExtCodec->RegisterExternalReceiveCodec( + channel.videoChannel, codecStruct.plType, &extDecoder); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViECodec->SetReceiveCodec(channel.videoChannel, + codecStruct); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ptrViEExtCodec->RegisterExternalSendCodec( + channel.videoChannel, codecStruct.plType, &extEncoder); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Use new external encoder + error = ViE.ptrViECodec->SetSendCodec(channel.videoChannel, + codecStruct); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + AutoTestSleep(KAutoTestSleepTimeMs/2); + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + + error = ptrViEExtCodec->DeRegisterExternalSendCodec( + channel.videoChannel,codecStruct.plType); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ptrViEExtCodec->DeRegisterExternalReceiveCodec( + channel.videoChannel,codecStruct.plType); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Verify that the encoder and decoder has been used + encodeCalls = extEncoder.GetFunctionCalls(); + numberOfErrors += ViETest::TestError(encodeCalls.InitEncode == 2, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(encodeCalls.Release == 2, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(encodeCalls.Encode > 30, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError( + encodeCalls.RegisterEncodeCompleteCallback == 2, + "ERROR: %s at line %d", __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(encodeCalls.SetRates > 1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(encodeCalls.SetPacketLoss > 1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + decodeCalls = extDecoder.GetFunctionCalls(); + numberOfErrors += ViETest::TestError(decodeCalls.InitDecode == 2, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(decodeCalls.Release == 2, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(decodeCalls.Decode > 30, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError( + decodeCalls.RegisterDecodeCompleteCallback == 2, + "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + int remainingInterfaces = ptrViEExtCodec->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } // tbI420Encoder and extDecoder goes out of scope + + ViETest::Log("Using internal I420 codec"); + AutoTestSleep(KAutoTestSleepTimeMs/2); + + } + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViEExternalCodec Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViEExternalCodec Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + return 0; + +#else + ViETest::Log(" ViEExternalCodec not enabled\n"); + return 0; +#endif +} diff --git a/video_engine/main/test/AutoTest/source/vie_autotest_custom_call.cc b/video_engine/main/test/AutoTest/source/vie_autotest_custom_call.cc new file mode 100644 index 0000000000..35628696f1 --- /dev/null +++ b/video_engine/main/test/AutoTest/source/vie_autotest_custom_call.cc @@ -0,0 +1,950 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_autotest_custom_call.cc + * + */ + +#include "vie_autotest_defines.h" +#include "vie_autotest.h" + +#include + +int ViEAutoTest::ViECustomCall() +{ + + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" Enter values to use custom settings\n"); + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + std::string str; + + // VoE + VoiceEngine* ptrVE = VoiceEngine::Create(); + numberOfErrors += ViETest::TestError(ptrVE != NULL, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + VoEBase* ptrVEBase = VoEBase::GetInterface(ptrVE); + numberOfErrors += ViETest::TestError(ptrVEBase != NULL, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrVEBase->Init(); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + VoECodec* ptrVECodec = VoECodec::GetInterface(ptrVE); + numberOfErrors += ViETest::TestError(ptrVECodec != NULL, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + VoEHardware* ptrVEHardware = VoEHardware::GetInterface(ptrVE); + numberOfErrors += ViETest::TestError(ptrVEHardware != NULL, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + VoEAudioProcessing* ptrVEAPM = VoEAudioProcessing::GetInterface(ptrVE); + numberOfErrors += ViETest::TestError(ptrVEAPM != NULL, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + + + // ViE + VideoEngine* ptrViE = NULL; + ptrViE = VideoEngine::Create(); + numberOfErrors += ViETest::TestError(ptrViE != NULL, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + ViEBase* ptrViEBase = ViEBase::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViEBase != NULL, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViEBase->Init(); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + ViECapture* ptrViECapture = ViECapture::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViECapture != NULL, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + ViERender* ptrViERender = ViERender::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViERender != NULL, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + ViECodec* ptrViECodec = ViECodec::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViECodec != NULL, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + ViENetwork* ptrViENetwork = ViENetwork::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViENetwork != NULL, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + + + bool startCall = false; + const unsigned int kMaxIPLength = 16; + char ipAddress[kMaxIPLength] = ""; + const unsigned int KMaxUniqueIdLength = 256; + char uniqueId[KMaxUniqueIdLength] = ""; + char deviceName[KMaxUniqueIdLength] = ""; + int videoTxPort = 0; + int videoRxPort = 0; + int videoChannel = -1; + int codecIdx = 0; + webrtc::VideoCodec videoCodec; + char audioCaptureDeviceName[KMaxUniqueIdLength] = ""; + char audioPlaybackDeviceName[KMaxUniqueIdLength] = ""; + int audioCaptureDeviceIndex = -1; + int audioPlaybackDeviceIndex = -1; + int audioTxPort = 0; + int audioRxPort = 0; + webrtc::CodecInst audioCodec; + int audioChannel = -1; + + while(1) + { + // IP + memset(ipAddress, 0, kMaxIPLength); + GetIPAddress(ipAddress); + + // video devices + memset(deviceName, 0, KMaxUniqueIdLength); + memset(uniqueId, 0, KMaxUniqueIdLength); + GetVideoDevice(ptrViEBase, ptrViECapture, deviceName, uniqueId); + + // video ports + videoTxPort = 0; + videoRxPort = 0; + GetVideoPorts(&videoTxPort, &videoRxPort); + + + // video codecs + memset((void*)&videoCodec, 0, sizeof(videoCodec)); + GetVideoCodec(ptrViECodec, videoCodec); + + + + // audio devices + memset(audioCaptureDeviceName, 0, KMaxUniqueIdLength); + memset(audioPlaybackDeviceName, 0, KMaxUniqueIdLength); + GetAudioDevices(ptrVEBase, ptrVEHardware, audioCaptureDeviceName, audioCaptureDeviceIndex, audioPlaybackDeviceName, audioPlaybackDeviceIndex); + + + // audio port + audioTxPort = 0; + audioRxPort = 0; + GetAudioPorts(&audioTxPort, &audioRxPort); + + // audio codec + memset((void*)&audioCodec, 0, sizeof(audioCodec)); + GetAudioCodec(ptrVECodec, audioCodec); + + // start the call now + PrintCallInformation(ipAddress, + deviceName, uniqueId, videoCodec, videoTxPort, videoRxPort, + audioCaptureDeviceName, audioPlaybackDeviceName, audioCodec, audioTxPort, audioRxPort); + + std::cout << std::endl; + std::cout << "1. Start the call" << std::endl; + std::cout << "2. Reconfigure call settings" << std::endl; + std::cout << "3. Go back to main menu" << std::endl; + std::cout << "What do you want to do? Press enter for default (Start the call): "; + + std::getline(std::cin, str); + int selection = 0; + selection = atoi(str.c_str()); + + if(selection == 0 || selection == 1){ + startCall = true; + break; + } + else if(selection == 2) + { + continue; + } + else if(selection == 3) + { + startCall = false; + break; + } + else + { + // invalid selection + std::cout << "ERROR: Code=" << error << " Invalid selection" << std::endl; + continue; + } + } + + + + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + + + if(startCall == true) + { + + // Configure Audio first + audioChannel = ptrVEBase->CreateChannel(); + + error = ptrVEBase->SetSendDestination(audioChannel, audioTxPort, ipAddress); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrVEBase->SetLocalReceiver(audioChannel, audioRxPort); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrVEHardware->SetRecordingDevice(audioCaptureDeviceIndex); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrVEHardware->SetPlayoutDevice(audioPlaybackDeviceIndex); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrVECodec->SetSendCodec(audioChannel, audioCodec); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrVECodec->SetVADStatus(audioChannel, true, webrtc::kVadAggressiveHigh ); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrVEAPM->SetAgcStatus(true, kAgcDefault); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrVEAPM->SetNsStatus(true, kNsHighSuppression); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + // Configure Video now + + error = ptrViE->SetTraceFile("ViECustomCall.txt"); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViEBase->SetVoiceEngine(ptrVE); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViEBase->CreateChannel(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViEBase->ConnectAudioChannel(videoChannel, audioChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + int captureId = 0; + error = ptrViECapture->AllocateCaptureDevice(uniqueId, KMaxUniqueIdLength, captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViECapture->ConnectCaptureDevice(captureId, videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViECapture->StartCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + ViERTP_RTCP* ptrViERtpRtcp = ViERTP_RTCP::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(ptrViE != NULL, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViERtpRtcp->SetRTCPStatus(videoChannel, kRtcpCompound_RFC4585); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViERtpRtcp->SetKeyFrameRequestMethod(videoChannel, kViEKeyFrameRequestPliRtcp); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViERtpRtcp->SetTMMBRStatus(videoChannel, true); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViERender->AddRenderer(captureId, _window1, 0, 0.0, 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + + error = ptrViERender->AddRenderer(videoChannel, _window2, 1, 0.0, 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViENetwork->SetSendDestination(videoChannel, ipAddress, videoTxPort); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViENetwork->SetLocalReceiver(videoChannel, videoRxPort); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViECodec->SetSendCodec(videoChannel, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViECodec->SetReceiveCodec(videoChannel, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + + + // **** start the engines + // VE first + error = ptrVEBase->StartReceive(audioChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrVEBase->StartPlayout(audioChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrVEBase->StartSend(audioChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + + // ViE next + error = ptrViEBase->StartSend(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViEBase->StartReceive(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViERender->MirrorRenderStream(captureId, true, false, true); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViERender->StartRender(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViERender->StartRender(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + + + //*************************************************************** + // Engine ready. Wait for input + //*************************************************************** + + + // Call started + std::cout << std::endl; + std::cout << "Loopback call started" << std::endl; + std::cout << std::endl << std::endl; + std::cout << "Press enter to stop..."; + std::getline(std::cin, str); + + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + + // audio engine first + error = ptrVEBase->StopReceive(audioChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrVEBase->StopPlayout(audioChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrVEBase->DeleteChannel(audioChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + + + + + // now do video + error = ptrViEBase->DisconnectAudioChannel(videoChannel); + + error = ptrViEBase->StopReceive(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViEBase->StopSend(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViERender->StopRender(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViERender->StopRender(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViERender->RemoveRenderer(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViERender->RemoveRenderer(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViECapture->StopCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViECapture->DisconnectCaptureDevice(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViECapture->ReleaseCaptureDevice(captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ptrViEBase->DeleteChannel(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + int remainingInterfaces = 0; + remainingInterfaces = ptrViECodec->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + remainingInterfaces = ptrViECapture->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + remainingInterfaces = ptrViERtpRtcp->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + remainingInterfaces = ptrViERender->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + remainingInterfaces = ptrViENetwork->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + remainingInterfaces = ptrViEBase->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + bool deleted = VideoEngine::Delete(ptrViE); + numberOfErrors += ViETest::TestError(deleted == true, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + ViETest::Log(" "); + ViETest::Log(" ViE Autotest Loopback Call Done"); + ViETest::Log("========================================"); + ViETest::Log(" "); + } + + return numberOfErrors; + +} + + + + + + + + + +bool ViEAutoTest::GetVideoDevice(ViEBase* ptrViEBase, ViECapture* ptrViECapture, char* captureDeviceName, char* captureDeviceUniqueId) +{ + int error = 0; + int numberOfErrors = 0; + int captureDeviceIndex = 0; + std::string str; + + const unsigned int KMaxDeviceNameLength = 128; + const unsigned int KMaxUniqueIdLength = 256; + char deviceName[KMaxDeviceNameLength]; + char uniqueId[KMaxUniqueIdLength]; + + + while(1) + { + memset(deviceName, 0, KMaxDeviceNameLength); + memset(uniqueId, 0, KMaxUniqueIdLength); + + std::cout << std::endl; + std::cout << "Available capture devices:" << std::endl; + int captureIdx = 0; + for (captureIdx = 0; captureIdx < ptrViECapture->NumberOfCaptureDevices(); captureIdx++) + { + memset(deviceName, 0, KMaxDeviceNameLength); + memset(uniqueId, 0, KMaxUniqueIdLength); + + error = ptrViECapture->GetCaptureDevice(captureIdx, deviceName, KMaxDeviceNameLength, uniqueId, KMaxUniqueIdLength); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + std::cout << " " << captureIdx+1 << ". " << deviceName << std::endl; + } + std::cout << "Choose a capture device. Press enter for default (" << deviceName << "/" << uniqueId << "): "; + std::getline(std::cin, str); + captureDeviceIndex = atoi(str.c_str()); + + if(captureDeviceIndex == 0) + { + // use default (or first) camera + error = ptrViECapture->GetCaptureDevice(0, deviceName, KMaxDeviceNameLength, uniqueId, KMaxUniqueIdLength); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + strcpy(captureDeviceUniqueId, uniqueId); + strcpy(captureDeviceName, deviceName); + return true; + } + else if(captureDeviceIndex < 0 || captureDeviceIndex > (int)ptrViECapture->NumberOfCaptureDevices()) + { + // invalid selection + continue; + } + else + { + error = ptrViECapture->GetCaptureDevice(captureDeviceIndex - 1, deviceName, KMaxDeviceNameLength, uniqueId, KMaxUniqueIdLength); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + strcpy(captureDeviceName, uniqueId); + strcpy(captureDeviceName, deviceName); + return true; + } + } +} + +bool ViEAutoTest::GetAudioDevices(VoEBase* ptrVEBase, VoEHardware* ptrVEHardware, + char* recordingDeviceName, int& recordingDeviceIndex, + char* playbackDeviceName, int& playbackDeviceIndex) +{ + int error = 0; + int numberOfErrors = 0; + int captureDeviceIndex = 0; + std::string str; + + const unsigned int KMaxDeviceNameLength = 128; + const unsigned int KMaxUniqueIdLength = 128; + char recordingDeviceUniqueName[KMaxDeviceNameLength]; + char playbackDeviceUniqueName[KMaxUniqueIdLength]; + + int numberOfRecordingDevices = -1; + error = ptrVEHardware->GetNumOfRecordingDevices(numberOfRecordingDevices); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + while(1) + { + recordingDeviceIndex = -1; + + std::cout << std::endl; + std::cout << "Available audio capture devices:" << std::endl; + int captureIdx = 0; + + for (captureIdx = 0; captureIdx < numberOfRecordingDevices ; captureIdx++) + { + memset(recordingDeviceName, 0, KMaxDeviceNameLength); + memset(recordingDeviceUniqueName, 0, KMaxDeviceNameLength); + error = ptrVEHardware->GetRecordingDeviceName(captureIdx, recordingDeviceName, recordingDeviceUniqueName); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + std::cout << " " << captureIdx+1 << ". " << recordingDeviceName << std::endl; + } + + std::cout << "Choose an audio capture device. Press enter for default (" << recordingDeviceName << "): "; + std::getline(std::cin, str); + int captureDeviceIndex = atoi(str.c_str()); + + if(captureDeviceIndex == 0) + { + // use default (or first) camera + recordingDeviceIndex = 0; + error = ptrVEHardware->GetRecordingDeviceName(recordingDeviceIndex, recordingDeviceName, recordingDeviceUniqueName); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + break; + } + else if(captureDeviceIndex < 0 || captureDeviceIndex > numberOfRecordingDevices) + { + // invalid selection + continue; + } + else + { + recordingDeviceIndex = captureDeviceIndex - 1; + error = ptrVEHardware->GetRecordingDeviceName(recordingDeviceIndex, recordingDeviceName, recordingDeviceUniqueName); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + break; + } + } + + int numberOfPlaybackDevices = -1; + error = ptrVEHardware->GetNumOfPlayoutDevices(numberOfPlaybackDevices); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + while(1) + { + playbackDeviceIndex = -1; + + std::cout << std::endl; + std::cout << "Available audio playout devices:" << std::endl; + int captureIdx = 0; + + for (captureIdx = 0; captureIdx < numberOfPlaybackDevices ; captureIdx++) + { + memset(playbackDeviceName, 0, KMaxDeviceNameLength); + memset(playbackDeviceUniqueName, 0, KMaxDeviceNameLength); + error = ptrVEHardware->GetPlayoutDeviceName(captureIdx, playbackDeviceName, playbackDeviceUniqueName); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + std::cout << " " << captureIdx+1 << ". " << playbackDeviceName << std::endl; + } + + std::cout << "Choose an audio playback device. Press enter for default (" << playbackDeviceName << "): "; + std::getline(std::cin, str); + int captureDeviceIndex = atoi(str.c_str()); + + if(captureDeviceIndex == 0) + { + // use default (or first) camera + playbackDeviceIndex = 0; + error = ptrVEHardware->GetPlayoutDeviceName(playbackDeviceIndex, playbackDeviceName, playbackDeviceUniqueName); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + return true; + } + else if(captureDeviceIndex < 0 || captureDeviceIndex > numberOfPlaybackDevices) + { + // invalid selection + continue; + } + else + { + playbackDeviceIndex = captureDeviceIndex - 1; + error = ptrVEHardware->GetPlayoutDeviceName(playbackDeviceIndex, playbackDeviceName, playbackDeviceUniqueName); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + return true; + } + } + + + + +} + + +// general settings functions +bool ViEAutoTest::GetIPAddress(char* iIP) +{ + int error = 0; + char oIP[16] = DEFAULT_SEND_IP; + std::string str; + + while(1) + { + std::cout << std::endl; + std::cout << "Enter destination IP. Press enter for default (" << oIP << "): "; + std::getline(std::cin, str); + + if(str.compare("") == 0) + { + // use default value; + strcpy(iIP, oIP); + return true; + } + + if(ValidateIP(str) == false) + { + std::cout << "Invalid entry. Try again." << std::endl; + continue; + } + + // done. Copy std::string to c_string and return + strcpy(iIP, str.c_str()); + return true; + } + + assert(false); // should never reach here + return false; +} + +bool ViEAutoTest::ValidateIP(std::string iStr) +{ + if(0 == iStr.compare("")){ + return false; + } + + return true; +} + +// video settings functions +bool ViEAutoTest::GetVideoPorts(int* txPort, int* rxPort) +{ + int error = 0; + std::string str; + int port = 0; + + // set to default values + *txPort = DEFAULT_VIDEO_PORT; + *rxPort = DEFAULT_VIDEO_PORT; + + while(1) + { + std::cout << "Enter video send port. Press enter for default (" << *txPort << ")"; + std::getline(std::cin, str); + port = atoi(str.c_str()); + + if(port == 0) + { + // default value + break; + } + else + { + // user selection + if(port <= 0 || port > 63556) + { + // invalid selection + continue; + } + else + { + *txPort = port; + break; // move on to rxport + } + } + } + + while(1) + { + std::cout << "Enter video receive port. Press enter for default (" << *rxPort << ")"; + std::getline(std::cin, str); + port = atoi(str.c_str()); + + + if(port == 0) + { + // default value + return true; + } + else + { + // user selection + if(port <= 0 || port > 63556) + { + // invalid selection + continue; + } + else + { + *rxPort = port; + break; // move on to rxport + } + } + } + + assert(false); + return false; +} +bool ViEAutoTest::GetVideoCodec(ViECodec* ptrViECodec, webrtc::VideoCodec& videoCodec) +{ + int error = 0; + int numberOfErrors = 0; + int codecSelection = 0; + std::string str; + memset(&videoCodec, 0, sizeof(webrtc::VideoCodec)); + + bool exitLoop=false; + while(!exitLoop) + { + std::cout << std::endl; + std::cout << "Available video codecs:" << std::endl; + int codecIdx = 0; + int defaultCodecIdx = 0; + for (codecIdx = 0; codecIdx < ptrViECodec->NumberOfCodecs(); codecIdx++) + { + error = ptrViECodec->GetCodec(codecIdx, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + // test for default codec index + if(strcmp(videoCodec.plName, DEFAULT_VIDEO_CODEC) == 0){ + defaultCodecIdx = codecIdx; + } + + std::cout << " " << codecIdx+1 << ". " << videoCodec.plName << std::endl; + } + std::cout << std::endl; + std::cout << "Choose video codec. Press enter for default (" << DEFAULT_VIDEO_CODEC << ")"; + std::getline(std::cin, str); + codecSelection = atoi(str.c_str()); + + + if(codecSelection == 0) + { + // use default + error = ptrViECodec->GetCodec(defaultCodecIdx, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + exitLoop=true; + } + else + { + // user selection + codecSelection = atoi(str.c_str())-1; + error = ptrViECodec->GetCodec(codecSelection, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + if(error != 0) + { + std::cout << "ERROR: Code=" << error << " Invalid selection" << std::endl; + continue; + } + exitLoop=true; + } + } + + std::cout << "Choose video width. Press enter for default (" + << DEFAULT_VIDEO_CODEC_WIDTH << ")"; + std::getline(std::cin, str); + int sizeSelection = atoi(str.c_str()); + if(sizeSelection!=0) + { + videoCodec.width=sizeSelection; + } + + std::cout << "Choose video height. Press enter for default (" + << DEFAULT_VIDEO_CODEC_HEIGHT << ")"; + std::getline(std::cin, str); + sizeSelection = atoi(str.c_str()); + if(sizeSelection!=0) + { + videoCodec.height=sizeSelection; + } + + + + return true; + +} + +// audio settings functions +bool ViEAutoTest::GetAudioPorts(int* txPort, int* rxPort) +{ + int error = 0; + int port = 0; + std::string str; + + // set to default values + *txPort = DEFAULT_AUDIO_PORT; + *rxPort = DEFAULT_AUDIO_PORT; + + while(1) + { + std::cout << "Enter audio send port. Press enter for default (" << *txPort << ")"; + std::getline(std::cin, str); + port = atoi(str.c_str()); + + if(port == 0) + { + // default value + break; + } + else + { + // user selection + if(port <= 0 || port > 63556) + { + // invalid selection + continue; + } + else + { + *txPort = port; + break; // move on to rxport + } + } + } + + while(1) + { + std::cout << "Enter audio receive port. Press enter for default (" << *rxPort << ")"; + std::getline(std::cin, str); + port = atoi(str.c_str()); + + if(port == 0) + { + // default value + return true; + } + else + { + // user selection + if(port <= 0 || port > 63556) + { + // invalid selection + continue; + } + else + { + *rxPort = port; + break; // move on to rxport + } + } + } + + assert(false); + return false; +} + +bool ViEAutoTest::GetAudioCodec(webrtc::VoECodec* ptrVeCodec, webrtc::CodecInst& audioCodec) +{ + int error = 0; + int numberOfErrors = 0; + int codecSelection = 0; + std::string str; + memset(&audioCodec, 0, sizeof(webrtc::CodecInst)); + + while(1) + { + std::cout << std::endl; + std::cout << "Available audio codecs:" << std::endl; + int codecIdx = 0; + int defaultCodecIdx = 0; + for (codecIdx = 0; codecIdx < ptrVeCodec->NumOfCodecs(); codecIdx++) + { + error = ptrVeCodec->GetCodec(codecIdx, audioCodec); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + // test for default codec index + if(strcmp(audioCodec.plname, DEFAULT_AUDIO_CODEC) == 0){ + defaultCodecIdx = codecIdx; + } + + std::cout << " " << codecIdx+1 << ". " << audioCodec.plname << std::endl; + } + std::cout << std::endl; + std::cout << "Choose audio codec. Press enter for default (" << DEFAULT_AUDIO_CODEC << ")"; + std::getline(std::cin, str); + codecSelection = atoi(str.c_str()); + + if(codecSelection == 0) + { + // use default + error = ptrVeCodec->GetCodec(defaultCodecIdx, audioCodec); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + return true; + } + else + { + // user selection + codecSelection = atoi(str.c_str())-1; + error = ptrVeCodec->GetCodec(codecSelection, audioCodec); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + if(error != 0) + { + std::cout << "ERROR: Code=" << error << " Invalid selection" << std::endl; + continue; + } + return true; + } + } + + assert(false); // should never reach here + return false; +} + + +void ViEAutoTest::PrintCallInformation(char* IP, + char* videoCaptureDeviceName, char* videoCaptureUniqueId, webrtc::VideoCodec videoCodec, int videoTxPort, int videoRxPort, + char* audioCaptureDeviceName, char* audioPlaybackDeviceName, webrtc::CodecInst audioCodec, int audioTxPort, int audioRxPort) +{ + std::string str; + + std::cout << "************************************************" << std::endl; + std::cout << "The call will use the following settings: " << std::endl; + std::cout << "\tIP: " << IP << std::endl; + std::cout << "\tVideo Capture Device: " << videoCaptureDeviceName << std::endl; + std::cout << "\t\tName: " << videoCaptureDeviceName << std::endl; + std::cout << "\t\tUniqueId: " << videoCaptureUniqueId << std::endl; + std::cout << "\tVideo Codec: " << std::endl; + std::cout << "\t\tplName: " << videoCodec.plName << std::endl; + std::cout << "\t\tplType: " << (int)videoCodec.plType << std::endl; + std::cout << "\t\twidth: " << videoCodec.width << std::endl; + std::cout << "\t\theight: " << videoCodec.height << std::endl; + std::cout << "\t Video Tx Port: " << videoTxPort << std::endl; + std::cout << "\t Video Rx Port: " << videoRxPort << std::endl; + std::cout << "\tAudio Capture Device: " << audioCaptureDeviceName << std::endl; + std::cout << "\tAudio Playback Device: " << audioPlaybackDeviceName << std::endl; + std::cout << "\tAudio Codec: " << std::endl; + std::cout << "\t\tplname: " << audioCodec.plname << std::endl; + std::cout << "\t\tpltype: " << (int)audioCodec.pltype << std::endl; + std::cout << "\t Audio Tx Port: " << audioTxPort << std::endl; + std::cout << "\t Audio Rx Port: " << audioRxPort << std::endl; + std::cout << "************************************************" << std::endl; + + +} + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/video_engine/main/test/AutoTest/source/vie_autotest_encryption.cc b/video_engine/main/test/AutoTest/source/vie_autotest_encryption.cc new file mode 100644 index 0000000000..49e1fa5600 --- /dev/null +++ b/video_engine/main/test/AutoTest/source/vie_autotest_encryption.cc @@ -0,0 +1,903 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// +// vie_autotest_encryption.cc +// + +#include "vie_autotest_defines.h" +#include "vie_autotest.h" +#include "engine_configurations.h" + +#include "tb_capture_device.h" +#include "tb_external_transport.h" +#include "tb_interfaces.h" +#include "tb_video_channel.h" + +class ViEAutotestEncryption: public Encryption +{ +public: + ViEAutotestEncryption() + { + } + ~ViEAutotestEncryption() + { + } + + virtual void encrypt(int channel_no, unsigned char * in_data, + unsigned char * out_data, int bytes_in, int* bytes_out) + { + for (int i = 0; i < bytes_in; i++) + { + out_data[i] = ~in_data[i]; + } + *bytes_out = bytes_in + 2; + } + + virtual void decrypt(int channel_no, unsigned char * in_data, + unsigned char * out_data, int bytes_in, int* bytes_out) + { + for (int i = 0; i < bytes_in - 2; i++) + { + out_data[i] = ~in_data[i]; + } + *bytes_out = bytes_in - 2; + } + + virtual void encrypt_rtcp(int channel_no, unsigned char * in_data, + unsigned char * out_data, int bytes_in, + int* bytes_out) + { + for (int i = 0; i < bytes_in; i++) + { + out_data[i] = ~in_data[i]; + } + *bytes_out = bytes_in + 2; + } + + virtual void decrypt_rtcp(int channel_no, unsigned char * in_data, + unsigned char * out_data, int bytes_in, + int* bytes_out) + { + for (int i = 0; i < bytes_in - 2; i++) + { + out_data[i] = ~in_data[i]; + } + *bytes_out = bytes_in - 2; + } +}; + +int ViEAutoTest::ViEEncryptionStandardTest() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViEEncryption Standard Test\n"); + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + + // Create VIE + tbInterfaces ViE("ViEEncryptionStandardTest", numberOfErrors); + // Create a video channel + tbVideoChannel tbChannel(ViE, numberOfErrors, webrtc::kVideoCodecVP8); + + // Create a capture device + tbCaptureDevice tbCapture(ViE, numberOfErrors); + tbCapture.ConnectTo(tbChannel.videoChannel); + + tbChannel.StartReceive(); + + tbChannel.StartSend(); + + error = ViE.ptrViERender->AddRenderer(tbCapture.captureId, _window1, 0, + 0.0, 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->StartRender(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->AddRenderer(tbChannel.videoChannel, _window2, 1, + 0.0, 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->StartRender(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + +#ifdef WEBRTC_SRTP + //*************************************************************** + // Engine ready. Begin testing class + //*************************************************************** + + + // + // SRTP + // + unsigned char srtpKey1[30] = + { 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}; + + // Encryption only + error = ViE.ptrViEEncryption->EnableSRTPReceive( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthNull, 0, 0, webrtc::kEncryption, srtpKey1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthNull, 0, 0, webrtc::kEncryption, srtpKey1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log("SRTP encryption only"); + AutoTestSleep(KAutoTestSleepTimeMs); + error = ViE.ptrViEEncryption->DisableSRTPReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Authentication only + error = ViE.ptrViEEncryption->EnableSRTPReceive(tbChannel.videoChannel, + webrtc::kCipherNull, 0, + webrtc::kAuthHmacSha1, 20, + 4, webrtc::kAuthentication, + srtpKey1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend(tbChannel.videoChannel, + webrtc::kCipherNull, 0, + webrtc::kAuthHmacSha1, 20, 4, + webrtc::kAuthentication, + srtpKey1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log("SRTP authentication only"); + AutoTestSleep(KAutoTestSleepTimeMs); + error = ViE.ptrViEEncryption->DisableSRTPReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Full protection + error = ViE.ptrViEEncryption->EnableSRTPReceive( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryptionAndAuthentication, + srtpKey1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryptionAndAuthentication, + srtpKey1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log("SRTP full protection"); + AutoTestSleep(KAutoTestSleepTimeMs); + error = ViE.ptrViEEncryption->DisableSRTPReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", +#endif // WEBRTC_SRTP + // + // External encryption + // + ViEAutotestEncryption testEncryption; + error = ViE.ptrViEBase->StartSend(tbChannel.videoChannel); + error = ViE.ptrViEEncryption->RegisterExternalEncryption( + tbChannel.videoChannel, testEncryption); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log( + "External encryption/decryption added, you should still see video"); + AutoTestSleep(KAutoTestSleepTimeMs); + error = ViE.ptrViEEncryption->DeregisterExternalEncryption( + tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViEEncryption Standard Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViEEncryption Standard Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + return 0; + +} + +int ViEAutoTest::ViEEncryptionExtendedTest() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViEEncryption Extended Test\n"); + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + + // Create VIE + tbInterfaces ViE("ViEEncryptionExtendedTest", numberOfErrors); + // Create a video channel + tbVideoChannel tbChannel(ViE, numberOfErrors, webrtc::kVideoCodecVP8); + + // Create a capture device + tbCaptureDevice tbCapture(ViE, numberOfErrors); + tbCapture.ConnectTo(tbChannel.videoChannel); + + tbChannel.StartReceive(); + tbChannel.StartSend(); + + error = ViE.ptrViERender->AddRenderer(tbCapture.captureId, _window1, 0, + 0.0, 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->StartRender(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->AddRenderer(tbChannel.videoChannel, _window2, 1, + 0.0, 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->StartRender(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + //*************************************************************** + // Engine ready. Begin testing class + //*************************************************************** + +#ifdef WEBRTC_SRTP + + // + // SRTP + // + unsigned char srtpKey1[30] = + { 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}; + unsigned char srtpKey2[30] = + { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, + 5, 4, 3, 2, 1, 0}; + // NULL + error = ViE.ptrViEEncryption->EnableSRTPReceive(tbChannel.videoChannel, + webrtc::kCipherNull, 0, + webrtc::kAuthNull, 0, 0, + webrtc::kNoProtection, + srtpKey1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend(tbChannel.videoChannel, + webrtc::kCipherNull, 0, + webrtc::kAuthNull, 0, 0, + webrtc::kNoProtection, + srtpKey1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log("SRTP NULL encryption/authentication"); + AutoTestSleep(KAutoTestSleepTimeMs); + error = ViE.ptrViEEncryption->DisableSRTPReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Encryption only + error = ViE.ptrViEEncryption->EnableSRTPReceive( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthNull, 0, 0, webrtc::kEncryption, srtpKey1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthNull, 0, 0, webrtc::kEncryption, srtpKey1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log("SRTP encryption only"); + AutoTestSleep(KAutoTestSleepTimeMs); + error = ViE.ptrViEEncryption->DisableSRTPReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Authentication only + error = ViE.ptrViEEncryption->EnableSRTPReceive(tbChannel.videoChannel, + webrtc::kCipherNull, 0, + webrtc::kAuthHmacSha1, 20, + 4, webrtc::kAuthentication, + srtpKey1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend(tbChannel.videoChannel, + webrtc::kCipherNull, 0, + webrtc::kAuthHmacSha1, 20, 4, + webrtc::kAuthentication, + srtpKey1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log("SRTP authentication only"); + AutoTestSleep(KAutoTestSleepTimeMs); + error = ViE.ptrViEEncryption->DisableSRTPReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Full protection + error = ViE.ptrViEEncryption->EnableSRTPReceive( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryptionAndAuthentication, + srtpKey1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryptionAndAuthentication, + srtpKey1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log("SRTP full protection"); + AutoTestSleep(KAutoTestSleepTimeMs); + error = ViE.ptrViEEncryption->DisableSRTPReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Change receive key, but not send key... + error = ViE.ptrViEEncryption->EnableSRTPReceive( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryptionAndAuthentication, + srtpKey2); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryptionAndAuthentication, + srtpKey1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log( + "\nSRTP receive key changed, you should not see any remote images"); + AutoTestSleep(KAutoTestSleepTimeMs); + + // Change send key too + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryptionAndAuthentication, + srtpKey2); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log("\nSRTP send key changed too, you should see remote video " + "again with some decoding artefacts at start"); + AutoTestSleep(KAutoTestSleepTimeMs); + error = ViE.ptrViEEncryption->DisableSRTPReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Disable receive, keep send + ViETest::Log("SRTP receive disabled , you shouldn't see any video"); + AutoTestSleep(KAutoTestSleepTimeMs); + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + +#endif //WEBRTC_SRTP + // + // External encryption + // + ViEAutotestEncryption testEncryption; + error + = ViE.ptrViEEncryption->RegisterExternalEncryption( + tbChannel.videoChannel, testEncryption); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log( + "External encryption/decryption added, you should still see video"); + AutoTestSleep(KAutoTestSleepTimeMs); + error + = ViE.ptrViEEncryption->DeregisterExternalEncryption( + tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViEEncryption Extended Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViEEncryption Extended Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + return 0; +} + +int ViEAutoTest::ViEEncryptionAPITest() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViEEncryption API Test\n"); + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + + //*************************************************************** + // Engine ready. Begin testing class + //*************************************************************** + + // Create VIE + tbInterfaces ViE("ViEEncryptionAPITest", numberOfErrors); + // Create a video channel + tbVideoChannel tbChannel(ViE, numberOfErrors, webrtc::kVideoCodecVP8); + + // Create a capture device + tbCaptureDevice tbCapture(ViE, numberOfErrors); + // Connect to channel + tbCapture.ConnectTo(tbChannel.videoChannel); + +#ifdef WEBRTC_SRTP + unsigned char srtpKey[30] = + { 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}; + + // + // EnableSRTPSend and DisableSRTPSend + // + + // Incorrect input argument, complete protection not enabled + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kNoProtection, srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryption, srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kAuthentication, srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Incorrect cipher key length + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 15, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryptionAndAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 257, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryptionAndAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherNull, 15, webrtc::kAuthHmacSha1, + 20, 4, webrtc::kEncryptionAndAuthentication, srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherNull, 257, webrtc::kAuthHmacSha1, + 20, 4, webrtc::kEncryptionAndAuthentication, srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Incorrect auth key length + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, + 30, webrtc::kAuthHmacSha1, 21, 4, webrtc::kEncryptionAndAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthNull, 257, 4, webrtc::kEncryptionAndAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 21, webrtc::kEncryptionAndAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthNull, 20, 13, webrtc::kEncryptionAndAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // NULL input + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryptionAndAuthentication, + NULL); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Double enable and disable + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryptionAndAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryptionAndAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error == -1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // No protection + error = ViE.ptrViEEncryption->EnableSRTPSend(tbChannel.videoChannel, + webrtc::kCipherNull, 0, + webrtc::kAuthNull, 0, 0, + webrtc::kNoProtection, + srtpKey); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Authentication only + error = ViE.ptrViEEncryption->EnableSRTPSend(tbChannel.videoChannel, + webrtc::kCipherNull, 0, + webrtc::kAuthHmacSha1, 20, 4, + webrtc::kAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend(tbChannel.videoChannel, + webrtc::kCipherNull, 0, + webrtc::kAuthHmacSha1, 1, 4, + webrtc::kAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend(tbChannel.videoChannel, + webrtc::kCipherNull, 0, + webrtc::kAuthHmacSha1, 20, 20, + webrtc::kAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend(tbChannel.videoChannel, + webrtc::kCipherNull, 0, + webrtc::kAuthHmacSha1, 1, 1, + webrtc::kAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Encryption only + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthNull, 0, 0, webrtc::kEncryption, srtpKey); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 16, + webrtc::kAuthNull, 0, 0, webrtc::kEncryption, srtpKey); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Full protection + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryptionAndAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // + // EnableSRTPReceive and DisableSRTPReceive + // + + // Incorrect input argument, complete protection not enabled + error = ViE.ptrViEEncryption->EnableSRTPReceive( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kNoProtection, srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPReceive( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryption, srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPReceive( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kAuthentication, srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Incorrect cipher key length + error = ViE.ptrViEEncryption->EnableSRTPReceive( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 15, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryptionAndAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPReceive( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 257, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryptionAndAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPReceive( + tbChannel.videoChannel, webrtc::kCipherNull, 15, webrtc::kAuthHmacSha1, + 20, 4, webrtc::kEncryptionAndAuthentication, srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPReceive( + tbChannel.videoChannel, webrtc::kCipherNull, 257, webrtc::kAuthHmacSha1, + 20, 4, webrtc::kEncryptionAndAuthentication, srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Incorrect auth key length + error = ViE.ptrViEEncryption->EnableSRTPReceive( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 21, 4, webrtc::kEncryptionAndAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPReceive( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthNull, 257, 4, webrtc::kEncryptionAndAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPReceive( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 21, webrtc::kEncryptionAndAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPReceive( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthNull, 20, 13, webrtc::kEncryptionAndAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // NULL input + error = ViE.ptrViEEncryption->EnableSRTPReceive( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryptionAndAuthentication, + NULL); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Double enable and disable + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryptionAndAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPSend( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryptionAndAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error == -1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // No protection + error = ViE.ptrViEEncryption->EnableSRTPReceive(tbChannel.videoChannel, + webrtc::kCipherNull, 0, + webrtc::kAuthNull, 0, 0, + webrtc::kNoProtection, + srtpKey); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Authentication only + + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPReceive(tbChannel.videoChannel, + webrtc::kCipherNull, 0, + webrtc::kAuthHmacSha1, 1, 4, + webrtc::kAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPReceive(tbChannel.videoChannel, + webrtc::kCipherNull, 0, + webrtc::kAuthHmacSha1, 20, + 20, webrtc::kAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPReceive(tbChannel.videoChannel, + webrtc::kCipherNull, 0, + webrtc::kAuthHmacSha1, 1, 1, + webrtc::kAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Encryption only + error = ViE.ptrViEEncryption->EnableSRTPReceive( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthNull, 0, 0, webrtc::kEncryption, srtpKey); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->EnableSRTPReceive( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 16, + webrtc::kAuthNull, 0, 0, webrtc::kEncryption, srtpKey); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Full protection + error = ViE.ptrViEEncryption->EnableSRTPReceive( + tbChannel.videoChannel, webrtc::kCipherAes128CounterMode, 30, + webrtc::kAuthHmacSha1, 20, 4, webrtc::kEncryptionAndAuthentication, + srtpKey); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DisableSRTPReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +#endif //WEBRTC_SRTP + // + // External encryption + // + + ViEAutotestEncryption testEncryption; + error = ViE.ptrViEEncryption->RegisterExternalEncryption( + tbChannel.videoChannel, testEncryption); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->RegisterExternalEncryption( + tbChannel.videoChannel, testEncryption); + numberOfErrors += ViETest::TestError(error == -1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DeregisterExternalEncryption( + tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEEncryption->DeregisterExternalEncryption( + tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViEEncryption API Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViEEncryption API Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + return 0; +} diff --git a/video_engine/main/test/AutoTest/source/vie_autotest_file.cc b/video_engine/main/test/AutoTest/source/vie_autotest_file.cc new file mode 100644 index 0000000000..7668c5d102 --- /dev/null +++ b/video_engine/main/test/AutoTest/source/vie_autotest_file.cc @@ -0,0 +1,858 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// +// vie_autotest_file.cc +// + +#include "vie_autotest_defines.h" +#include "vie_autotest.h" +#include "engine_configurations.h" + +#include "tb_interfaces.h" +#include "tb_capture_device.h" + +#include "voe_codec.h" + +// TODO: remove using ... +using namespace std; + +class ViEAutotestFileObserver: public ViEFileObserver +{ +public: + ViEAutotestFileObserver() {}; + ~ViEAutotestFileObserver() {}; + + void PlayFileEnded(const WebRtc_Word32 fileId) + { + ViETest::Log("PlayFile ended"); + } +}; + +int ViEAutoTest::ViEFileStandardTest() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViEFile Standard Test\n"); + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + + { + ViETest::Log("Starting a loopback call..."); + + tbInterfaces interfaces = tbInterfaces("ViEFileStandardTest", + numberOfErrors); + + VideoEngine* ptrViE = interfaces.ptrViE; + ViEBase* ptrViEBase = interfaces.ptrViEBase; + ViECapture* ptrViECapture = interfaces.ptrViECapture; + ViERender* ptrViERender = interfaces.ptrViERender; + ViECodec* ptrViECodec = interfaces.ptrViECodec; + ViERTP_RTCP* ptrViERtpRtcp = interfaces.ptrViERtpRtcp; + ViENetwork* ptrViENetwork = interfaces.ptrViENetwork; + + tbCaptureDevice captureDevice = tbCaptureDevice(interfaces, + numberOfErrors); + int captureId = captureDevice.captureId; + + int videoChannel = -1; + error = ptrViEBase->CreateChannel(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViECapture->ConnectCaptureDevice(captureId, videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViERtpRtcp->SetRTCPStatus(videoChannel, + kRtcpCompound_RFC4585); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViERtpRtcp->SetKeyFrameRequestMethod( + videoChannel, kViEKeyFrameRequestPliRtcp); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViERtpRtcp->SetTMMBRStatus(videoChannel, true); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViERender->AddRenderer(captureId, _window1, 0, 0.0, 0.0, + 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViERender->AddRenderer(videoChannel, _window2, 1, 0.0, 0.0, + 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViERender->StartRender(captureId); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViERender->StartRender(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + webrtc::VideoCodec videoCodec; + memset(&videoCodec, 0, sizeof(webrtc::VideoCodec)); + for (int idx = 0; idx < ptrViECodec->NumberOfCodecs(); idx++) + { + error = ptrViECodec->GetCodec(idx, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViECodec->SetReceiveCodec(videoChannel, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + } + + // Find the codec used for encoding the channel + for (int idx = 0; idx < ptrViECodec->NumberOfCodecs(); idx++) + { + error = ptrViECodec->GetCodec(idx, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + if (videoCodec.codecType == webrtc::kVideoCodecVP8) + { + error = ptrViECodec->SetSendCodec(videoChannel, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + break; + } + } + // Find the codec used for recording. + for (int idx = 0; idx < ptrViECodec->NumberOfCodecs(); idx++) + { + error = ptrViECodec->GetCodec(idx, videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + if (videoCodec.codecType == webrtc::kVideoCodecI420) + { + break; + } + } + + + const char* ipAddress = "127.0.0.1"; + const unsigned short rtpPort = 6000; + error = ptrViENetwork->SetLocalReceiver(videoChannel, rtpPort); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViEBase->StartReceive(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViENetwork->SetSendDestination(videoChannel, ipAddress, + rtpPort); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViEBase->StartSend(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + ViEFile* ptrViEFile = ViEFile::GetInterface(ptrViE); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + VoiceEngine* ptrVEEngine = VoiceEngine::Create(); + VoEBase* ptrVEBase = VoEBase::GetInterface(ptrVEEngine); + ptrVEBase->Init(); + + int audioChannel = ptrVEBase->CreateChannel(); + ptrViEBase->SetVoiceEngine(ptrVEEngine); + ptrViEBase->ConnectAudioChannel(videoChannel, audioChannel); + + webrtc::CodecInst audioCodec; + webrtc::VoECodec* ptrVECodec = + webrtc::VoECodec::GetInterface(ptrVEEngine); + for (int index = 0; index < ptrVECodec->NumOfCodecs(); index++) + { + ptrVECodec->GetCodec(index, audioCodec); + if (0 == strcmp(audioCodec.plname, "PCMU") || 0 + == strcmp(audioCodec.plname, "PCMA")) + { + break; // these two types are allowed as avi recording formats + } + } + + webrtc::CodecInst audioCodec2; + + //*************************************************************** + // Engine ready. Begin testing class + //*************************************************************** + + // Call started + ViETest::Log("Call started\nYou should see local preview from camera\n" + "in window 1 and the remote video in window 2."); + AutoTestSleep(2000); + + const int RENDER_TIMEOUT = 1000; + const int TEST_SPACING = 1000; + const int UI_UPDATE_INTERVAL = 2000; + const int VIDEO_LENGTH = 5000; + + + const char renderStartImage[1024] = VIE_TEST_FILES_ROOT "renderStartImage.jpg"; + const char captureDeviceImage[1024] = VIE_TEST_FILES_ROOT "captureDeviceImage.jpg"; + const char renderTimeoutFile[1024] = VIE_TEST_FILES_ROOT "renderTimeoutImage.jpg"; + const char snapshotCaptureDeviceFileName[256] = VIE_TEST_FILES_ROOT + "snapshotCaptureDevice.jpg"; + const char incomingVideo[1024] = VIE_TEST_FILES_ROOT "incomingVideo.avi"; + const char outgoingVideo[1024] = VIE_TEST_FILES_ROOT "outgoingVideo.avi"; + char snapshotRenderFileName[256] = VIE_TEST_FILES_ROOT "snapshotRenderer.jpg"; + + ViEPicture capturePicture; + ViEPicture renderPicture; + ViEPicture renderTimeoutPicture; // TODO: init with and image + + ViEAutotestFileObserver fileObserver; + int fileId; + + AutoTestSleep(TEST_SPACING); + + // testing StartRecordIncomingVideo and StopRecordIncomingVideo + { + ViETest::Log("Recording incoming video (currently no audio) for %d " + "seconds", VIDEO_LENGTH); + + error = ptrViEFile->StartRecordIncomingVideo(videoChannel, + incomingVideo, + NO_AUDIO, audioCodec2, + videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + AutoTestSleep(VIDEO_LENGTH); + ViETest::Log("Stop recording incoming video"); + + error = ptrViEFile->StopRecordIncomingVideo(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + ViETest::Log("Done\n"); + } + + AutoTestSleep(TEST_SPACING); + + // testing GetFileInformation + { + webrtc::VideoCodec fileVideoCodec; + webrtc::CodecInst fileAudioCodec; + ViETest::Log("Reading video file information"); + + error = ptrViEFile->GetFileInformation(incomingVideo, + fileVideoCodec, + fileAudioCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + PrintAudioCodec(fileAudioCodec); + PrintVideoCodec(fileVideoCodec); + } + + // testing StartPlayFile and RegisterObserver + { + ViETest::Log("Start playing file: %s with observer", incomingVideo); + error = ptrViEFile->StartPlayFile(incomingVideo, fileId); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + ViETest::Log("Registering file observer"); + error = ptrViEFile->RegisterObserver(fileId, fileObserver); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + ViETest::Log("Done\n"); + } + + // testing SendFileOnChannel and StopSendFileOnChannel + { + ViETest::Log("Sending video on channel"); + // should fail since we are sending the capture device. + error = ptrViEFile->SendFileOnChannel(fileId, videoChannel); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + // Disconnect the camera + error = ptrViECapture->DisconnectCaptureDevice(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + // And try playing the file again. + error = ptrViEFile->SendFileOnChannel(fileId, videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + AutoTestSleep(VIDEO_LENGTH); + ViETest::Log("Stopped sending video on channel"); + error = ptrViEFile->StopSendFileOnChannel(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + ViETest::Log("Done\n"); + } + + AutoTestSleep(TEST_SPACING); + + // stop playing the file + { + ViETest::Log("Stop playing the file."); + error = ptrViEFile->StopPlayFile(fileId); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + ViETest::Log("Done\n"); + } + + // testing StartRecordOutgoingVideo and StopRecordOutgoingVideo + { + // connect the camera to the output. + error = ptrViECapture->ConnectCaptureDevice(captureId, + videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + ViETest::Log("Recording outgoing video (currently no audio) for %d " + "seconds", VIDEO_LENGTH); + error = ptrViEFile->StartRecordOutgoingVideo(videoChannel, + outgoingVideo, + NO_AUDIO, audioCodec2, + videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + AutoTestSleep(VIDEO_LENGTH); + ViETest::Log("Stop recording outgoing video"); + error = ptrViEFile->StopRecordOutgoingVideo(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + ViETest::Log("Done\n"); + } + + // again testing GetFileInformation + { + error = ptrViEFile->GetFileInformation(incomingVideo, videoCodec, + audioCodec2); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + PrintAudioCodec(audioCodec2); + PrintVideoCodec(videoCodec); + } + + AutoTestSleep(TEST_SPACING); + + // GetCaptureDeviceSnapshot + { + ViETest::Log("Testing GetCaptureDeviceSnapshot(int, ViEPicture)"); + ViETest::Log("Taking a picture to use for displaying ViEPictures " + "for the rest of file test"); + ViETest::Log("Hold an object to the camera. Ready?..."); + AutoTestSleep(1000); + ViETest::Log("3"); + AutoTestSleep(1000); + ViETest::Log("...2"); + AutoTestSleep(1000); + ViETest::Log("...1"); + AutoTestSleep(1000); + ViETest::Log("...Taking picture!"); + error = ptrViEFile->GetCaptureDeviceSnapshot(captureId, + capturePicture); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + ViETest::Log("Remove paper. Picture has been taken"); + AutoTestSleep(TEST_SPACING); + + ViETest::Log("Done\n"); + } + + AutoTestSleep(TEST_SPACING); + + // GetRenderSnapshot + { + ViETest::Log("Testing GetRenderSnapshot(int, char*)"); + + ViETest::Log("Taking snapshot of videoChannel %d", captureId); + error = ptrViEFile->GetRenderSnapshot(captureId, + snapshotRenderFileName); + ViETest::Log("Wrote image to file %s", snapshotRenderFileName); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + ViETest::Log("Done\n"); + AutoTestSleep(TEST_SPACING); + } + + // GetRenderSnapshot + { + ViETest::Log("Testing GetRenderSnapshot(int, ViEPicture)"); + error = ptrViEFile->GetRenderSnapshot(captureId, renderPicture); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + ViETest::Log("Done\n"); + } + + AutoTestSleep(TEST_SPACING); + + // GetCaptureDeviceSnapshot + { + ViETest::Log("Testing GetCaptureDeviceSnapshot(int, char*)"); + ViETest::Log("Taking snapshot from capture device %d", captureId); + error = ptrViEFile->GetCaptureDeviceSnapshot( + captureId, snapshotCaptureDeviceFileName); + ViETest::Log("Wrote image to file %s", + snapshotCaptureDeviceFileName); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + ViETest::Log("Done\n"); + } + + AutoTestSleep(TEST_SPACING); + + // Testing: SetCaptureDeviceImage + { + ViETest::Log("Testing SetCaptureDeviceImage(int, char*)"); + error = ptrViECapture->StopCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViEFile->SetCaptureDeviceImage(captureId, + captureDeviceImage); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + ViETest::Log("you should see the capture device image now"); + AutoTestSleep(2 * RENDER_TIMEOUT); + error = ptrViECapture->StartCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + ViETest::Log("Done\n"); + } + + AutoTestSleep(TEST_SPACING); + + // Testing: SetCaptureDeviceImage + { + ViETest::Log("Testing SetCaptureDeviceImage(int, ViEPicture)"); + error = ptrViECapture->StopCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error + = ptrViEFile->SetCaptureDeviceImage(captureId, capturePicture); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + ViETest::Log("you should see the capture device image now"); + AutoTestSleep(2 * RENDER_TIMEOUT); + error = ptrViECapture->StartCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + ViETest::Log("Done\n"); + } + + AutoTestSleep(TEST_SPACING); + + // testing SetRenderStartImage(videoChannel, renderStartImage); + { + ViETest::Log("Testing SetRenderStartImage(int, char*)"); + // set render image, then stop capture and stop render to display it + ViETest::Log("Stoping renderer, setting start image, then " + "restarting"); + error = ptrViEFile->SetRenderStartImage(videoChannel, + renderStartImage); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + error = ptrViECapture->StopCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + error = ptrViERender->StopRender(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + ViETest::Log("Render start image should be displayed."); + AutoTestSleep(RENDER_TIMEOUT); + + // restarting capture and render + error = ptrViECapture->StartCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViERender->StartRender(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + ViETest::Log("Done\n"); + } + + AutoTestSleep(TEST_SPACING); + + // testing SetRenderStartImage(videoChannel, renderStartImage); + { + ViETest::Log("Testing SetRenderStartImage(int, ViEPicture)"); + // set render image, then stop capture and stop render to display it + ViETest::Log("Stoping renderer, setting start image, then " + "restarting"); + error = ptrViEFile->SetRenderStartImage(videoChannel, + capturePicture); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + error = ptrViECapture->StopCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + error = ptrViERender->StopRender(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + ViETest::Log("Render start image should be displayed."); + AutoTestSleep(RENDER_TIMEOUT); + + // restarting capture and render + error = ptrViECapture->StartCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + error = ptrViERender->StartRender(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + ViETest::Log("Done\n"); + } + + AutoTestSleep(TEST_SPACING); + + // testing SetRenderTimeoutImage(videoChannel, renderTimeoutFile, + // RENDER_TIMEOUT); + { + ViETest::Log("Testing SetRenderTimeoutImage(int, char*)"); + ViETest::Log("Stopping capture device to induce timeout of %d ms", + RENDER_TIMEOUT); + error = ptrViEFile->SetRenderTimeoutImage(videoChannel, + renderTimeoutFile, + RENDER_TIMEOUT); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + // now stop sending frames to the remote renderer and wait for + // timeout + error = ptrViECapture->StopCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + AutoTestSleep(RENDER_TIMEOUT); + ViETest::Log("Timeout image should be displayed now for %d ms", + RENDER_TIMEOUT * 2); + AutoTestSleep(RENDER_TIMEOUT * 2); + + // restart the capture device to undo the timeout + error = ptrViECapture->StartCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + ViETest::Log("Restarting capture device"); + AutoTestSleep(RENDER_TIMEOUT); + ViETest::Log("Done\n"); + } + + AutoTestSleep(TEST_SPACING); + + // Need to create a ViEPicture object to pass into this function. + // SetRenderTimeoutImage(videoChannel, renderTimeoutFile, + // RENDER_TIMEOUT); + { + ViETest::Log("Testing SetRenderTimeoutImage(int, ViEPicture)"); + ViETest::Log("Stopping capture device to induce timeout of %d", + RENDER_TIMEOUT); + error = ptrViEFile->SetRenderTimeoutImage(videoChannel, + capturePicture, + RENDER_TIMEOUT); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + // now stop sending frames to the remote renderer and wait for + // timeout + error = ptrViECapture->StopCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + AutoTestSleep(RENDER_TIMEOUT); + ViETest::Log("Timeout image should be displayed now for %d", + RENDER_TIMEOUT * 2); + AutoTestSleep(RENDER_TIMEOUT * 2); + + // restart the capture device to undo the timeout + error = ptrViECapture->StartCapture(captureId); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + ViETest::Log("Restarting capture device"); + ViETest::Log("Done\n"); + } + + // testing DeregisterObserver + { + ViETest::Log("Deregistering file observer"); + // Should fail since we don't observe this file. + error = ptrViEFile->DeregisterObserver(fileId, fileObserver); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + } + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + + error = ptrViEBase->StopReceive(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViEBase->StopSend(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViERender->StopRender(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViERender->RemoveRenderer(captureId); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViERender->RemoveRenderer(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViECapture->DisconnectCaptureDevice(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViEFile->FreePicture(capturePicture); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViEFile->FreePicture(renderPicture); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViEFile->FreePicture(renderTimeoutPicture); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + error = ptrViEBase->DeleteChannel(videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + int remainingInterfaces = 0; + + remainingInterfaces = ptrViEFile->Release(); + numberOfErrors += ViETest::TestError(remainingInterfaces == 0, + "ERROR:%d %s at line %d", + ptrViEBase->LastError(), + __FUNCTION__, __LINE__); + + } + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViEFile API Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViEFile Standard Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + + return 0; +} + +int ViEAutoTest::ViEFileExtendedTest() +{ + + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViEFile Extended Test\n"); + + ViETest::Log(" "); + ViETest::Log(" ViEFile Extended Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + return 0; +} + +int ViEAutoTest::ViEFileAPITest() +{ + + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViEFile API Test- nothing tested. Only tested in Standard test.\n"); + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + + + + ViETest::Log(" "); + ViETest::Log(" ViEFile API Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + return 0; +} + diff --git a/video_engine/main/test/AutoTest/source/vie_autotest_image_process.cc b/video_engine/main/test/AutoTest/source/vie_autotest_image_process.cc new file mode 100644 index 0000000000..b71d839f3c --- /dev/null +++ b/video_engine/main/test/AutoTest/source/vie_autotest_image_process.cc @@ -0,0 +1,420 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// +// vie_autotest_image_process.cc +// + +// Settings +#include "vie_autotest_defines.h" +#include "vie_autotest.h" +#include "engine_configurations.h" + +#include "tb_interfaces.h" +#include "tb_video_channel.h" +#include "tb_capture_device.h" + +class MyEffectFilter: public ViEEffectFilter +{ +public: + MyEffectFilter() {} + + ~MyEffectFilter() {} + + virtual int Transform(int size, unsigned char* frameBuffer, + unsigned int timeStamp90KHz, unsigned int width, + unsigned int height) + { + // Black and white + memset(frameBuffer + (2 * size) / 3, 0x7f, size / 3); + return 0; + } +}; + +int ViEAutoTest::ViEImageProcessStandardTest() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViEImageProcess Standard Test\n"); + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + + int rtpPort = 6000; + // Create VIE + tbInterfaces ViE("ViEImageProcessAPITest", numberOfErrors); + // Create a video channel + tbVideoChannel tbChannel(ViE, numberOfErrors, webrtc::kVideoCodecVP8); + // Create a capture device + tbCaptureDevice tbCapture(ViE, numberOfErrors); + + tbCapture.ConnectTo(tbChannel.videoChannel); + tbChannel.StartReceive(rtpPort); + tbChannel.StartSend(rtpPort); + + MyEffectFilter effectFilter; + + error = ViE.ptrViERender->AddRenderer(tbCapture.captureId, _window1, 0, + 0.0, 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->StartRender(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->AddRenderer(tbChannel.videoChannel, _window2, 1, + 0.0, 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->StartRender(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("Capture device is renderered in Window 1"); + ViETest::Log("Remote stream is renderered in Window 2"); + AutoTestSleep(KAutoTestSleepTimeMs); + + //*************************************************************** + // Engine ready. Begin testing class + //*************************************************************** + + + error + = ViE.ptrViEImageProcess->RegisterCaptureEffectFilter( + tbCapture.captureId, effectFilter); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("Black and white filter registered for capture device, " + "affects both windows"); + AutoTestSleep(KAutoTestSleepTimeMs); + + error = ViE.ptrViEImageProcess->DeregisterCaptureEffectFilter( + tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEImageProcess->RegisterRenderEffectFilter( + tbChannel.videoChannel, effectFilter); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("Remove capture effect filter, adding filter for incoming " + "stream"); + ViETest::Log("Only Window 2 should be black and white"); + AutoTestSleep(KAutoTestSleepTimeMs); + + error = ViE.ptrViERender->StopRender(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->RemoveRenderer(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + int rtpPort2 = rtpPort + 100; + // Create a video channel + tbVideoChannel tbChannel2(ViE, numberOfErrors, webrtc::kVideoCodecVP8); + + tbCapture.ConnectTo(tbChannel2.videoChannel); + tbChannel2.StartReceive(rtpPort2); + tbChannel2.StartSend(rtpPort2); + + error = ViE.ptrViERender->AddRenderer(tbChannel2.videoChannel, _window1, 1, + 0.0, 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->StartRender(tbChannel2.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEImageProcess->DeregisterRenderEffectFilter( + tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("Local renderer removed, added new channel and rendering in " + "Window1."); + + error = ViE.ptrViEImageProcess->RegisterCaptureEffectFilter( + tbCapture.captureId, effectFilter); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("Black and white filter registered for capture device, " + "affects both windows"); + AutoTestSleep(KAutoTestSleepTimeMs); + + error = ViE.ptrViEImageProcess->DeregisterCaptureEffectFilter( + tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEImageProcess->RegisterSendEffectFilter( + tbChannel.videoChannel, effectFilter); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("Capture filter removed."); + ViETest::Log("Black and white filter registered for one channel, Window2 " + "should be black and white"); + AutoTestSleep(KAutoTestSleepTimeMs); + + error = ViE.ptrViEImageProcess->DeregisterSendEffectFilter( + tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViEImageProcess Standard Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViEImageProcess Standard Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + return 0; +} + +int ViEAutoTest::ViEImageProcessExtendedTest() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViEImageProcess Extended Test\n"); + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + + numberOfErrors = ViEImageProcessStandardTest(); + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViEImageProcess Extended Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViEImageProcess Extended Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + return 0; +} + +int ViEAutoTest::ViEImageProcessAPITest() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViEImageProcess API Test\n"); + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + + int rtpPort = 6000; + tbInterfaces ViE("ViEImageProcessAPITest", numberOfErrors); + tbVideoChannel tbChannel(ViE, numberOfErrors, webrtc::kVideoCodecVP8); + tbCaptureDevice tbCapture(ViE, numberOfErrors); + + tbCapture.ConnectTo(tbChannel.videoChannel); + + MyEffectFilter effectFilter; + + // + // Capture effect filter + // + // Add effect filter + error = ViE.ptrViEImageProcess->RegisterCaptureEffectFilter( + tbCapture.captureId, effectFilter); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + // Add again -> error + error = ViE.ptrViEImageProcess->RegisterCaptureEffectFilter( + tbCapture.captureId, effectFilter); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->DeregisterCaptureEffectFilter( + tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + // Double deregister + error = ViE.ptrViEImageProcess->DeregisterCaptureEffectFilter( + tbCapture.captureId); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + // Non-existing capture device + error = ViE.ptrViEImageProcess->RegisterCaptureEffectFilter( + tbChannel.videoChannel, effectFilter); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // + // Render effect filter + // + error = ViE.ptrViEImageProcess->RegisterRenderEffectFilter( + tbChannel.videoChannel, effectFilter); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->RegisterRenderEffectFilter( + tbChannel.videoChannel, effectFilter); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->DeregisterRenderEffectFilter( + tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->DeregisterRenderEffectFilter( + tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + // Non-existing channel id + error = ViE.ptrViEImageProcess->RegisterRenderEffectFilter( + tbCapture.captureId, effectFilter); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // + // Send effect filter + // + error = ViE.ptrViEImageProcess->RegisterSendEffectFilter( + tbChannel.videoChannel, effectFilter); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->RegisterSendEffectFilter( + tbChannel.videoChannel, effectFilter); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->DeregisterSendEffectFilter( + tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->DeregisterSendEffectFilter( + tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->RegisterSendEffectFilter( + tbCapture.captureId, effectFilter); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // + // Denoising + // + error = ViE.ptrViEImageProcess->EnableDenoising(tbCapture.captureId, true); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->EnableDenoising(tbCapture.captureId, true); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->EnableDenoising(tbCapture.captureId, false); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->EnableDenoising(tbCapture.captureId, false); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->EnableDenoising(tbChannel.videoChannel, + true); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // + // Deflickering + // + error = ViE.ptrViEImageProcess->EnableDeflickering(tbCapture.captureId, + true); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->EnableDeflickering(tbCapture.captureId, + true); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->EnableDeflickering(tbCapture.captureId, + false); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->EnableDeflickering(tbCapture.captureId, + false); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->EnableDeflickering(tbChannel.videoChannel, + true); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // + // Color enhancement + // + error = ViE.ptrViEImageProcess->EnableColorEnhancement( + tbChannel.videoChannel, false); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->EnableColorEnhancement( + tbChannel.videoChannel, true); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->EnableColorEnhancement( + tbChannel.videoChannel, true); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->EnableColorEnhancement( + tbChannel.videoChannel, false); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->EnableColorEnhancement( + tbChannel.videoChannel, false); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEImageProcess->EnableColorEnhancement(tbCapture.captureId, + true); + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViEImageProcess Extended Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViEImageProcess Extended Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + return 0; +} diff --git a/video_engine/main/test/AutoTest/source/vie_autotest_linux.cc b/video_engine/main/test/AutoTest/source/vie_autotest_linux.cc new file mode 100644 index 0000000000..8f5f66e656 --- /dev/null +++ b/video_engine/main/test/AutoTest/source/vie_autotest_linux.cc @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// +// vie_autotest_linux.cc +// + +#include "vie_autotest_linux.h" + +#include "vie_autotest_defines.h" +#include "vie_autotest_main.h" + +#include "engine_configurations.h" +#include "critical_section_wrapper.h" +#include "thread_wrapper.h" + +ViEAutoTestWindowManager::ViEAutoTestWindowManager() : + _hdsp1(NULL), + _hdsp2(NULL) +{ +} + +ViEAutoTestWindowManager::~ViEAutoTestWindowManager() +{ + TerminateWindows(); +} + +void* ViEAutoTestWindowManager::GetWindow1() +{ + return (void*) _hwnd1; +} + +void* ViEAutoTestWindowManager::GetWindow2() +{ + return (void*) _hwnd2; +} + +int ViEAutoTestWindowManager::TerminateWindows() +{ + if (_hwnd1) + { + ViEDestroyWindow(&_hwnd1, _hdsp1); + } + if (_hwnd2) + { + ViEDestroyWindow(&_hwnd2, _hdsp2); + } + return 0; +} + +int ViEAutoTestWindowManager::CreateWindows(AutoTestRect window1Size, + AutoTestRect window2Size, + void* window1Title, + void* window2Title) +{ + ViECreateWindow(&_hwnd1, &_hdsp1, window1Size.origin.x, + window1Size.origin.y, window1Size.size.width, + window1Size.size.height, (char*) window1Title); + ViECreateWindow(&_hwnd2, &_hdsp2, window2Size.origin.x, + window2Size.origin.y, window2Size.size.width, + window2Size.size.height, (char*) window2Title); + + return 0; +} + +int ViEAutoTestWindowManager::ViECreateWindow(Window *outWindow, + Display **outDisplay, int xpos, + int ypos, int width, int height, + char* title) +{ + int screen; + XEvent evnt; + XSetWindowAttributes xswa; // window attribute struct + XVisualInfo vinfo; // screen visual info struct + unsigned long mask; // attribute mask + + // get connection handle to xserver + Display* _display = XOpenDisplay(NULL); + + // get screen number + screen = DefaultScreen(_display); + + // put desired visual info for the screen in vinfo + // TODO: more display settings should be allowed + if (XMatchVisualInfo(_display, screen, 24, TrueColor, &vinfo) != 0) + { + //printf( "Screen visual info match!\n" ); + } + // set window attributes + xswa.colormap = XCreateColormap(_display, DefaultRootWindow(_display), + vinfo.visual, AllocNone); + xswa.event_mask = StructureNotifyMask | ExposureMask; + xswa.background_pixel = 0; + xswa.border_pixel = 0; + + // value mask for attributes + mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask; + + Window _window = XCreateWindow(_display, DefaultRootWindow(_display), xpos, + ypos, width, height, 0, vinfo.depth, + InputOutput, vinfo.visual, mask, &xswa); + + // Set window name + XStoreName(_display, _window, title); + XSetIconName(_display, _window, title); + + // make x report events for mask + XSelectInput(_display, _window, StructureNotifyMask); + + // map the window to the display + XMapWindow(_display, _window); + + // wait for map event + do + { + XNextEvent(_display, &evnt); + + } while (evnt.type != MapNotify || evnt.xmap.event != _window); + + *outWindow = _window; + *outDisplay = _display; + return 0; +} + +int ViEAutoTestWindowManager::ViEDestroyWindow(Window *window, Display *display) +{ + XUnmapWindow(display, *window); + XDestroyWindow(display, *window); + XSync(display, false); + return 0; +} + +bool ViEAutoTestWindowManager::SetTopmostWindow() +{ + return 0; +} + +int main() +{ + ViEAutoTestMain autoTest; + autoTest.UseAnswerFile("answers.txt"); + return autoTest.BeginOSIndependentTesting(); + +} diff --git a/video_engine/main/test/AutoTest/source/vie_autotest_loopback.cc b/video_engine/main/test/AutoTest/source/vie_autotest_loopback.cc new file mode 100644 index 0000000000..042c564681 --- /dev/null +++ b/video_engine/main/test/AutoTest/source/vie_autotest_loopback.cc @@ -0,0 +1,509 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// +// vie_autotest_loopback.cc +// +// This code is also used as sample code for ViE 3.0 +// + +#include "vie_autotest_defines.h" +#include "vie_autotest.h" + +// =================================================================== +// +// BEGIN: VideoEngine 3.0 Sample Code +// + +#include "common_types.h" +#include "voe_base.h" +#include "vie_base.h" +#include "vie_capture.h" +#include "vie_codec.h" +#include "vie_network.h" +#include "vie_render.h" +#include "vie_rtp_rtcp.h" + +int VideoEngineSampleCode(void* window1, void* window2) +{ + //******************************************************** + // Begin create/initialize Video Engine for testing + //******************************************************** + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + + // + // Create a VideoEngine instance + // + VideoEngine* ptrViE = NULL; + ptrViE = VideoEngine::Create(); + if (ptrViE == NULL) + { + printf("ERROR in VideoEngine::Create\n"); + return -1; + } + + error = ptrViE->SetTraceFilter(webrtc::kTraceAll); + if (error == -1) + { + printf("ERROR in VideoEngine::SetTraceLevel\n"); + return -1; + } + +#ifdef ANDROID + error = ptrViE->SetTraceFile("/sdcard/ViETrace.txt"); + if (error == -1) + { + printf("ERROR in VideoEngine::SetTraceFile\n"); + return -1; + } + + error = ptrViE->SetTraceFile("/sdcard/ViEEncryptedTrace.txt"); + if (error == -1) + { + printf("ERROR in VideoEngine::SetTraceFile\n"); + return -1; + } +#else + error = ptrViE->SetTraceFile("ViETrace.txt"); + if (error == -1) + { + printf("ERROR in VideoEngine::SetTraceFile\n"); + return -1; + } + +#endif + + // + // Init VideoEngine and create a channel + // + ViEBase* ptrViEBase = ViEBase::GetInterface(ptrViE); + if (ptrViEBase == NULL) + { + printf("ERROR in ViEBase::GetInterface\n"); + return -1; + } + + error = ptrViEBase->Init(); + if (error == -1) + { + printf("ERROR in ViEBase::Init\n"); + return -1; + } + + int videoChannel = -1; + error = ptrViEBase->CreateChannel(videoChannel); + if (error == -1) + { + printf("ERROR in ViEBase::CreateChannel\n"); + return -1; + } + + // + // List available capture devices, allocate and connect. + // + ViECapture* ptrViECapture = ViECapture::GetInterface(ptrViE); + if (ptrViEBase == NULL) + { + printf("ERROR in ViECapture::GetInterface\n"); + return -1; + } + + const unsigned int KMaxDeviceNameLength = 128; + const unsigned int KMaxUniqueIdLength = 256; + char deviceName[KMaxDeviceNameLength]; + memset(deviceName, 0, KMaxDeviceNameLength); + char uniqueId[KMaxUniqueIdLength]; + memset(uniqueId, 0, KMaxUniqueIdLength); + + printf("Available capture devices:\n"); + int captureIdx = 0; + for (captureIdx = 0; + captureIdx < ptrViECapture->NumberOfCaptureDevices(); + captureIdx++) + { + memset(deviceName, 0, KMaxDeviceNameLength); + memset(uniqueId, 0, KMaxUniqueIdLength); + + error = ptrViECapture->GetCaptureDevice(captureIdx, deviceName, + KMaxDeviceNameLength, uniqueId, + KMaxUniqueIdLength); + if (error == -1) + { + printf("ERROR in ViECapture::GetCaptureDevice\n"); + return -1; + } + printf("\t %d. %s\n", captureIdx + 1, deviceName); + } + printf("\nChoose capture device: "); +#ifdef ANDROID + captureIdx = 0; + printf("0\n"); +#else + int dummy = scanf("%d", &captureIdx); + getchar(); + captureIdx = captureIdx - 1; // Compensate for idx start at 1. +#endif + error = ptrViECapture->GetCaptureDevice(captureIdx, deviceName, + KMaxDeviceNameLength, uniqueId, + KMaxUniqueIdLength); + if (error == -1) + { + printf("ERROR in ViECapture::GetCaptureDevice\n"); + return -1; + } + + int captureId = 0; + error = ptrViECapture->AllocateCaptureDevice(uniqueId, KMaxUniqueIdLength, + captureId); + if (error == -1) + { + printf("ERROR in ViECapture::AllocateCaptureDevice\n"); + return -1; + } + + error = ptrViECapture->ConnectCaptureDevice(captureId, videoChannel); + if (error == -1) + { + printf("ERROR in ViECapture::ConnectCaptureDevice\n"); + return -1; + } + + error = ptrViECapture->StartCapture(captureId); + if (error == -1) + { + printf("ERROR in ViECapture::StartCapture\n"); + return -1; + } + + // + // RTP/RTCP settings + // + ViERTP_RTCP* ptrViERtpRtcp = ViERTP_RTCP::GetInterface(ptrViE); + if (ptrViERtpRtcp == NULL) + { + printf("ERROR in ViERTP_RTCP::GetInterface\n"); + return -1; + } + + error = ptrViERtpRtcp->SetRTCPStatus(videoChannel, kRtcpCompound_RFC4585); + if (error == -1) + { + printf("ERROR in ViERTP_RTCP::SetRTCPStatus\n"); + return -1; + } + + error = ptrViERtpRtcp->SetKeyFrameRequestMethod(videoChannel, + kViEKeyFrameRequestPliRtcp); + if (error == -1) + { + printf("ERROR in ViERTP_RTCP::SetKeyFrameRequestMethod\n"); + return -1; + } + + error = ptrViERtpRtcp->SetTMMBRStatus(videoChannel, true); + if (error == -1) + { + printf("ERROR in ViERTP_RTCP::SetTMMBRStatus\n"); + return -1; + } + + // + // Set up rendering + // + ViERender* ptrViERender = ViERender::GetInterface(ptrViE); + if (ptrViERender == NULL) + { + printf("ERROR in ViERender::GetInterface\n"); + return -1; + } + + error + = ptrViERender->AddRenderer(captureId, window1, 0, 0.0, 0.0, 1.0, 1.0); + if (error == -1) + { + printf("ERROR in ViERender::AddRenderer\n"); + return -1; + } + + error = ptrViERender->StartRender(captureId); + if (error == -1) + { + printf("ERROR in ViERender::StartRender\n"); + return -1; + } + + error = ptrViERender->AddRenderer(videoChannel, window2, 1, 0.0, 0.0, 1.0, + 1.0); + if (error == -1) + { + printf("ERROR in ViERender::AddRenderer\n"); + return -1; + } + + error = ptrViERender->StartRender(videoChannel); + if (error == -1) + { + printf("ERROR in ViERender::StartRender\n"); + return -1; + } + + // + // Setup codecs + // + ViECodec* ptrViECodec = ViECodec::GetInterface(ptrViE); + if (ptrViECodec == NULL) + { + printf("ERROR in ViECodec::GetInterface\n"); + return -1; + } + + // Check available codecs and prepare receive codecs + printf("\nAvailable codecs:\n"); + webrtc::VideoCodec videoCodec; + memset(&videoCodec, 0, sizeof(webrtc::VideoCodec)); + int codecIdx = 0; + for (codecIdx = 0; codecIdx < ptrViECodec->NumberOfCodecs(); codecIdx++) + { + error = ptrViECodec->GetCodec(codecIdx, videoCodec); + if (error == -1) + { + printf("ERROR in ViECodec::GetCodec\n"); + return -1; + } + + // try to keep the test frame size small when I420 + if (videoCodec.codecType == webrtc::kVideoCodecI420) + { + videoCodec.width = 176; + videoCodec.height = 144; + } + + error = ptrViECodec->SetReceiveCodec(videoChannel, videoCodec); + if (error == -1) + { + printf("ERROR in ViECodec::SetReceiveCodec\n"); + return -1; + } + if (videoCodec.codecType != webrtc::kVideoCodecRED + && videoCodec.codecType != webrtc::kVideoCodecULPFEC) + { + printf("\t %d. %s\n", codecIdx + 1, videoCodec.plName); + } + } + printf("Choose codec: "); +#ifdef ANDROID + codecIdx = 0; + printf("0\n"); +#else + dummy = scanf("%d", &codecIdx); + getchar(); + codecIdx = codecIdx - 1; // Compensate for idx start at 1. +#endif + + error = ptrViECodec->GetCodec(codecIdx, videoCodec); + if (error == -1) + { + printf("ERROR in ViECodec::GetCodec\n"); + return -1; + } + + // try to keep the test frame size small when I420 + if (videoCodec.codecType == webrtc::kVideoCodecI420) + { + videoCodec.width = 176; + videoCodec.height = 144; + } + + error = ptrViECodec->SetSendCodec(videoChannel, videoCodec); + if (error == -1) + { + printf("ERROR in ViECodec::SetSendCodec\n"); + return -1; + } + + // + // Address settings + // + ViENetwork* ptrViENetwork = ViENetwork::GetInterface(ptrViE); + if (ptrViENetwork == NULL) + { + printf("ERROR in ViENetwork::GetInterface\n"); + return -1; + } + + const char* ipAddress = "127.0.0.1"; + const unsigned short rtpPort = 6000; + error = ptrViENetwork->SetLocalReceiver(videoChannel, rtpPort); + if (error == -1) + { + printf("ERROR in ViENetwork::SetLocalReceiver\n"); + return -1; + } + + error = ptrViEBase->StartReceive(videoChannel); + if (error == -1) + { + printf("ERROR in ViENetwork::StartReceive\n"); + return -1; + } + + error = ptrViENetwork->SetSendDestination(videoChannel, ipAddress, rtpPort); + if (error == -1) + { + printf("ERROR in ViENetwork::SetSendDestination\n"); + return -1; + } + + error = ptrViEBase->StartSend(videoChannel); + if (error == -1) + { + printf("ERROR in ViENetwork::StartSend\n"); + return -1; + } + + //******************************************************** + // Engine started + //******************************************************** + + + // Call started + printf("\nLoopback call started\n\n"); + printf("Press enter to stop..."); + while ((getchar()) != '\n') + ; + + //******************************************************** + // Testing finished. Tear down Video Engine + //******************************************************** + + error = ptrViEBase->StopReceive(videoChannel); + if (error == -1) + { + printf("ERROR in ViEBase::StopReceive\n"); + return -1; + } + + error = ptrViEBase->StopSend(videoChannel); + if (error == -1) + { + printf("ERROR in ViEBase::StopSend\n"); + return -1; + } + + error = ptrViERender->StopRender(captureId); + if (error == -1) + { + printf("ERROR in ViERender::StopRender\n"); + return -1; + } + + error = ptrViERender->RemoveRenderer(captureId); + if (error == -1) + { + printf("ERROR in ViERender::RemoveRenderer\n"); + return -1; + } + + error = ptrViERender->StopRender(videoChannel); + if (error == -1) + { + printf("ERROR in ViERender::StopRender\n"); + return -1; + } + + error = ptrViERender->RemoveRenderer(videoChannel); + if (error == -1) + { + printf("ERROR in ViERender::RemoveRenderer\n"); + return -1; + } + + error = ptrViECapture->StopCapture(captureId); + if (error == -1) + { + printf("ERROR in ViECapture::StopCapture\n"); + return -1; + } + + error = ptrViECapture->DisconnectCaptureDevice(videoChannel); + if (error == -1) + { + printf("ERROR in ViECapture::DisconnectCaptureDevice\n"); + return -1; + } + + error = ptrViECapture->ReleaseCaptureDevice(captureId); + if (error == -1) + { + printf("ERROR in ViECapture::ReleaseCaptureDevice\n"); + return -1; + } + + error = ptrViEBase->DeleteChannel(videoChannel); + if (error == -1) + { + printf("ERROR in ViEBase::DeleteChannel\n"); + return -1; + } + + int remainingInterfaces = 0; + remainingInterfaces = ptrViECodec->Release(); + remainingInterfaces += ptrViECapture->Release(); + remainingInterfaces += ptrViERtpRtcp->Release(); + remainingInterfaces += ptrViERender->Release(); + remainingInterfaces += ptrViENetwork->Release(); + remainingInterfaces += ptrViEBase->Release(); + if (remainingInterfaces > 0) + { + printf("ERROR: Could not release all interfaces\n"); + return -1; + } + + bool deleted = VideoEngine::Delete(ptrViE); + if (deleted == false) + { + printf("ERROR in VideoEngine::Delete\n"); + return -1; + } + + return 0; + + // + // END: VideoEngine 3.0 Sample Code + // + // =================================================================== +} + +int ViEAutoTest::ViELoopbackCall() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViE Autotest Loopback Call\n"); + + if (VideoEngineSampleCode(_window1, _window2) == 0) + { + ViETest::Log(" "); + ViETest::Log(" ViE Autotest Loopback Call Done"); + ViETest::Log("========================================"); + ViETest::Log(" "); + + return 0; + } + + ViETest::Log(" "); + ViETest::Log(" ViE Autotest Loopback Call Failed"); + ViETest::Log("========================================"); + ViETest::Log(" "); + return 1; + +} diff --git a/video_engine/main/test/AutoTest/source/vie_autotest_mac_carbon.cc b/video_engine/main/test/AutoTest/source/vie_autotest_mac_carbon.cc new file mode 100644 index 0000000000..b07cbe5d48 --- /dev/null +++ b/video_engine/main/test/AutoTest/source/vie_autotest_mac_carbon.cc @@ -0,0 +1,358 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_autotest_mac_carbon.cc + * + */ + +#include "engine_configurations.h" + +#if defined(CARBON_RENDERING) +#include "vie_autotest_mac_carbon.h" +#include "vie_autotest_defines.h" +#include "vie_autotest.h" +#include "vie_autotest_main.h" + +ViEAutoTestWindowManager::ViEAutoTestWindowManager() : + _carbonWindow1(new WindowRef()), + _carbonWindow2(new WindowRef()), + _hiView1(new HIViewRef()), + _hiView2(new HIViewRef()) +{ +} + +ViEAutoTestWindowManager::~ViEAutoTestWindowManager() +{ + if (_carbonWindow1EventHandlerRef) + RemoveEventHandler(_carbonWindow1EventHandlerRef); + + if (_carbonWindow2EventHandlerRef) + RemoveEventHandler(_carbonWindow2EventHandlerRef); + + if (_carbonHIView1EventHandlerRef) + RemoveEventHandler(_carbonHIView1EventHandlerRef); + + if (_carbonHIView2EventHandlerRef) + RemoveEventHandler(_carbonHIView2EventHandlerRef); + + delete _carbonWindow1; + delete _carbonWindow2; + delete _hiView1; + delete _hiView2; +} + +int ViEAutoTestWindowManager::CreateWindows(AutoTestRect window1Size, + AutoTestRect window2Size, + char* window1Title, + char* window2Title) +{ + + WindowAttributes windowAttributes = kWindowStandardDocumentAttributes + | kWindowStandardHandlerAttribute | kWindowCompositingAttribute; + Rect windowContentRect; + static const EventTypeSpec + windowEventTypes[] = { kEventClassWindow, kEventWindowBoundsChanged, + kEventClassWindow, kEventWindowBoundsChanging, kEventClassWindow, + kEventWindowZoomed, kEventClassWindow, kEventWindowExpanded, + kEventClassWindow, kEventWindowClickResizeRgn, kEventClassWindow, + kEventWindowClickDragRgn }; + + // ************* Window 1 and Event Handler *********************** + + SetRect(&windowContentRect, window1Size.origin.x, window1Size.origin.y, + window1Size.origin.x + window1Size.size.width, window1Size.origin.y + + window1Size.size.height); + + CreateNewWindow(kDocumentWindowClass, windowAttributes, &windowContentRect, + _carbonWindow1); + SetWindowTitleWithCFString(*_carbonWindow1, CFSTR("Carbon Window 1")); + ShowWindow(*_carbonWindow1); + InitCursor(); + InstallWindowEventHandler(*_carbonWindow1, + NewEventHandlerUPP(HandleWindowEvent), + GetEventTypeCount(windowEventTypes), + windowEventTypes, (void*) this, + &_carbonWindow1EventHandlerRef); + + // ************* Window 2 and Event Handler *********************** + + SetRect(&windowContentRect, window2Size.origin.x, window2Size.origin.y, + window2Size.origin.x + window2Size.size.width, window2Size.origin.y + + window2Size.size.height); + + CreateNewWindow(kDocumentWindowClass, windowAttributes, &windowContentRect, + _carbonWindow2); + SetWindowTitleWithCFString(*_carbonWindow2, CFSTR("Carbon Window 2")); + ShowWindow(*_carbonWindow2); + InitCursor(); + InstallWindowEventHandler(*_carbonWindow2, + NewEventHandlerUPP(HandleWindowEvent), + GetEventTypeCount(windowEventTypes), + windowEventTypes, (void*) this, + &_carbonWindow2EventHandlerRef); + +#if defined(HIVIEWREF_MODE) + OSStatus status; + static const EventTypeSpec hiviewEventTypes[] = { kEventClassControl, + kEventControlBoundsChanged, kEventClassControl, kEventControlDraw }; + + HIRect hiView1Rect = { 10, 10, 200, 200 }; + status = HICreateCustomView(&hiView1Rect, &_hiView1); + status = HIViewAddSubview(&_carbonWindow1, _hiView1); + HIViewSetZOrder(_hiView1, kHIViewZOrderAbove, NULL); + HIViewSetVisible(_hiView1, true); + + HIViewInstallEventHandler(_hiView1, NewEventHandlerUPP(HandleHIViewEvent), + GetEventTypeCount(hiviewEventTypes), + hiviewEventTypes, (void *) this, + &_carbonHIView1EventHandlerRef); + + HIRect hiView2Rect = { 10, 10, 200, 200 }; + status = HICreateCustomView(&hiView2Rect, &_hiView2); + status = HIViewAddSubview(&_carbonWindow2, _hiView2); + HIViewSetZOrder(_hiView2, kHIViewZOrderAbove, NULL); + HIViewSetVisible(_hiView2, true); + + HIViewInstallEventHandler(_hiView2, NewEventHandlerUPP(HandleHIViewEvent), + GetEventTypeCount(hiviewEventTypes), + hiviewEventTypes, (void *) this, + &_carbonHIView2EventHandlerRef); +#endif + + return 0; +} + +pascal OSStatus ViEAutoTestWindowManager::HandleWindowEvent( + EventHandlerCallRef nextHandler, EventRef theEvent, void* userData) +{ + + WindowRef windowRef = NULL; + + int eventType = GetEventKind(theEvent); + + // see https://dcs.sourcerepo.com/dcs/tox_view/trunk/tox/libraries/ + // i686-win32/include/quicktime/CarbonEvents.h for a list of codes + GetEventParameter(theEvent, kEventParamDirectObject, typeWindowRef, NULL, + sizeof(WindowRef), NULL, &windowRef); + + ViEAutoTestWindowManager* obj = (ViEAutoTestWindowManager*) (userData); + + if (windowRef == obj->GetWindow1()) + { + // event was triggered on window 1 + } + else if (windowRef == obj->GetWindow2()) + { + // event was triggered on window 2 + } + + if (kEventWindowBoundsChanged == eventType) + { + } + else if (kEventWindowBoundsChanging == eventType) + { + } + else if (kEventWindowZoomed == eventType) + { + } + else if (kEventWindowExpanding == eventType) + { + } + else if (kEventWindowExpanded == eventType) + { + } + else if (kEventWindowClickResizeRgn == eventType) + { + } + else if (kEventWindowClickDragRgn == eventType) + { + } + else + { + } + + return noErr; +} + +pascal OSStatus ViEAutoTestWindowManager::HandleHIViewEvent( + EventHandlerCallRef nextHandler, EventRef theEvent, void* userData) +{ + HIViewRef hiviewRef = NULL; + + // see https://dcs.sourcerepo.com/dcs/tox_view/trunk/tox/libraries/ + // i686-win32/include/quicktime/CarbonEvents.h for a list of codes + int eventType = GetEventKind(theEvent); + OSStatus status = noErr; + status = GetEventParameter(theEvent, kEventParamDirectObject, + typeControlRef, NULL, sizeof(ControlRef), NULL, + &hiviewRef); + + if (GetEventClass(theEvent) == kEventClassControl) + { + if (GetEventKind(theEvent) == kEventControlDraw) + { + ViEAutoTestWindowManager* obj = + (ViEAutoTestWindowManager*) (userData); + + CGContextRef context; + status = GetEventParameter(theEvent, kEventParamCGContextRef, + typeCGContextRef, NULL, sizeof(context), + NULL, &context); + HIRect viewBounds; + + HIViewRef* ptrHIViewRef = + static_cast (obj->GetWindow1()); + if (hiviewRef == *ptrHIViewRef) + { + // color hiview1 + CGContextSetRGBFillColor(context, 1, 0, 0, 1); + HIViewGetBounds(hiviewRef, &viewBounds); + CGContextFillRect(context, viewBounds); + } + + ptrHIViewRef = static_cast (obj->GetWindow1()); + if (hiviewRef == *ptrHIViewRef) + { + CGContextSetRGBFillColor(context, 0, 1, 0, 1); + HIViewGetBounds(hiviewRef, &viewBounds); + CGContextFillRect(context, viewBounds); + } + + } + } + + /* + + + VideoRenderAGL* obj = (VideoRenderAGL*)(userData); + WindowRef parentWindow = HIViewGetWindow(hiviewRef); + bool updateUI = true; + + if(kEventControlBoundsChanged == eventType){ + } + else if(kEventControlDraw == eventType){ + } + else{ + updateUI = false; + } + + if(true == updateUI){ + obj->ParentWindowResized(parentWindow); + obj->UpdateClipping(); + obj->RenderOffScreenBuffers(); + } + */ + + return status; +} + +int ViEAutoTestWindowManager::TerminateWindows() +{ + return 0; +} + +void* ViEAutoTestWindowManager::GetWindow1() +{ +#if defined(HIVIEWREF_MODE) + return (void*)_hiView1; +#else + return (void*) _carbonWindow1; +#endif + +} +void* ViEAutoTestWindowManager::GetWindow2() +{ +#if defined(HIVIEWREF_MODE) + return (void*)_hiView2; +#else + return (void*) _carbonWindow2; +#endif + +} + +bool ViEAutoTestWindowManager::SetTopmostWindow() +{ + return true; +} + +/* + + int main (int argc, const char * argv[]) + { + ViEAutoTestMain autoTest; + + if(argc > 1){ + autoTest.UseAnswerFile(argv[1]); + } + + int success = autoTest.BeginOSIndependentTesting(); + + } + + */ + +int main(int argc, const char * argv[]) +{ + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + [NSApplication sharedApplication]; + + // we have to run the test in a secondary thread because we need to run a runloop, which blocks + if (argc > 1) + { +AutoTestClass * autoTestClass = [[AutoTestClass alloc]init]; + [NSThread detachNewThreadSelector:@selector(autoTestWithArg:) + toTarget:autoTestClass withObject:[NSString stringWithFormat:@"%s", + argv[1]]]; +} +else +{ + AutoTestClass* autoTestClass = [[AutoTestClass alloc]init]; + [NSThread detachNewThreadSelector:@selector(autoTestWithArg:) + toTarget:autoTestClass withObject:nil]; +} + +// process OS events. Blocking call +[[NSRunLoop currentRunLoop]run]; +[pool release]; +} + +@implementation AutoTestClass + +-(void)autoTestWithArg:(NSString*)answerFile; +{ + + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + ViEAutoTestMain autoTest; + + if(NSOrderedSame != [answerFile compare:@""]) + { + char answerFileUTF8[1024] = ""; + strcpy(answerFileUTF8, (char*)[answerFileUTF8 UTF8]); + autoTest.UseAnswerFile(answerFileUTF8); + } + + int success = autoTest.BeginOSIndependentTesting(); + + [pool release]; + return; +} +// TODO: move window creation to Obj-c class so GUI commands can be run on the +// main NSThread +// -(void)createWindow1:(AutoTestRect)window1Size +// AndWindow2:(AutoTestRect)window2Size WithTitle1:(char*)window1Title +// AndTitle2:(char*)window2Title{ + +@end + +#endif + diff --git a/video_engine/main/test/AutoTest/source/vie_autotest_mac_cocoa.cc b/video_engine/main/test/AutoTest/source/vie_autotest_mac_cocoa.cc new file mode 100644 index 0000000000..df9290eab9 --- /dev/null +++ b/video_engine/main/test/AutoTest/source/vie_autotest_mac_cocoa.cc @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "engine_configurations.h" + +#if defined(COCOA_RENDERING) +#include "vie_autotest_mac_cocoa.h" +#include "vie_autotest_defines.h" +#include "vie_autotest.h" +#include "vie_autotest_main.h" + +ViEAutoTestWindowManager::ViEAutoTestWindowManager() : + _cocoaRenderView1(nil), _cocoaRenderView2(nil) +{ + +} + +ViEAutoTestWindowManager::~ViEAutoTestWindowManager() +{ + if (_cocoaRenderView1) + { + [ _cocoaRenderView1 release]; + } + if(_cocoaRenderView2) + { + [_cocoaRenderView2 release]; + } +} + +int ViEAutoTestWindowManager::CreateWindows(AutoTestRect window1Size, + AutoTestRect window2Size, + void* window1Title, + void* window2Title) +{ + NSRect outWindow1Frame = NSMakeRect(window1Size.origin.x, + window1Size.origin.y, + window1Size.size.width, + window1Size.size.height); + NSWindow* outWindow1 = [[NSWindow alloc] initWithContentRect:outWindow1Frame + styleMask:NSTitledWindowMask + backing:NSBackingStoreBuffered defer:NO]; + [outWindow1 orderOut:nil]; + NSRect cocoaRenderView1Frame = NSMakeRect(0, 0, window1Size.size.width, + window1Size.size.height); + _cocoaRenderView1 = [[CocoaRenderView alloc] + initWithFrame:cocoaRenderView1Frame]; + [[outWindow1 contentView] addSubview:_cocoaRenderView1]; + [outWindow1 setTitle:[NSString stringWithFormat:@"%s", window1Title]]; + [outWindow1 makeKeyAndOrderFront:NSApp]; + + NSRect outWindow2Frame = NSMakeRect(window2Size.origin.x, + window2Size.origin.y, + window2Size.size.width, + window2Size.size.height); + NSWindow* outWindow2 = [[NSWindow alloc] initWithContentRect:outWindow2Frame + styleMask:NSTitledWindowMask + backing:NSBackingStoreBuffered defer:NO]; + [outWindow2 orderOut:nil]; + NSRect cocoaRenderView2Frame = NSMakeRect(0, 0, window2Size.size.width, + window2Size.size.height); + _cocoaRenderView2 = [[CocoaRenderView alloc] + initWithFrame:cocoaRenderView2Frame]; + [[outWindow2 contentView] addSubview:_cocoaRenderView2]; + [outWindow2 setTitle:[NSString stringWithFormat:@"%s", window2Title]]; + [outWindow2 makeKeyAndOrderFront:NSApp]; + + return 0; +} + +int ViEAutoTestWindowManager::TerminateWindows() +{ + return 0; +} + +void* ViEAutoTestWindowManager::GetWindow1() +{ + return _cocoaRenderView1; +} + +void* ViEAutoTestWindowManager::GetWindow2() +{ + return _cocoaRenderView2; +} + +bool ViEAutoTestWindowManager::SetTopmostWindow() +{ + return true; +} + +int main(int argc, const char * argv[]) +{ + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + [NSApplication sharedApplication]; + +#if defined(MAC_COCOA_USE_NSRUNLOOP) + // we have to run the test in a secondary thread because we need to run a + // runloop, which blocks + if (argc > 1) + { + AutoTestClass * autoTestClass = [[AutoTestClass alloc]init]; + [NSThread detachNewThreadSelector:@selector(autoTestWithArg:) + toTarget:autoTestClass withObject:[NSString stringWithFormat:@"%s", + argv[1]]]; + } + else + { + AutoTestClass* autoTestClass = [[AutoTestClass alloc]init]; + [NSThread detachNewThreadSelector:@selector(autoTestWithArg:) + toTarget:autoTestClass withObject:nil]; + } + +// process OS events. Blocking call +[[NSRunLoop mainRunLoop]run]; + +#else + + ViEAutoTestMain autoTest; + int success = autoTest.BeginOSIndependentTesting(); +#endif +[pool release]; +} + +@implementation AutoTestClass + +-(void)autoTestWithArg:(NSString*)answerFile; +{ + + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + ViEAutoTestMain autoTest; + + if(NSOrderedSame != [answerFile compare:@""]) + { + char answerFileUTF8[1024] = ""; + strcpy(answerFileUTF8, (char*)[answerFileUTF8 UTF8]); + autoTest.UseAnswerFile(answerFileUTF8); + } + + int success = autoTest.BeginOSIndependentTesting(); + + [pool release]; + return; +} + +@end + +#endif + diff --git a/video_engine/main/test/AutoTest/source/vie_autotest_main.cc b/video_engine/main/test/AutoTest/source/vie_autotest_main.cc new file mode 100644 index 0000000000..df1c262f0a --- /dev/null +++ b/video_engine/main/test/AutoTest/source/vie_autotest_main.cc @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * vie_autotest_main.cc + * + */ + +#include "vie_autotest.h" +#include "vie_autotest_defines.h" +#include "vie_autotest_main.h" +#include "vie_codec.h" +#include "voe_codec.h" + +#if defined(WIN32) + #include "vie_autotest_windows.h" + #include + #include //ShellExecute +#elif defined(WEBRTC_MAC_INTEL) + #if defined(COCOA_RENDERING) + #include "vie_autotest_mac_cocoa.h" +#elif defined(CARBON_RENDERING) + #include "vie_autotest_mac_carbon.h" +#endif +#elif defined(WEBRTC_LINUX) + #include "vie_autotest_linux.h" +#endif + +ViEAutoTestMain::ViEAutoTestMain() : + _answers(), + _answersCount(0), + _useAnswerFile() +{ +} + +bool ViEAutoTestMain::BeginOSIndependentTesting() +{ + // Create platform dependent render windows + ViEAutoTestWindowManagerInterface* windowManager = + new ViEAutoTestWindowManager(); + +#if (defined(_WIN32)) + TCHAR window1Title[1024] = _T("ViE Autotest Window 1"); + TCHAR window2Title[1024] = _T("ViE Autotest Window 2"); +#else + char window1Title[1024] = "ViE Autotest Window 1"; + char window2Title[1024] = "ViE Autotest Window 2"; +#endif + + AutoTestRect window1Size(352, 288, 600, 100); + AutoTestRect window2Size(352, 288, 1000, 100); + windowManager->CreateWindows(window1Size, window2Size, window1Title, + window2Title); + windowManager->SetTopmostWindow(); + + // Create the test cases + ViEAutoTest vieAutoTest(windowManager->GetWindow1(), + windowManager->GetWindow2()); + + ViETest::Log(" ============================== "); + ViETest::Log(" WebRTC ViE 3.x Autotest "); + ViETest::Log(" ============================== \n"); + + int testType = 0; + int testErrors = 0; + do + { + ViETest::Log("Test types: "); + ViETest::Log("\t 0. Quit"); + ViETest::Log("\t 1. All standard tests (delivery test)"); + ViETest::Log("\t 2. All API tests"); + ViETest::Log("\t 3. All extended test"); + ViETest::Log("\t 4. Specific standard test"); + ViETest::Log("\t 5. Specific API test"); + ViETest::Log("\t 6. Specific extended test"); + ViETest::Log("\t 7. Simple loopback call"); + ViETest::Log("\t 8. Custom configure a call"); + ViETest::Log("Select type of test: "); + + if (_useAnswerFile) + { + //GetNextAnswer(str); + } + else + { + int dummy = scanf("%d", &testType); + getchar(); + } + ViETest::Log(""); + + if (testType < 1 && testType > 7) + { + ViETest::Log("ERROR: Invalid selection. Try again"); + continue; + } + + switch (testType) + { + case 0: + break; + + case 1: + { + int deliveryErrors = testErrors; + testErrors += vieAutoTest.ViEStandardTest(); + if (testErrors == deliveryErrors) + { + // No errors found in delivery test, create delivery + ViETest::Log("Standard/delivery passed."); + } + else + { + // Didn't pass, don't create delivery files + ViETest::Log("\nStandard/delivery test failed!\n"); + } + break; + } + case 2: + testErrors += vieAutoTest.ViEAPITest(); + break; + + case 3: + testErrors += vieAutoTest.ViEExtendedTest(); + break; + + case 4: // specific Standard + testType = GetClassTestSelection(); + + switch (testType) + { + case 1: // base + testErrors += vieAutoTest.ViEBaseStandardTest(); + break; + + case 2: // capture + testErrors += vieAutoTest.ViECaptureStandardTest(); + break; + + case 3: // codec + testErrors += vieAutoTest.ViECodecStandardTest(); + break; + + case 5: //encryption + testErrors += vieAutoTest.ViEEncryptionStandardTest(); + break; + + case 6: // file + testErrors += vieAutoTest.ViEFileStandardTest(); + break; + + case 7: // image process + testErrors += vieAutoTest.ViEImageProcessStandardTest(); + break; + + case 8: // network + testErrors += vieAutoTest.ViENetworkStandardTest(); + break; + + case 9: // Render + testErrors += vieAutoTest.ViERenderStandardTest(); + break; + + case 10: // RTP/RTCP + testErrors += vieAutoTest.ViERtpRtcpStandardTest(); + break; + case 11: + break; + + default: + break; + } + break; + + case 5: // specific API + testType = GetClassTestSelection(); + + switch (testType) + { + case 1: // base + testErrors += vieAutoTest.ViEBaseAPITest(); + break; + + case 2: // capture + testErrors += vieAutoTest.ViECaptureAPITest(); + break; + + case 3: // codec + testErrors += vieAutoTest.ViECodecAPITest(); + break; + + case 5: //encryption + testErrors += vieAutoTest.ViEEncryptionAPITest(); + break; + + case 6: // file + testErrors += vieAutoTest.ViEFileAPITest(); + break; + + case 7: // image process + testErrors += vieAutoTest.ViEImageProcessAPITest(); + break; + + case 8: // network + testErrors += vieAutoTest.ViENetworkAPITest(); + break; + + case 9: // Render + testErrors += vieAutoTest.ViERenderAPITest(); + break; + + case 10: // RTP/RTCP + testErrors += vieAutoTest.ViERtpRtcpAPITest(); + break; + case 11: + break; + + default: + break; + } + break; + case 6: // specific extended + testType = GetClassTestSelection(); + + switch (testType) + { + case 1: // base + testErrors += vieAutoTest.ViEBaseExtendedTest(); + break; + + case 2: // capture + testErrors += vieAutoTest.ViECaptureExtendedTest(); + break; + + case 3: // codec + testErrors += vieAutoTest.ViECodecExtendedTest(); + break; + + case 5: //encryption + testErrors += vieAutoTest.ViEEncryptionExtendedTest(); + break; + + case 6: // file + testErrors += vieAutoTest.ViEFileExtendedTest(); + break; + + case 7: // image process + testErrors += vieAutoTest.ViEImageProcessExtendedTest(); + break; + + case 8: // network + testErrors += vieAutoTest.ViENetworkExtendedTest(); + break; + + case 9: // Render + testErrors += vieAutoTest.ViERenderExtendedTest(); + break; + + case 10: // RTP/RTCP + testErrors += vieAutoTest.ViERtpRtcpExtendedTest(); + break; + case 11: + break; + + default: + break; + } + break; + case 7: + testErrors += vieAutoTest.ViELoopbackCall(); + break; + case 8: + testErrors += vieAutoTest.ViECustomCall(); + break; + default: + break; + } + } while (testType != 0); + + windowManager->TerminateWindows(); + + if (testErrors) + { + ViETest::Log("Test done with errors, see ViEAutotestLog.txt for test " + "result.\n"); + } + else + { + ViETest::Log("Test done without errors, see ViEAutotestLog.txt for " + "test result.\n"); + } + printf("Press enter to quit..."); + char c; + while ((c = getchar()) != '\n' && c != EOF) + /* discard */; + + delete windowManager; + + return true; +} + +int ViEAutoTestMain::GetClassTestSelection() +{ + int testType = 0; + std::string answer; + int dummy = 0; + while (1) + { + ViETest::Log("Choose specific test: "); + ViETest::Log("\t 1. Base "); + ViETest::Log("\t 2. Capture"); + ViETest::Log("\t 3. Codec"); + ViETest::Log("\t 5. Encryption"); + ViETest::Log("\t 6. File"); + ViETest::Log("\t 7. Image Process"); + ViETest::Log("\t 8. Network"); + ViETest::Log("\t 9. Render"); + ViETest::Log("\t 10. RTP/RTCP"); + ViETest::Log("\t 11. Go back to previous menu"); + ViETest::Log("Select type of test: "); + + if (_useAnswerFile) + { + //GetNextAnswer(answer); + } + else + { + dummy = scanf("%d", &testType); + getchar(); + } + ViETest::Log("\n"); + if (testType >= 1 && testType <= 13) + { + return testType; + } + ViETest::Log("ERROR: Invalid selection. Try again"); + } + + return -1; +} + +bool ViEAutoTestMain::GetAnswer(int index, string& answer) +{ + if (!_useAnswerFile || index > _answersCount) + { + return false; + } + answer = _answers[index]; + return true; +} + +bool ViEAutoTestMain::IsUsingAnswerFile() +{ + + return _useAnswerFile; +} + +// TODO: write without stl +bool ViEAutoTestMain::UseAnswerFile(const char* fileName) +{ + return false; + /* + _useAnswerFile = false; + + ViETest::Log("Opening answer file: %s...", fileName); + + ifstream answerFile(fileName); + if(!answerFile) + { + ViETest::Log("failed! X(\n"); + return false; + } + + _answersCount = 1; + _answersIndex = 1; + char lineContent[128] = ""; + while(!answerFile.eof()) + { + answerFile.getline(lineContent, 128); + _answers[_answersCount++] = string(lineContent); + } + answerFile.close(); + + cout << "Success :)" << endl << endl; + + _useAnswerFile = true; + + return _useAnswerFile; + */ +} + + diff --git a/video_engine/main/test/AutoTest/source/vie_autotest_network.cc b/video_engine/main/test/AutoTest/source/vie_autotest_network.cc new file mode 100644 index 0000000000..9605913022 --- /dev/null +++ b/video_engine/main/test/AutoTest/source/vie_autotest_network.cc @@ -0,0 +1,1058 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// +// vie_autotest_network.cc +// + +#include "vie_autotest_defines.h" +#include "vie_autotest.h" +#include "engine_configurations.h" + +#include "tb_capture_device.h" +#include "tb_external_transport.h" +#include "tb_interfaces.h" +#include "tb_video_channel.h" + +#if defined(_WIN32) +#include +#elif defined(WEBRTC_MAC_INTEL) + +#endif + +class ViEAutoTestNetworkObserver: public ViENetworkObserver +{ +public: + ViEAutoTestNetworkObserver() + { + } + ~ViEAutoTestNetworkObserver() + { + } + virtual void OnPeriodicDeadOrAlive(const int videoChannel, const bool alive) + { + } + virtual void PacketTimeout(const int videoChannel, + const ViEPacketTimeout timeout) + { + } +}; + +int ViEAutoTest::ViENetworkStandardTest() +{ + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + + tbInterfaces ViE("ViENetworkStandardTest", numberOfErrors); // Create VIE + tbCaptureDevice tbCapture(ViE, numberOfErrors); + error = ViE.ptrViERender->AddRenderer(tbCapture.captureId, _window1, 0, + 0.0, 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->StartRender(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + { + // Create a video channel + tbVideoChannel tbChannel(ViE, numberOfErrors, webrtc::kVideoCodecVP8); + tbCapture.ConnectTo(tbChannel.videoChannel); + + error = ViE.ptrViERender->AddRenderer(tbChannel.videoChannel, _window2, + 1, 0.0, 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->StartRender(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + //*************************************************************** + // Engine ready. Begin testing class + //*************************************************************** + + + // + // Transport + // + tbExternalTransport testTransport(*ViE.ptrViENetwork); + error = ViE.ptrViENetwork->RegisterSendTransport(tbChannel.videoChannel, + testTransport); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEBase->StartReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEBase->StartSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error= ViE.ptrViERtpRtcp->SetKeyFrameRequestMethod(tbChannel.videoChannel, + kViEKeyFrameRequestPliRtcp); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("Call started using external transport, video should " + "see video in both windows\n"); + AutoTestSleep(KAutoTestSleepTimeMs); + + error = ViE.ptrViEBase->StopReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEBase->StopSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->DeregisterSendTransport( + tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + char myIpAddress[64]; + memset(myIpAddress, 0, 64); + unsigned short rtpPort = 1234; + memcpy(myIpAddress, "127.0.0.1", sizeof("127.0.0.1")); + error = ViE.ptrViENetwork->SetLocalReceiver(tbChannel.videoChannel, + rtpPort, rtpPort + 1, + myIpAddress); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendDestination(tbChannel.videoChannel, + myIpAddress, rtpPort, + rtpPort + 1, rtpPort); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEBase->StartReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEBase->StartSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("Changed to WebRTC SocketTransport, you should still see " + "video in both windows\n"); + AutoTestSleep(KAutoTestSleepTimeMs); + + error = ViE.ptrViENetwork->SetSourceFilter(tbChannel.videoChannel, + rtpPort + 10, rtpPort + 11, + myIpAddress); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log("Added UDP port filter for incorrect ports, you should " + "not see video in Window2"); + AutoTestSleep(2000); + error = ViE.ptrViENetwork->SetSourceFilter(tbChannel.videoChannel, + rtpPort, rtpPort + 1, + "123.1.1.0"); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log("Added IP filter for incorrect IP address, you should not " + "see video in Window2"); + AutoTestSleep(2000); + error = ViE.ptrViENetwork->SetSourceFilter(tbChannel.videoChannel, + rtpPort, rtpPort + 1, + myIpAddress); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log("Added IP filter for this computer, you should see video " + "in Window2 again\n"); + AutoTestSleep(KAutoTestSleepTimeMs); + + tbCapture.Disconnect(tbChannel.videoChannel); + } + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViENetwork Standard Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViENetwork Standard Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + return 0; +} + +int ViEAutoTest::ViENetworkExtendedTest() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViENetwork Extended Test\n"); + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + int numberOfErrors = ViENetworkStandardTest(); + + int error = 0; + bool succeeded = true; + + tbInterfaces ViE("ViENetworkExtendedTest", numberOfErrors); // Create VIE + tbCaptureDevice tbCapture(ViE, numberOfErrors); + error = ViE.ptrViERender->AddRenderer(tbCapture.captureId, _window1, 0, + 0.0, 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->StartRender(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + { + // + // ToS + // + // Create a video channel + tbVideoChannel tbChannel(ViE, numberOfErrors, webrtc::kVideoCodecVP8); + tbCapture.ConnectTo(tbChannel.videoChannel); + char* remoteIp = "192.168.200.1"; + int DSCP = 0; + bool useSetSockOpt = false; + + webrtc::VideoCodec videoCodec; + error = ViE.ptrViECodec->GetSendCodec(tbChannel.videoChannel, + videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + videoCodec.maxFramerate = 5; + error = ViE.ptrViECodec->SetSendCodec(tbChannel.videoChannel, + videoCodec); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + //*************************************************************** + // Engine ready. Begin testing class + //*************************************************************** + + char myIpAddress[64]; + memset(myIpAddress, 0, 64); + unsigned short rtpPort = 9000; + error = ViE.ptrViENetwork->GetLocalIP(myIpAddress, false); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetLocalReceiver(tbChannel.videoChannel, + rtpPort, rtpPort + 1, + myIpAddress); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendDestination(tbChannel.videoChannel, + remoteIp, rtpPort, + rtpPort + 1, rtpPort); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // ToS + error = ViE.ptrViENetwork->SetSendToS(tbChannel.videoChannel, 2); + if (error != 0) + { + ViETest::Log("ViESetSendToS error!."); + ViETest::Log("You must be admin to run these tests."); + ViETest::Log("On Win7 and late Vista, you need to right click the " + "exe and choose"); + ViETest::Log("\"Run as administrator\"\n"); + int dummyChar = getchar(); + } + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->GetSendToS(tbChannel.videoChannel, DSCP, + useSetSockOpt); // No ToS set + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEBase->StartReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEBase->StartSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("Use Wireshark to capture the outgoing video stream and " + "verify ToS settings\n"); + ViETest::Log(" DSCP set to 0x%x\n", DSCP); + AutoTestSleep(1000); + + error = ViE.ptrViENetwork->SetSendToS(tbChannel.videoChannel, 63); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->GetSendToS(tbChannel.videoChannel, DSCP, + useSetSockOpt); // No ToS set + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log(" DSCP set to 0x%x\n", DSCP); + AutoTestSleep(1000); + + error = ViE.ptrViENetwork->SetSendToS(tbChannel.videoChannel, 0); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendToS(tbChannel.videoChannel, 2, true); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->GetSendToS(tbChannel.videoChannel, DSCP, + useSetSockOpt); // No ToS set + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log(" DSCP set to 0x%x\n", DSCP); + AutoTestSleep(1000); + + error = ViE.ptrViENetwork->SetSendToS(tbChannel.videoChannel, 63, true); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->GetSendToS(tbChannel.videoChannel, DSCP, + useSetSockOpt); // No ToS set + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + ViETest::Log(" DSCP set to 0x%x\n", DSCP); + AutoTestSleep(1000); + + tbCapture.Disconnect(tbChannel.videoChannel); + } + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViENetwork Extended Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViENetwork Extended Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + return 0; +} + +int ViEAutoTest::ViENetworkAPITest() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViENetwork API Test\n"); + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + + tbInterfaces ViE("ViENetworkAPITest", numberOfErrors); // Create VIE + { + // Create a video channel + tbVideoChannel tbChannel(ViE, numberOfErrors, webrtc::kVideoCodecVP8); + + //*************************************************************** + // Engine ready. Begin testing class + //*************************************************************** + + // + // External transport + // + tbExternalTransport testTransport(*ViE.ptrViENetwork); + error = ViE.ptrViENetwork->RegisterSendTransport(tbChannel.videoChannel, + testTransport); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->RegisterSendTransport(tbChannel.videoChannel, + testTransport); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + unsigned char packet[1500]; + void* ptrPacket = (void*) packet; + packet[0] = 0x80; // V=2, P=0, X=0, CC=0 + packet[1] = 0x78; // M=0, PT = 120 (VP8) + error = ViE.ptrViENetwork->ReceivedRTPPacket(tbChannel.videoChannel, + packet, 1500); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->ReceivedRTCPPacket(tbChannel.videoChannel, + packet, 1500); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEBase->StartReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->ReceivedRTPPacket(tbChannel.videoChannel, + packet, 1500); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->ReceivedRTCPPacket(tbChannel.videoChannel, + packet, 1500); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->ReceivedRTPPacket(tbChannel.videoChannel, + packet, 11); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->ReceivedRTPPacket(tbChannel.videoChannel, + packet, 11); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->ReceivedRTPPacket(tbChannel.videoChannel, + packet, 3000); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->ReceivedRTPPacket(tbChannel.videoChannel, + packet, 3000); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEBase->StopReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEBase->StartSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->DeregisterSendTransport( + tbChannel.videoChannel); // Sending + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEBase->StopSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->DeregisterSendTransport( + tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->DeregisterSendTransport( + tbChannel.videoChannel); // Already deregistered + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // + // Local receiver + // + // TODO (perkj) change when B 4239431 is fixed. + /*error = ViE.ptrViENetwork->SetLocalReceiver(tbChannel.videoChannel, + 1234, 1234, "127.0.0.1"); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__);*/ + error = ViE.ptrViENetwork->SetLocalReceiver(tbChannel.videoChannel, + 1234, 1235, "127.0.0.1"); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetLocalReceiver(tbChannel.videoChannel, + 1234, 1235, "127.0.0.1"); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetLocalReceiver(tbChannel.videoChannel, + 1236, 1237, "127.0.0.1"); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + unsigned short rtpPort = 0; + unsigned short rtcpPort = 0; + char ipAddress[64]; + memset(ipAddress, 0, 64); + error = ViE.ptrViENetwork->GetLocalReceiver(tbChannel.videoChannel, + rtpPort, rtcpPort, + ipAddress); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEBase->StartReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetLocalReceiver(tbChannel.videoChannel, + 1234, 1235, "127.0.0.1"); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->GetLocalReceiver(tbChannel.videoChannel, + rtpPort, rtcpPort, + ipAddress); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEBase->StopReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // + // Send destination + // + error = ViE.ptrViENetwork->SetSendDestination(tbChannel.videoChannel, + "127.0.0.1", 1234, 1235, + 1234, 1235); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendDestination(tbChannel.videoChannel, + "127.0.0.1", 1236, 1237, + 1234, 1235); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + unsigned short sourceRtpPort = 0; + unsigned short sourceRtcpPort = 0; + error = ViE.ptrViENetwork->GetSendDestination(tbChannel.videoChannel, + ipAddress, rtpPort, + rtcpPort, sourceRtpPort, + sourceRtcpPort); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEBase->StartSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Not allowed while sending + error = ViE.ptrViENetwork->SetSendDestination(tbChannel.videoChannel, + "127.0.0.1", 1234, 1235, + 1234, 1235); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(ViE.ptrViEBase->LastError() + == kViENetworkAlreadySending, "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + error = ViE.ptrViEBase->StopSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViENetwork->SetSendDestination(tbChannel.videoChannel, + "127.0.0.1", 1234, 1235, + 1234, 1235); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEBase->StartSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViENetwork->GetSendDestination(tbChannel.videoChannel, + ipAddress, rtpPort, + rtcpPort, sourceRtpPort, + sourceRtcpPort); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEBase->StopSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // + // Address information + // + + // GetSourceInfo: Tested in functional test + error = ViE.ptrViENetwork->GetLocalIP(ipAddress, false); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + // TODO: IPv6 + + // + // Filter + // + error = ViE.ptrViENetwork->GetSourceFilter(tbChannel.videoChannel, + rtpPort, rtcpPort, ipAddress); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSourceFilter(tbChannel.videoChannel, + 1234, 1235, "10.10.10.10"); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSourceFilter(tbChannel.videoChannel, + 1236, 1237, "127.0.0.1"); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->GetSourceFilter(tbChannel.videoChannel, + rtpPort, rtcpPort, ipAddress); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSourceFilter(tbChannel.videoChannel, 0, + 0, NULL); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->GetSourceFilter(tbChannel.videoChannel, + rtpPort, rtcpPort, ipAddress); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } + { + tbVideoChannel tbChannel(ViE, numberOfErrors); // Create a video channel + error = ViE.ptrViENetwork->SetLocalReceiver(tbChannel.videoChannel, + 1234); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + int DSCP = 0; + bool useSetSockOpt = false; + // SetSockOpt should work without a locally bind socket + error = ViE.ptrViENetwork->GetSendToS(tbChannel.videoChannel, DSCP, + useSetSockOpt); // No ToS set + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(DSCP == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + // Invalid input + error = ViE.ptrViENetwork->SetSendToS(tbChannel.videoChannel, -1, true); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + // Invalid input + error = ViE.ptrViENetwork->SetSendToS(tbChannel.videoChannel, 64, true); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + // Valid + error = ViE.ptrViENetwork->SetSendToS(tbChannel.videoChannel, 20, true); + + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->GetSendToS(tbChannel.videoChannel, DSCP, + useSetSockOpt); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError((DSCP == 20 && useSetSockOpt + == true), "ERROR: %s at line %d", __FUNCTION__, __LINE__); + // Disable + error = ViE.ptrViENetwork->SetSendToS(tbChannel.videoChannel, 0, true); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->GetSendToS(tbChannel.videoChannel, DSCP, + useSetSockOpt); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(DSCP == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + char myIpAddress[64]; + memset(myIpAddress, 0, 64); + // Get local ip to be able to set ToS withtou setSockOpt + error = ViE.ptrViENetwork->GetLocalIP(myIpAddress, false); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetLocalReceiver(tbChannel.videoChannel, + 1234, 1235, myIpAddress); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + // Invalid input + error = ViE.ptrViENetwork->SetSendToS(tbChannel.videoChannel, -1, + false); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendToS(tbChannel.videoChannel, 64, + false); // Invalid input + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->GetSendToS(tbChannel.videoChannel, DSCP, + useSetSockOpt); // No ToS set + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(DSCP == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendToS(tbChannel.videoChannel, 20, + false); // Valid + if (error != 0) + { + ViETest::Log("ViESetSendToS error!."); + ViETest::Log("You must be admin to run these tests."); + ViETest::Log("On Win7 and late Vista, you need to right click the " + "exe and choose"); + ViETest::Log("\"Run as administrator\"\n"); + int dummyChar = getchar(); + } + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->GetSendToS(tbChannel.videoChannel, DSCP, + useSetSockOpt); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +#ifdef _WIN32 + numberOfErrors += ViETest::TestError((DSCP == 20 + && useSetSockOpt == false), + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +#else // useSetSockOpt is true on Linux and Mac + numberOfErrors += ViETest::TestError((DSCP == 20 + && useSetSockOpt == true), + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +#endif + error = ViE.ptrViENetwork->SetSendToS(tbChannel.videoChannel, 0, false); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->GetSendToS(tbChannel.videoChannel, DSCP, + useSetSockOpt); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(DSCP == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } + { + // From qos.h. (*) -> supported by ViE + // + // #define SERVICETYPE_NOTRAFFIC 0x00000000 + // #define SERVICETYPE_BESTEFFORT 0x00000001 (*) + // #define SERVICETYPE_CONTROLLEDLOAD 0x00000002 (*) + // #define SERVICETYPE_GUARANTEED 0x00000003 (*) + // #define SERVICETYPE_NETWORK_UNAVAILABLE 0x00000004 + // #define SERVICETYPE_GENERAL_INFORMATION 0x00000005 + // #define SERVICETYPE_NOCHANGE 0x00000006 + // #define SERVICETYPE_NONCONFORMING 0x00000009 + // #define SERVICETYPE_NETWORK_CONTROL 0x0000000A + // #define SERVICETYPE_QUALITATIVE 0x0000000D (*) + // + // #define SERVICE_BESTEFFORT 0x80010000 + // #define SERVICE_CONTROLLEDLOAD 0x80020000 + // #define SERVICE_GUARANTEED 0x80040000 + // #define SERVICE_QUALITATIVE 0x80200000 + + tbVideoChannel tbChannel(ViE, numberOfErrors); // Create a video channel + + +#if defined(_WIN32) + // No socket + error = ViE.ptrViENetwork->SetSendGQoS(tbChannel.videoChannel, true, + SERVICETYPE_BESTEFFORT); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViENetwork->SetLocalReceiver(tbChannel.videoChannel, + 1234); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + // Sender not initialized + error = ViE.ptrViENetwork->SetSendGQoS(tbChannel.videoChannel, true, + SERVICETYPE_BESTEFFORT); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendDestination(tbChannel.videoChannel, + "127.0.0.1", 12345); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Try to set all non-supported service types + error = ViE.ptrViENetwork->SetSendGQoS(tbChannel.videoChannel, true, + SERVICETYPE_NOTRAFFIC); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendGQoS(tbChannel.videoChannel, true, + SERVICETYPE_NETWORK_UNAVAILABLE); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendGQoS(tbChannel.videoChannel, true, + SERVICETYPE_GENERAL_INFORMATION); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendGQoS(tbChannel.videoChannel, true, + SERVICETYPE_NOCHANGE); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendGQoS(tbChannel.videoChannel, true, + SERVICETYPE_NONCONFORMING); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendGQoS(tbChannel.videoChannel, true, + SERVICETYPE_NOTRAFFIC); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendGQoS(tbChannel.videoChannel, true, + SERVICETYPE_NETWORK_CONTROL); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendGQoS(tbChannel.videoChannel, true, + SERVICE_BESTEFFORT); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendGQoS(tbChannel.videoChannel, true, + SERVICE_CONTROLLEDLOAD); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendGQoS(tbChannel.videoChannel, true, + SERVICE_GUARANTEED); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendGQoS(tbChannel.videoChannel, true, + SERVICE_QUALITATIVE); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Loop through valid service settings + bool enabled = false; + int serviceType = 0; + int overrideDSCP = 0; + + error = ViE.ptrViENetwork->GetSendGQoS(tbChannel.videoChannel, enabled, + serviceType, overrideDSCP); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(enabled == false, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendGQoS(tbChannel.videoChannel, true, + SERVICETYPE_BESTEFFORT); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->GetSendGQoS(tbChannel.videoChannel, enabled, + serviceType, overrideDSCP); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError((enabled == true && serviceType + == SERVICETYPE_BESTEFFORT && overrideDSCP == false), + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendGQoS(tbChannel.videoChannel, true, + SERVICETYPE_CONTROLLEDLOAD); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->GetSendGQoS(tbChannel.videoChannel, enabled, + serviceType, overrideDSCP); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError((enabled == true && serviceType + == SERVICETYPE_CONTROLLEDLOAD && overrideDSCP == false), + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendGQoS(tbChannel.videoChannel, true, + SERVICETYPE_GUARANTEED); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->GetSendGQoS(tbChannel.videoChannel, enabled, + serviceType, overrideDSCP); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError( + (enabled == true + && serviceType == SERVICETYPE_GUARANTEED + && overrideDSCP == false), + "ERROR: %s at line %d", __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendGQoS(tbChannel.videoChannel, true, + SERVICETYPE_QUALITATIVE); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->GetSendGQoS(tbChannel.videoChannel, enabled, + serviceType, overrideDSCP); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError((enabled == true && serviceType + == SERVICETYPE_QUALITATIVE && overrideDSCP == false), + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetSendGQoS(tbChannel.videoChannel, false, + SERVICETYPE_QUALITATIVE); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->GetSendGQoS(tbChannel.videoChannel, enabled, + serviceType, overrideDSCP); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(enabled == false, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +#endif + } + { + // + // MTU and packet burst + // + // Create a video channel + tbVideoChannel tbChannel(ViE, numberOfErrors); + // Invalid input + error = ViE.ptrViENetwork->SetMTU(tbChannel.videoChannel, 1600); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + // Invalid input + error = ViE.ptrViENetwork->SetMTU(tbChannel.videoChannel, 800); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // + // Observer and timeout + // + ViEAutoTestNetworkObserver vieTestObserver; + error = ViE.ptrViENetwork->RegisterObserver(tbChannel.videoChannel, + vieTestObserver); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->RegisterObserver(tbChannel.videoChannel, + vieTestObserver); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetPeriodicDeadOrAliveStatus( + tbChannel.videoChannel, true); // No observer + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->DeregisterObserver(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViENetwork->DeregisterObserver(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViENetwork->SetPeriodicDeadOrAliveStatus( + tbChannel.videoChannel, true); // No observer + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Packet timout notification + error = ViE.ptrViENetwork->SetPacketTimeoutNotification( + tbChannel.videoChannel, true, 10); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } +#if 0 + virtual int SendUDPPacket(const int videoChannel, const void* data, + const unsigned int length, int& transmittedBytes, + bool useRtcpSocket = false) = 0; +#endif + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViENetwork API Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViENetwork API Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + return 0; +} diff --git a/video_engine/main/test/AutoTest/source/vie_autotest_render.cc b/video_engine/main/test/AutoTest/source/vie_autotest_render.cc new file mode 100644 index 0000000000..718775b322 --- /dev/null +++ b/video_engine/main/test/AutoTest/source/vie_autotest_render.cc @@ -0,0 +1,500 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// +// vie_autotest_render.cc +// + +#include "vie_autotest_defines.h" +#include "vie_autotest.h" +#include "engine_configurations.h" + +#include "video_render.h" + +#include "tb_interfaces.h" +#include "tb_video_channel.h" +#include "tb_capture_device.h" + +#if defined(WIN32) +#include +#include +#include +#elif defined(WEBRTC_LINUX) + //From windgi.h + #undef RGB + #define RGB(r,g,b) ((unsigned long)(((unsigned char)(r)|((unsigned short)((unsigned char)(g))<<8))|(((unsigned long)(unsigned char)(b))<<16))) + //From ddraw.h +/* typedef struct _DDCOLORKEY + { + DWORD dwColorSpaceLowValue; // low boundary of color space that is to + DWORD dwColorSpaceHighValue; // high boundary of color space that is + } DDCOLORKEY;*/ +#elif defined(WEBRTC_MAC) +#endif + +class ViEAutoTestExternalRenderer: public ExternalRenderer +{ +public: + ViEAutoTestExternalRenderer() : + _width(0), + _height(0) + { + } + virtual int FrameSizeChange(unsigned int width, unsigned int height, + unsigned int numberOfStreams) + { + _width = width; + _height = height; + return 0; + } + + virtual int DeliverFrame(unsigned char* buffer, int bufferSize) + { + if (bufferSize != _width * _height * 3 / 2) + { + ViETest::Log("incorrect render buffer received, of length = %d\n", + bufferSize); + return 0; + } + ViETest::Log("callback DeliverFrame is good\n"); + return 0; + } + +public: + virtual ~ViEAutoTestExternalRenderer() + { + } +private: + int _width, _height; +}; + +int ViEAutoTest::ViERenderStandardTest() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViERender Standard Test\n"); + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + int rtpPort = 6000; + + tbInterfaces ViE("ViERender", numberOfErrors); + + // Create a video channel + tbVideoChannel tbChannel(ViE, numberOfErrors, webrtc::kVideoCodecVP8); + tbCaptureDevice tbCapture(ViE, numberOfErrors); // Create a capture device + tbCapture.ConnectTo(tbChannel.videoChannel); + tbChannel.StartReceive(rtpPort); + tbChannel.StartSend(rtpPort); + + error = ViE.ptrViERender->RegisterVideoRenderModule(*_vrm1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->AddRenderer(tbCapture.captureId, _window1, 0, + 0.0, 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->StartRender(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->RegisterVideoRenderModule(*_vrm2); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->AddRenderer(tbChannel.videoChannel, _window2, 1, + 0.0, 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->StartRender(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("\nCapture device is renderered in Window 1"); + ViETest::Log("Remote stream is renderered in Window 2"); + AutoTestSleep(KAutoTestSleepTimeMs); + + error = ViE.ptrViERender->StopRender(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->RemoveRenderer(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + +#ifndef ANDROID // PIP and full screen rendering is not supported on Android + error = ViE.ptrViERender->DeRegisterVideoRenderModule(*_vrm1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->AddRenderer(tbCapture.captureId, _window2, 0, + 0.75, 0.75, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->StartRender(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("\nCapture device is now rendered in Window 2, PiP."); + ViETest::Log("Switching to full screen rendering in %d seconds.\n", + KAutoTestSleepTimeMs / 1000); + AutoTestSleep(KAutoTestSleepTimeMs); + + error = ViE.ptrViERender->RemoveRenderer(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->RemoveRenderer(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->DeRegisterVideoRenderModule(*_vrm2); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Destroy render module and create new in full screen mode + VideoRender::DestroyVideoRender(_vrm1); + _vrm1 = NULL; + _vrm1 = VideoRender::CreateVideoRender(4563, _window1, true, _renderType); + numberOfErrors += ViETest::TestError(_vrm1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->RegisterVideoRenderModule(*_vrm1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->AddRenderer(tbCapture.captureId, _window1, 0, + 0.75f, 0.75f, 1.0f, 1.0f); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->StartRender(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->AddRenderer(tbChannel.videoChannel, _window1, 1, + 0.0, 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->StartRender(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + AutoTestSleep(KAutoTestSleepTimeMs); + + error = ViE.ptrViERender->RemoveRenderer(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->RemoveRenderer(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->DeRegisterVideoRenderModule(*_vrm1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Destroy full screen render module and create new in normal mode + VideoRender::DestroyVideoRender(_vrm1); + _vrm1 = NULL; + _vrm1 = VideoRender::CreateVideoRender(4561, _window1, false, _renderType); + numberOfErrors += ViETest::TestError(_vrm1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); +#endif + + //*************************************************************** + // Engine ready. Begin testing class + //*************************************************************** + + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + tbCapture.Disconnect(tbChannel.videoChannel); + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViERender Standard Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViERender Standard Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + + return 0; +} + +int ViEAutoTest::ViERenderExtendedTest() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViERender Extended Test\n"); + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + int rtpPort = 6000; + + tbInterfaces ViE("ViERender_API", numberOfErrors); + + // Create a video channel + tbVideoChannel tbChannel(ViE, numberOfErrors, webrtc::kVideoCodecVP8); + tbCaptureDevice tbCapture(ViE, numberOfErrors); // Create a capture device + tbCapture.ConnectTo(tbChannel.videoChannel); + tbChannel.StartReceive(rtpPort); + tbChannel.StartSend(rtpPort); + + error = ViE.ptrViERender->RegisterVideoRenderModule(*_vrm1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->AddRenderer(tbCapture.captureId, _window1, 0, + 0.0, 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->StartRender(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->RegisterVideoRenderModule(*_vrm2); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->AddRenderer(tbChannel.videoChannel, _window2, 1, + 0.0, 0.0, 1.0, 1.0); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->StartRender(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("\nCapture device is renderered in Window 1"); + ViETest::Log("Remote stream is renderered in Window 2"); + AutoTestSleep(KAutoTestSleepTimeMs); + +#ifdef _WIN32 + ViETest::Log("\nConfiguring Window2"); + ViETest::Log("you will see video only in first quadrant"); + error = ViE.ptrViERender->ConfigureRender(tbChannel.videoChannel, 0, 0.0f, + 0.0f, 0.5f, 0.5f); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + AutoTestSleep(KAutoTestSleepTimeMs); + + ViETest::Log("you will see video only in fourth quadrant"); + error = ViE.ptrViERender->ConfigureRender(tbChannel.videoChannel, 0, 0.5f, + 0.5f, 1.0f, 1.0f); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + AutoTestSleep(KAutoTestSleepTimeMs); + + ViETest::Log("normal video on Window2"); + error = ViE.ptrViERender->ConfigureRender(tbChannel.videoChannel, 0, 0.0f, + 0.0f, 1.0f, 1.0f); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + AutoTestSleep(KAutoTestSleepTimeMs); +#endif + + ViETest::Log("Mirroring Local Preview (Window1) Left-Right"); + error = ViE.ptrViERender->MirrorRenderStream(tbCapture.captureId, true, + false, true); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + AutoTestSleep(KAutoTestSleepTimeMs); + + ViETest::Log("\nMirroring Local Preview (Window1) Left-Right and Up-Down"); + error = ViE.ptrViERender->MirrorRenderStream(tbCapture.captureId, true, + true, true); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + AutoTestSleep(KAutoTestSleepTimeMs); + + ViETest::Log("\nMirroring Remote Window(Window2) Up-Down"); + error = ViE.ptrViERender->MirrorRenderStream(tbChannel.videoChannel, true, + true, false); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + AutoTestSleep(KAutoTestSleepTimeMs); + + ViETest::Log("Disabling Mirroing on Window1 and Window2"); + error = ViE.ptrViERender->MirrorRenderStream(tbCapture.captureId, false, + false, false); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + AutoTestSleep(KAutoTestSleepTimeMs); + error = ViE.ptrViERender->MirrorRenderStream(tbChannel.videoChannel, false, + false, false); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + AutoTestSleep(KAutoTestSleepTimeMs); + + ViETest::Log("\nEnabling Full Screen render in 5 sec"); + + error = ViE.ptrViERender->RemoveRenderer(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->DeRegisterVideoRenderModule(*_vrm1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->RemoveRenderer(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->DeRegisterVideoRenderModule(*_vrm2); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Destroy render module and create new in full screen mode + VideoRender::DestroyVideoRender(_vrm1); + _vrm1 = NULL; + _vrm1 = VideoRender::CreateVideoRender(4563, _window1, true, _renderType); + numberOfErrors += ViETest::TestError(_vrm1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->RegisterVideoRenderModule(*_vrm1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->AddRenderer(tbCapture.captureId, _window1, 0, + 0.0f, 0.0f, 1.0f, 1.0f); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->StartRender(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + AutoTestSleep(KAutoTestSleepTimeMs); + + error = ViE.ptrViERender->StopRender(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + /* error = ViE.ptrViERender->StopRender(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + */ + ViETest::Log("\nStop renderer"); + + error = ViE.ptrViERender->RemoveRenderer(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + /* error = ViE.ptrViERender->RemoveRenderer(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", __FUNCTION__, __LINE__); + */ + ViETest::Log("\nRemove renderer"); + + error = ViE.ptrViERender->DeRegisterVideoRenderModule(*_vrm1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Destroy full screen render module and create new for external rendering + VideoRender::DestroyVideoRender(_vrm1); + _vrm1 = NULL; + _vrm1 = VideoRender::CreateVideoRender(4564, NULL, false, _renderType); + numberOfErrors += ViETest::TestError(_vrm1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->RegisterVideoRenderModule(*_vrm1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("\nExternal Render Test"); + ViEAutoTestExternalRenderer externalRenderObj; + error = ViE.ptrViERender->AddRenderer(tbCapture.captureId, + webrtc::kVideoI420, + &externalRenderObj); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->StartRender(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + AutoTestSleep(KAutoTestSleepTimeMs); + + error = ViE.ptrViERender->StopRender(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERender->RemoveRenderer(tbCapture.captureId); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERender->DeRegisterVideoRenderModule(*_vrm1); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Destroy render module for external rendering and create new in normal + // mode + VideoRender::DestroyVideoRender(_vrm1); + _vrm1 = NULL; + _vrm1 = VideoRender::CreateVideoRender(4561, _window1, false, _renderType); + numberOfErrors += ViETest::TestError(_vrm1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + tbCapture.Disconnect(tbChannel.videoChannel); + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViERender Extended Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViERender Extended Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + + return 0; +} + +int ViEAutoTest::ViERenderAPITest() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViERender API Test\n"); + + int error = 0; + int numberOfErrors = 0; + + //TODO add the real tests cases + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViERender API Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViERender API Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + + return 0; +} diff --git a/video_engine/main/test/AutoTest/source/vie_autotest_rtp_rtcp.cc b/video_engine/main/test/AutoTest/source/vie_autotest_rtp_rtcp.cc new file mode 100644 index 0000000000..d76ff51c7c --- /dev/null +++ b/video_engine/main/test/AutoTest/source/vie_autotest_rtp_rtcp.cc @@ -0,0 +1,966 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// +// vie_autotest_rtp_rtcp.cc +// +#include + +#include "vie_autotest_defines.h" +#include "vie_autotest.h" +#include "engine_configurations.h" + +#include "tb_capture_device.h" +#include "tb_external_transport.h" +#include "tb_interfaces.h" +#include "tb_video_channel.h" + +class ViERtpObserver: public ViERTPObserver +{ +public: + ViERtpObserver() + { + } + virtual ~ViERtpObserver() + { + } + + virtual void IncomingSSRCChanged(const int videoChannel, + const unsigned int SSRC) + { + } + virtual void IncomingCSRCChanged(const int videoChannel, + const unsigned int CSRC, const bool added) + { + } +}; + +class ViERtcpObserver: public ViERTCPObserver +{ +public: + int _channel; + unsigned char _subType; + unsigned int _name; + char* _data; + unsigned short _dataLength; + + ViERtcpObserver() : + _channel(-1), + _subType(0), + _name(-1), + _data(NULL), + _dataLength(0) + { + } + ~ViERtcpObserver() + { + if (_data) + { + delete[] _data; + } + } + virtual void OnApplicationDataReceived( + const int videoChannel, const unsigned char subType, + const unsigned int name, const char* data, + const unsigned short dataLengthInBytes) + { + _channel = videoChannel; + _subType = subType; + _name = name; + if (dataLengthInBytes > _dataLength) + { + delete[] _data; + _data = NULL; + } + if (_data == NULL) + { + _data = new char[dataLengthInBytes]; + } + memcpy(_data, data, dataLengthInBytes); + _dataLength = dataLengthInBytes; + } +}; + +int ViEAutoTest::ViERtpRtcpStandardTest() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViERTP_RTCP Standard Test\n"); + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + + int rtpPort = 6000; + // Create VIE + tbInterfaces ViE("ViERtpRtcpStandardTest", numberOfErrors); + // Create a video channel + tbVideoChannel tbChannel(ViE, numberOfErrors, webrtc::kVideoCodecVP8); + + // Create a capture device + tbCaptureDevice tbCapture(ViE, numberOfErrors); + tbCapture.ConnectTo(tbChannel.videoChannel); + + ViETest::Log("\n"); + tbExternalTransport myTransport(*(ViE.ptrViENetwork)); + + error = ViE.ptrViENetwork->RegisterSendTransport(tbChannel.videoChannel, + myTransport); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + //*************************************************************** + // Engine ready. Begin testing class + //*************************************************************** + + + + + unsigned short startSequenceNumber = 12345; + ViETest::Log("Set start sequence number: %u\n"); + error = ViE.ptrViERtpRtcp->SetStartSequenceNumber(tbChannel.videoChannel, + startSequenceNumber); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + myTransport.EnableSequenceNumberCheck(); + + error = ViE.ptrViEBase->StartReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEBase->StartSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + AutoTestSleep(1000); + + unsigned short receivedSequenceNumber = + myTransport.GetFirstSequenceNumber(); + ViETest::Log("First received sequence number: %u\n\n"); + numberOfErrors += ViETest::TestError( + receivedSequenceNumber == startSequenceNumber, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEBase->StopSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // + // RTCP CName + // + ViETest::Log("Testing CName\n\n"); + char* sendCName = "ViEAutoTestCName\0"; + error = ViE.ptrViERtpRtcp->SetRTCPCName(tbChannel.videoChannel, sendCName); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + char returnCName[ViERTP_RTCP::KMaxRTCPCNameLength]; + memset(returnCName, 0, ViERTP_RTCP::KMaxRTCPCNameLength); + error + = ViE.ptrViERtpRtcp->GetRTCPCName(tbChannel.videoChannel, returnCName); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError((strcmp(sendCName, returnCName) == 0), + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + error = ViE.ptrViEBase->StartSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + AutoTestSleep(1000); + + char remoteCName[ViERTP_RTCP::KMaxRTCPCNameLength]; + memset(remoteCName, 0, ViERTP_RTCP::KMaxRTCPCNameLength); + error = ViE.ptrViERtpRtcp->GetRemoteRTCPCName(tbChannel.videoChannel, + remoteCName); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError((strcmp(sendCName, remoteCName) == 0), + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + // + // Statistics + // + // Stop and restart to clear stats + ViETest::Log("Testing statistics\n\n"); + error = ViE.ptrViEBase->StopReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEBase->StopSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + myTransport.ClearStats(); + std::cout << "Enter Packet Loss Percentage" << std::endl; + std::string rate_str; + std::getline(std::cin, rate_str); + int rate = atoi(rate_str.c_str()); + myTransport.SetPacketLoss(rate); + + // Start send to verify sending stats + + error = ViE.ptrViERtpRtcp->SetStartSequenceNumber(tbChannel.videoChannel, + startSequenceNumber); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEBase->StartSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEBase->StartReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + AutoTestSleep(KAutoTestSleepTimeMs); + + unsigned short sentFractionsLost = 0; + unsigned int sentCumulativeLost = 0; + unsigned int sentExtendedMax = 0; + unsigned int sentJitter = 0; + int sentRttMs = 0; + unsigned short recFractionsLost = 0; + unsigned int recCumulativeLost = 0; + unsigned int recExtendedMax = 0; + unsigned int recJitter = 0; + int recRttMs = 0; + + error = ViE.ptrViEBase->StopReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + AutoTestSleep(2000); + + error = ViE.ptrViERtpRtcp->GetSentRTCPStatistics(tbChannel.videoChannel, + sentFractionsLost, + sentCumulativeLost, + sentExtendedMax, + sentJitter, sentRttMs); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError((sentCumulativeLost > 0 + && sentExtendedMax > startSequenceNumber && sentJitter > 0 && sentRttMs + > 0), "ERROR: %s at line %d", __FUNCTION__, __LINE__); + + error = ViE.ptrViERtpRtcp->GetReceivedRTCPStatistics(tbChannel.videoChannel, + recFractionsLost, + recCumulativeLost, + recExtendedMax, + recJitter, recRttMs); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError( + (recCumulativeLost > 0 + && recExtendedMax > startSequenceNumber + && recJitter > 0 + && recRttMs > 0), + "ERROR: %s at line %d", __FUNCTION__, __LINE__); + // Check that rec stats extended max is greater than what we've sent. + numberOfErrors += ViETest::TestError(recExtendedMax >= sentExtendedMax, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + // + // Keepalive + // + ViETest::Log("Testing RTP keep alive...\n\n"); + error = ViE.ptrViEBase->StopSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEBase->StartReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + myTransport.SetPacketLoss(0); + myTransport.ClearStats(); + + const char keepAlivePT = 109; + unsigned int deltaTimeSeconds = 2; + error = ViE.ptrViERtpRtcp->SetRTPKeepAliveStatus(tbChannel.videoChannel, + true, keepAlivePT, + deltaTimeSeconds); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + AutoTestSleep(KAutoTestSleepTimeMs); + + error = ViE.ptrViERtpRtcp->SetRTPKeepAliveStatus(tbChannel.videoChannel, + false, keepAlivePT, + deltaTimeSeconds); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + WebRtc_Word32 numRtpPackets = 0; + WebRtc_Word32 numDroppedPackets = 0; + WebRtc_Word32 numRtcpPackets = 0; + myTransport.GetStats(numRtpPackets, numDroppedPackets, numRtcpPackets); + numberOfErrors += ViETest::TestError(numRtpPackets == KAutoTestSleepTimeMs + / (1000 * deltaTimeSeconds), "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + // Test to set SSRC + unsigned int setSSRC = 0x01234567; + ViETest::Log("Set SSRC %u\n"); + error = ViE.ptrViERtpRtcp->SetLocalSSRC(tbChannel.videoChannel, setSSRC); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEBase->StartReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEBase->StartSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + myTransport.EnableSSRCCheck(); + + AutoTestSleep(2000); + unsigned int receivedSSRC = myTransport.ReceivedSSRC(); + ViETest::Log("Received SSRC %u\n\n"); + numberOfErrors += ViETest::TestError(setSSRC == receivedSSRC, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + unsigned int localSSRC = 0; + error = ViE.ptrViERtpRtcp->GetLocalSSRC(tbChannel.videoChannel, localSSRC); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(localSSRC == setSSRC, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + unsigned int remoteSSRC = 0; + error + = ViE.ptrViERtpRtcp->GetRemoteSSRC(tbChannel.videoChannel, remoteSSRC); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(remoteSSRC == setSSRC, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + error = ViE.ptrViEBase->StopSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + + ViETest::Log("Testing RTP dump...\n\n"); + +#ifdef ANDROID + char* inDumpName = "/sdcard/IncomingRTPDump.rtp"; + char* outDumpName = "/sdcard/OutgoingRTPDump.rtp"; +#else + char* inDumpName = "IncomingRTPDump.rtp"; + char* outDumpName = "OutgoingRTPDump.rtp"; +#endif + + error = ViE.ptrViERtpRtcp->StartRTPDump(tbChannel.videoChannel, inDumpName, + kRtpIncoming); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->StartRTPDump(tbChannel.videoChannel, + outDumpName, kRtpOutgoing); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEBase->StartSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + AutoTestSleep(KAutoTestSleepTimeMs); + + error = ViE.ptrViEBase->StopSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + AutoTestSleep(1000); + + error + = ViE.ptrViERtpRtcp->StopRTPDump(tbChannel.videoChannel, kRtpIncoming); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error + = ViE.ptrViERtpRtcp->StopRTPDump(tbChannel.videoChannel, kRtpOutgoing); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // Make sure data was actuall saved to the file and we stored the same + // amount of data in both files + FILE* inDump = fopen(inDumpName, "r"); + fseek(inDump, 0L, SEEK_END); + long inEndPos = ftell(inDump); + fclose(inDump); + FILE* outDump = fopen(outDumpName, "r"); + fseek(outDump, 0L, SEEK_END); + long outEndPos = ftell(outDump); + fclose(outDump); + + numberOfErrors += ViETest::TestError((inEndPos > 0 + && inEndPos < outEndPos + 100), + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + // Deregister external transport + error = ViE.ptrViENetwork->DeregisterSendTransport(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViERTP_RTCP Standard Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViERTP_RTCP Standard Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + return 0; +} + +int ViEAutoTest::ViERtpRtcpExtendedTest() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViERTP_RTCP Extended Test\n"); + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + + numberOfErrors = ViERtpRtcpStandardTest(); + + int rtpPort = 6000; + // Create VIE + tbInterfaces ViE("ViERtpRtcpStandardTest", numberOfErrors); + // Create a video channel + tbVideoChannel tbChannel(ViE, numberOfErrors, webrtc::kVideoCodecVP8); + // Create a capture device + tbCaptureDevice tbCapture(ViE, numberOfErrors); + tbCapture.ConnectTo(tbChannel.videoChannel); + + //tbChannel.StartReceive(rtpPort); + //tbChannel.StartSend(rtpPort); + tbExternalTransport myTransport(*(ViE.ptrViENetwork)); + + error = ViE.ptrViENetwork->RegisterSendTransport(tbChannel.videoChannel, + myTransport); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEBase->StartReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEBase->StartSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + //*************************************************************** + // Engine ready. Begin testing class + //*************************************************************** + + + // + // Application specific RTCP + // + // + + ViERtcpObserver rtcpObserver; + error = ViE.ptrViERtpRtcp->RegisterRTCPObserver(tbChannel.videoChannel, + rtcpObserver); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + unsigned char subType = 3; + unsigned int name = static_cast (0x41424344); // 'ABCD'; + const char* data = "ViEAutoTest Data of length 32 --"; + const unsigned short numBytes = 32; + + error = ViE.ptrViERtpRtcp->SendApplicationDefinedRTCPPacket( + tbChannel.videoChannel, subType, name, data, numBytes); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViETest::Log("Sending RTCP application data...\n"); + AutoTestSleep(KAutoTestSleepTimeMs); + numberOfErrors += ViETest::TestError( + (subType = rtcpObserver._subType + && !strncmp(data, rtcpObserver._data, 32) + && name == rtcpObserver._name + && numBytes == rtcpObserver._dataLength), + "ERROR: %s at line %d", __FUNCTION__, __LINE__); + ViETest::Log("\t RTCP application data received\n"); + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + + error = ViE.ptrViEBase->StopReceive(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViEBase->StopSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViENetwork->DeregisterSendTransport(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViERTP_RTCP Extended Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViERTP_RTCP Extended Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + return 0; +} + +int ViEAutoTest::ViERtpRtcpAPITest() +{ + ViETest::Log(" "); + ViETest::Log("========================================"); + ViETest::Log(" ViERTP_RTCP API Test\n"); + + //*************************************************************** + // Begin create/initialize WebRTC Video Engine for testing + //*************************************************************** + + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + + int rtpPort = 6000; + // Create VIE + tbInterfaces ViE("ViERtpRtcpAPITest", numberOfErrors); + // Create a video channel + tbVideoChannel tbChannel(ViE, numberOfErrors, webrtc::kVideoCodecVP8); + // Create a capture device + tbCaptureDevice tbCapture(ViE, numberOfErrors); + tbCapture.ConnectTo(tbChannel.videoChannel); + + //*************************************************************** + // Engine ready. Begin testing class + //*************************************************************** + + + // + // Check different RTCP modes + // + ViERTCPMode rtcpMode = kRtcpNone; + error = ViE.ptrViERtpRtcp->GetRTCPStatus(tbChannel.videoChannel, rtcpMode); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(rtcpMode == kRtcpCompound_RFC4585, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + error = ViE.ptrViERtpRtcp->SetRTCPStatus(tbChannel.videoChannel, + kRtcpCompound_RFC4585); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->GetRTCPStatus(tbChannel.videoChannel, rtcpMode); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(rtcpMode == kRtcpCompound_RFC4585, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + error = ViE.ptrViERtpRtcp->SetRTCPStatus(tbChannel.videoChannel, + kRtcpNonCompound_RFC5506); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->GetRTCPStatus(tbChannel.videoChannel, rtcpMode); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(rtcpMode == kRtcpNonCompound_RFC5506, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + error = ViE.ptrViERtpRtcp->SetRTCPStatus(tbChannel.videoChannel, kRtcpNone); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->GetRTCPStatus(tbChannel.videoChannel, rtcpMode); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError(rtcpMode == kRtcpNone, + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + error = ViE.ptrViERtpRtcp->SetRTCPStatus(tbChannel.videoChannel, + kRtcpCompound_RFC4585); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + // + // CName is testedn in SimpleTest + // Start sequence number is tested in SimplTEst + // + char* testCName = "ViEAutotestCName"; + error = ViE.ptrViERtpRtcp->SetRTCPCName(tbChannel.videoChannel, testCName); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + char returnCName[256]; + memset(returnCName, 0, 256); + error + = ViE.ptrViERtpRtcp->GetRTCPCName(tbChannel.videoChannel, returnCName); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError((strcmp(testCName, returnCName) == 0), + "ERROR: %s at line %d", __FUNCTION__, + __LINE__); + + // + // SSRC + // + error = ViE.ptrViERtpRtcp->SetLocalSSRC(tbChannel.videoChannel, 0x01234567); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->SetLocalSSRC(tbChannel.videoChannel, 0x76543210); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + unsigned int ssrc = 0; + error = ViE.ptrViERtpRtcp->GetLocalSSRC(tbChannel.videoChannel, ssrc); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERtpRtcp->SetStartSequenceNumber(tbChannel.videoChannel, + 1000); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + tbChannel.StartSend(); + error = ViE.ptrViERtpRtcp->SetStartSequenceNumber(tbChannel.videoChannel, + 12345); + numberOfErrors += ViETest::TestError(error == -1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + tbChannel.StopSend(); + + // + // Start sequence number + // + error = ViE.ptrViERtpRtcp->SetStartSequenceNumber(tbChannel.videoChannel, + 12345); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->SetStartSequenceNumber(tbChannel.videoChannel, + 1000); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + tbChannel.StartSend(); + error = ViE.ptrViERtpRtcp->SetStartSequenceNumber(tbChannel.videoChannel, + 12345); + numberOfErrors += ViETest::TestError(error == -1, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + tbChannel.StopSend(); + + // + // Application specific RTCP + // + { + unsigned char subType = 3; + unsigned int name = static_cast (0x41424344); // 'ABCD'; + const char* data = "ViEAutoTest Data of length 32 --"; + const unsigned short numBytes = 32; + + tbChannel.StartSend(); + error = ViE.ptrViERtpRtcp->SendApplicationDefinedRTCPPacket( + tbChannel.videoChannel, subType, name, data, numBytes); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->SendApplicationDefinedRTCPPacket( + tbChannel.videoChannel, subType, name, NULL, numBytes); + // NULL input + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->SendApplicationDefinedRTCPPacket( + tbChannel.videoChannel, subType, name, data, numBytes - 1); + // incorrect length + numberOfErrors += ViETest::TestError(error != 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->GetRTCPStatus(tbChannel.videoChannel, + rtcpMode); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->SendApplicationDefinedRTCPPacket( + tbChannel.videoChannel, subType, name, data, numBytes); + // RTCP off + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->SetRTCPStatus(tbChannel.videoChannel, + kRtcpCompound_RFC4585); + numberOfErrors += ViETest::TestError(error == 0, "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + tbChannel.StopSend(); + error = ViE.ptrViERtpRtcp->SendApplicationDefinedRTCPPacket( + tbChannel.videoChannel, subType, name, data, numBytes); + // Not sending + numberOfErrors += ViETest::TestError(error != 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } + + // + // Statistics + // + // Tested in SimpleTest(), we'll get errors if we haven't received a RTCP + // packet. + + // + // RTP Keepalive + // + { + char setPT = 123; + unsigned int setDeltaTime = 10; + bool enabled = false; + char getPT = 0; + unsigned int getDeltaTime = 0; + error = ViE.ptrViERtpRtcp->SetRTPKeepAliveStatus(tbChannel.videoChannel, + true, 119); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->SetRTPKeepAliveStatus(tbChannel.videoChannel, + true, setPT, + setDeltaTime); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->SetRTPKeepAliveStatus(tbChannel.videoChannel, + false, setPT, + setDeltaTime); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->SetRTPKeepAliveStatus(tbChannel.videoChannel, + true, setPT, + setDeltaTime); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERtpRtcp->GetRTPKeepAliveStatus(tbChannel.videoChannel, + enabled, getPT, + getDeltaTime); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + numberOfErrors += ViETest::TestError((enabled == true, + setPT == getPT, + setDeltaTime == getDeltaTime), + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViEBase->StartSend(tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + error = ViE.ptrViERtpRtcp->SetRTPKeepAliveStatus(tbChannel.videoChannel, + true, setPT, + setDeltaTime); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + tbChannel.StopSend(); + error = ViE.ptrViERtpRtcp->SetRTPKeepAliveStatus(tbChannel.videoChannel, + enabled, getPT, 0); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->SetRTPKeepAliveStatus(tbChannel.videoChannel, + enabled, getPT, 61); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } + // + // RTP Dump + // + { +#ifdef ANDROID + char* dumpName = "/sdcard/DumpFileName.rtp"; +#else + char* dumpName = "DumpFileName.rtp"; +#endif + error = ViE.ptrViERtpRtcp->StartRTPDump(tbChannel.videoChannel, + dumpName, kRtpIncoming); + + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->StopRTPDump(tbChannel.videoChannel, + kRtpIncoming); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->StopRTPDump(tbChannel.videoChannel, + kRtpIncoming); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->StartRTPDump(tbChannel.videoChannel, + dumpName, kRtpOutgoing); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->StopRTPDump(tbChannel.videoChannel, + kRtpOutgoing); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->StopRTPDump(tbChannel.videoChannel, + kRtpOutgoing); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->StartRTPDump(tbChannel.videoChannel, + dumpName, (RTPDirections) 3); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } + // + // RTP/RTCP Observers + // + { + ViERtpObserver rtpObserver; + error = ViE.ptrViERtpRtcp->RegisterRTPObserver(tbChannel.videoChannel, + rtpObserver); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->RegisterRTPObserver(tbChannel.videoChannel, + rtpObserver); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->DeregisterRTPObserver( + tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->DeregisterRTPObserver( + tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + + ViERtcpObserver rtcpObserver; + error = ViE.ptrViERtpRtcp->RegisterRTCPObserver(tbChannel.videoChannel, + rtcpObserver); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->RegisterRTCPObserver(tbChannel.videoChannel, + rtcpObserver); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->DeregisterRTCPObserver( + tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->DeregisterRTCPObserver( + tbChannel.videoChannel); + numberOfErrors += ViETest::TestError(error == -1, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } + // + // PLI + // + { + error = ViE.ptrViERtpRtcp->SetKeyFrameRequestMethod( + tbChannel.videoChannel, kViEKeyFrameRequestPliRtcp); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->SetKeyFrameRequestMethod( + tbChannel.videoChannel, kViEKeyFrameRequestPliRtcp); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->SetKeyFrameRequestMethod( + tbChannel.videoChannel, kViEKeyFrameRequestNone); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + error = ViE.ptrViERtpRtcp->SetKeyFrameRequestMethod( + tbChannel.videoChannel, kViEKeyFrameRequestNone); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } + // + // NACK + // + { + error = ViE.ptrViERtpRtcp->SetNACKStatus(tbChannel.videoChannel, true); + numberOfErrors += ViETest::TestError(error == 0, + "ERROR: %s at line %d", + __FUNCTION__, __LINE__); + } + + //*************************************************************** + // Testing finished. Tear down Video Engine + //*************************************************************** + + if (numberOfErrors > 0) + { + // Test failed + ViETest::Log(" "); + ViETest::Log(" ERROR ViERTP_RTCP API Test FAILED!"); + ViETest::Log(" Number of errors: %d", numberOfErrors); + ViETest::Log("========================================"); + ViETest::Log(" "); + return numberOfErrors; + } + + ViETest::Log(" "); + ViETest::Log(" ViERTP_RTCP API Test PASSED!"); + ViETest::Log("========================================"); + ViETest::Log(" "); + return 0; +} diff --git a/video_engine/main/test/AutoTest/source/vie_autotest_windows.cc b/video_engine/main/test/AutoTest/source/vie_autotest_windows.cc new file mode 100644 index 0000000000..1d264d11ff --- /dev/null +++ b/video_engine/main/test/AutoTest/source/vie_autotest_windows.cc @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// +// vie_autotest_windows.cc +// + +#include "vie_autotest_windows.h" + +#include "vie_autotest_defines.h" +#include "vie_autotest_main.h" + +#include "engine_configurations.h" +#include "critical_section_wrapper.h" +#include "thread_wrapper.h" + +#include + +#ifdef _DEBUG +//#include "vld.h" +#endif + +// Disable Visual studio warnings +// 'this' : used in base member initializer list +#pragma warning(disable: 4355) +// new behavior: elements of array 'XXX' will be default initialized +#pragma warning(disable: 4351) + +LRESULT CALLBACK ViEAutoTestWinProc(HWND hWnd, UINT uMsg, WPARAM wParam, + LPARAM lParam) +{ + switch (uMsg) + { + case WM_DESTROY: + PostQuitMessage( WM_QUIT); + break; + case WM_COMMAND: + break; + } + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} + +ViEAutoTestWindowManager::ViEAutoTestWindowManager() : + _window1(NULL), + _window2(NULL), + _terminate(false), + _eventThread(*webrtc::ThreadWrapper::CreateThread(EventProcess, this, + webrtc::kNormalPriority, + "ViEAutotestEventThread")), + _crit(*webrtc::CriticalSectionWrapper::CreateCriticalSection()), + _hwnd1(NULL), + _hwnd2(NULL), + _hwnd1Size(), + _hwnd2Size(), + _hwnd1Title(), + _hwnd2Title() +{ +} + +ViEAutoTestWindowManager::~ViEAutoTestWindowManager() +{ + if (_hwnd1) + { + ViEDestroyWindow(_hwnd1); + } + if (_hwnd2) + { + ViEDestroyWindow(_hwnd1); + } + delete &_crit; +} + +void* ViEAutoTestWindowManager::GetWindow1() +{ + return _window1; +} + +void* ViEAutoTestWindowManager::GetWindow2() +{ + return _window2; +} + +int ViEAutoTestWindowManager::CreateWindows(AutoTestRect window1Size, + AutoTestRect window2Size, + void* window1Title, + void* window2Title) +{ + _hwnd1Size.Copy(window1Size); + _hwnd2Size.Copy(window2Size); + memcpy(_hwnd1Title, window1Title, TITLE_LENGTH); + memcpy(_hwnd2Title, window2Title, TITLE_LENGTH); + + unsigned int tId = 0; + _eventThread.Start(tId); + + do + { + _crit.Enter(); + if (_window1 != NULL) + break; + _crit.Leave(); + AutoTestSleep(10); + } while (true); + _crit.Leave(); + return 0; +} + +int ViEAutoTestWindowManager::TerminateWindows() +{ + _eventThread.SetNotAlive(); + + _terminate = true; + if (_eventThread.Stop()) + { + _crit.Enter(); + delete &_eventThread; + _crit.Leave(); + } + + return 0; +} + +bool ViEAutoTestWindowManager::EventProcess(void* obj) +{ + return static_cast (obj)->EventLoop(); +} + +bool ViEAutoTestWindowManager::EventLoop() +{ + _crit.Enter(); + + //ViECreateWindow(_hwnd1, 352, 288, 600, 100, "ViE Autotest Window 1"); + //ViECreateWindow(_hwnd2, 352, 288, 1000, 100, "ViE Autotest Window 2"); + + ViECreateWindow(_hwnd1, _hwnd1Size.origin.x, _hwnd1Size.origin.y, + _hwnd1Size.size.width, _hwnd1Size.size.height, _hwnd1Title); + ViECreateWindow(_hwnd2, _hwnd2Size.origin.x, _hwnd2Size.origin.y, + _hwnd2Size.size.width, _hwnd2Size.size.height, _hwnd2Title); + + _window1 = (void*) _hwnd1; + _window2 = (void*) _hwnd2; + MSG msg; + while (!_terminate) + { + if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + _crit.Leave(); + AutoTestSleep(10); + _crit.Enter(); + } + ViEDestroyWindow(_hwnd1); + ViEDestroyWindow(_hwnd2); + _crit.Leave(); + + return false; +} + +int ViEAutoTestWindowManager::ViECreateWindow(HWND &hwndMain, int xPos, + int yPos, int width, int height, + TCHAR* className) +{ + HINSTANCE hinst = GetModuleHandle(0); + WNDCLASSEX wcx; + wcx.hInstance = hinst; + wcx.lpszClassName = className; + wcx.lpfnWndProc = (WNDPROC) ViEAutoTestWinProc; + wcx.style = CS_DBLCLKS; + wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wcx.hIconSm = LoadIcon(NULL, IDI_APPLICATION); + wcx.hCursor = LoadCursor(NULL, IDC_ARROW); + wcx.lpszMenuName = NULL; + wcx.cbSize = sizeof(WNDCLASSEX); + wcx.cbClsExtra = 0; + wcx.cbWndExtra = 0; + wcx.hbrBackground = GetSysColorBrush(COLOR_3DFACE); + + // Register our window class with the operating system. + // If there is an error, exit program. + ViETest::TestError(RegisterClassEx(&wcx) != NULL, + "Could not register autotest window"); + + // Create the main window. + hwndMain = CreateWindowEx(0, // no extended styles + className, // class name + className, // window name + WS_OVERLAPPED | WS_THICKFRAME,// overlapped window + xPos, // horizontal position + yPos, // vertical position + width, // width + height, // height + (HWND) NULL, // no parent or owner window + (HMENU) NULL, // class menu used + hinst, // instance handle + NULL); // no window creation data + + if (!hwndMain) + { + int error = GetLastError(); + return -1; + } + + // Show the window using the flag specified by the program + // that started the application, and send the application + // a WM_PAINT message. + + ShowWindow(hwndMain, SW_SHOWDEFAULT); + UpdateWindow(hwndMain); + + ::SetWindowPos(hwndMain, HWND_TOP, xPos, yPos, width, height, + SWP_FRAMECHANGED); + + return 0; +} + +int ViEAutoTestWindowManager::ViEDestroyWindow(HWND& hwnd) +{ + ::DestroyWindow(hwnd); + return 0; +} + +bool ViEAutoTestWindowManager::SetTopmostWindow() +{ + + // meant to put terminal window on top + + return true; +} + +int main(int argc, char* argv[]) +{ + + ViEAutoTestMain autoTest; + if (argc > 1) + { + autoTest.UseAnswerFile(argv[1]); + } + return autoTest.BeginOSIndependentTesting(); + +} diff --git a/video_engine/main/test/AutoTest/vie_auto_test.gypi b/video_engine/main/test/AutoTest/vie_auto_test.gypi new file mode 100644 index 0000000000..c3a1749595 --- /dev/null +++ b/video_engine/main/test/AutoTest/vie_auto_test.gypi @@ -0,0 +1,183 @@ +# Copyright (c) 2009 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'includes': [ + '../../../../common_settings.gypi', # Common settings + ], + 'targets': [ + { + 'target_name': 'vie_auto_test', + 'type': 'executable', + 'dependencies': [ + 'system_wrappers/source/system_wrappers.gyp:system_wrappers', + 'modules/video_render/main/source/video_render.gyp:video_render_module', + 'modules/video_capture/main/source/video_capture.gyp:video_capture_module', + 'voice_engine/main/source/voice_engine_core.gyp:voice_engine_core', + 'video_engine/main/source/video_engine_core.gyp:video_engine_core', + ], + 'include_dirs': [ + 'interface/', + '../../interface', + '../../source', + '../../../../modules/video_coding/codecs/interface/', + ], + 'sources': [ + # interfaces + 'interface/tb_capture_device.h', + 'interface/tb_external_transport.h', + 'interface/tb_I420_codec.h', + 'interface/tb_interfaces.h', + 'interface/tb_video_channel.h', + 'interface/vie_autotest.h', + 'interface/vie_autotest_defines.h', + 'interface/vie_autotest_linux.h', + 'interface/vie_autotest_mac_carbon.h', + 'interface/vie_autotest_mac_cocoa.h', + 'interface/vie_autotest_main.h', + 'interface/vie_autotest_window_manager_interface.h', + 'interface/vie_autotest_windows.h', + + # PLATFORM INDEPENDENT SOURCE FILES + 'source/tb_capture_device.cc', + 'source/tb_external_transport.cc', + 'source/tb_I420_codec.cc', + 'source/tb_interfaces.cc', + 'source/tb_video_channel.cc', + 'source/vie_autotest.cc', + 'source/vie_autotest_base.cc', + 'source/vie_autotest_capture.cc', + 'source/vie_autotest_codec.cc', + 'source/vie_autotest_encryption.cc', + 'source/vie_autotest_file.cc', + 'source/vie_autotest_image_process.cc', + 'source/vie_autotest_loopback.cc', + 'source/vie_autotest_main.cc', + 'source/vie_autotest_network.cc', + 'source/vie_autotest_render.cc', + 'source/vie_autotest_rtp_rtcp.cc', + 'source/vie_autotest_custom_call.cc', + # PLATFORM SPECIFIC SOURCE FILES - Will be filtered below + # Linux + 'source/vie_autotest_linux.cc', + # Mac + 'source/vie_autotest_mac_cocoa.cc', + 'source/vie_autotest_mac_carbon.cc', + # Windows + 'source/vie_autotest_windows.cc', + ], # sources + 'conditions': [ + # DEFINE PLATFORM SPECIFIC SOURCE FILES + ['OS!="linux"', { + 'sources!': [ + 'source/vie_autotest_linux.cc', + ], + }], + ['OS!="mac"', { + 'sources!': [ + 'source/vie_autotest_mac_cocoa.cc', + 'source/vie_autotest_mac_carbon.cc', + ], + }], + ['OS!="win"', { + 'sources!': [ + 'source/vie_autotest_windows.cc', + ], + }], + ['OS=="win"', { + 'dependencies': [ + 'video_engine/main/test/WindowsTest/windowstest.gyp:vie_win_test', + ], + }], + + # DEFINE PLATFORM SPECIFIC INCLUDE AND CFLAGS + ['OS=="mac" or OS=="linux"', { + 'cflags': [ + '-Wno-write-strings', + ], + 'ldflags': [ + '-lpthread -lm', + ], + }], + ['OS=="linux"', { + 'ldflags': [ + # '-L<(libvpx_hack_dir)/<(OS)/<(target_arch)', + ], + 'libraries': [ + '-lrt', + '-lXext', + '-lX11', + '-lasound', + '-lpulse', + + + ], + }], + ['OS=="mac"', { + 'xcode_settings': { + 'OTHER_CPLUSPLUSFLAGS': '-x objective-c++', + 'OTHER_LDFLAGS': [ + '-framework Foundation -framework AppKit -framework Cocoa -framework OpenGL -framework CoreVideo -framework CoreAudio -framework AudioToolbox', + ], + }, + }], + #Copy media files + ['OS=="linux" or OS=="mac"', { + 'actions': [ + { + 'action_name': 'copy media files', + 'inputs': [ + 'media', + ], + 'outputs': [ + 'media_files_in_tmp', + ], + 'action': [ + '/bin/sh', '-c', + 'mkdir -p /tmp/vie_auto_test; cp -f video_engine/main/test/AutoTest/media/* /tmp/vie_auto_test' + ], + }, + ], + }], + ['OS=="win"', { + 'actions': [ + { + 'action_name': 'create vie test folder', + 'inputs': [ + 'media', + ], + 'outputs': [ + 'tmp/vie_auto_test/', + ], + 'action': [ + 'cmd', '/c', + 'mdkdir \\tmp\\vie_auto_test' + ], + }, + { + 'action_name': 'copy media files', + 'inputs': [ + 'media', + ], + 'outputs': [ + '\\tmp\\vie_auto_test\\*.jpg', + '\\tmp\\vie_auto_test\\*.yuv', + ], + 'action': [ + 'cmd', '/c', + 'xcopy /Y /R video_engine\\main\\test\\AutoTest\\media\\* \\tmp\\vie_auto_test' + ], + }, + ], + }], + ], #conditions + }, + ], +} + +# Local Variables: +# tab-width:2 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=2 shiftwidth=2: diff --git a/video_engine/main/test/SimpleCocoaGUI/GUI_Defines.h b/video_engine/main/test/SimpleCocoaGUI/GUI_Defines.h new file mode 100644 index 0000000000..83828441b9 --- /dev/null +++ b/video_engine/main/test/SimpleCocoaGUI/GUI_Defines.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * GUI_Defines.h + * + */ + + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_TEST_SIMPLECOCOAGUI_GUI_DEFINES_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_TEST_SIMPLECOCOAGUI_GUI_DEFINES_H_ + +#define ViE_TEST(x) if(-1 == x){ \ +int errNum = _ptrViEBase->LastError(); \ +NSLog(@"ERROR: %d at %s:%d", errNum, __FUNCTION__, __LINE__); \ +} + + +// Video Engine Related +#define V_CAPTURE_DEVICE_INDEX 0 +#define V_VIE_CAPTURE_ID 747 +#define V_DEVICE_NAME_LENGTH 256 +#define V_CODEC_INDEX 2 +#define V_IP_ADDRESS "127.0.0.1" +#define V_RTP_PORT 8000 + + + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_TEST_SIMPLECOCOAGUI_GUI_DEFINES_H_ diff --git a/video_engine/main/test/SimpleCocoaGUI/SimpleCocoaGUI-Info.plist b/video_engine/main/test/SimpleCocoaGUI/SimpleCocoaGUI-Info.plist new file mode 100644 index 0000000000..d0d3a18fe4 --- /dev/null +++ b/video_engine/main/test/SimpleCocoaGUI/SimpleCocoaGUI-Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + + CFBundleIdentifier + com.yourcompany.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NSMainNibFile + SimpleCocoaGUI + NSPrincipalClass + NSApplication + + diff --git a/video_engine/main/test/SimpleCocoaGUI/SimpleCocoaGUIAppDelegate.h b/video_engine/main/test/SimpleCocoaGUI/SimpleCocoaGUIAppDelegate.h new file mode 100644 index 0000000000..10d52fc5ec --- /dev/null +++ b/video_engine/main/test/SimpleCocoaGUI/SimpleCocoaGUIAppDelegate.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// +// SimpleCocoaGUIAppDelegate.h +// + +#import +#include +using namespace std; + +@class ViECocoaRenderView; + +#include "GUI_Defines.h" + +#include "common_types.h" +#include "voe_base.h" + +#include "vie_base.h" +#include "vie_capture.h" +#include "vie_codec.h" +#include "vie_file.h" +#include "vie_network.h" +#include "vie_render.h" +#include "vie_rtp_rtcp.h" +#include "vie_errors.h" + + + +@interface SimpleCocoaGUIAppDelegate : NSObject { + NSWindow* _window; + IBOutlet NSOpenGLView* _vieCocoaRenderView1; + IBOutlet NSOpenGLView* _vieCocoaRenderView2; + IBOutlet NSButton* _butRestartLoopback; + VideoEngine* _ptrViE; + ViEBase* _ptrViEBase; + ViECapture* _ptrViECapture; + ViERender* _ptrViERender; + ViECodec* _ptrViECodec; + ViENetwork* _ptrViENetwork; + + bool _fullScreen; + int _videoChannel; + + int _captureId; + + VideoEngine* ptrViE; + ViEBase* ptrViEBase; + ViECapture* ptrViECapture; + ViERTP_RTCP* ptrViERtpRtcp; + ViERender* ptrViERender; + ViECodec* ptrViECodec; + ViENetwork* ptrViENetwork; +} + +@property (assign) IBOutlet NSWindow* window; +-(void)createUI:(bool)fullScreen; +-(void)initViECocoaTest; +-(void)initializeVariables; +-(void)NSLogVideoCodecs; +-(void)startViECocoaTest; +-(int)initLoopback; +-(int)ioLooback; +-(int)startLoopback; +-(int)stopLooback; + +-(IBAction)handleRestart:(id)sender; + + +@end diff --git a/video_engine/main/test/SimpleCocoaGUI/SimpleCocoaGUIAppDelegate.mm b/video_engine/main/test/SimpleCocoaGUI/SimpleCocoaGUIAppDelegate.mm new file mode 100644 index 0000000000..d594cfe1f3 --- /dev/null +++ b/video_engine/main/test/SimpleCocoaGUI/SimpleCocoaGUIAppDelegate.mm @@ -0,0 +1,1075 @@ +// +// SimpleCocoaGUIAppDelegate.m +// + +#import "SimpleCocoaGUIAppDelegate.h" + +@implementation SimpleCocoaGUIAppDelegate + +@synthesize window = _window; + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + +// [self initializeVariables]; + [self createUI]; +// [self initViECocoaTest]; +// [self NSLogVideoCodecs]; +// [self startViECocoaTest]; + +// [self startLoopback]; + + [self ioLooback]; +} + +-(void)createUI{ + + NSRect outWindow1Frame = NSMakeRect(200, 200, 200, 200); + NSWindow* outWindow1 = [[NSWindow alloc] initWithContentRect:outWindow1Frame styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO]; + [outWindow1 orderOut:nil]; + NSRect vieAutotestCocoaRenderView1Frame = NSMakeRect(0, 0, 200, 200); + _vieCocoaRenderView1 = [[ViECocoaRenderView alloc] initWithFrame:vieAutotestCocoaRenderView1Frame]; + [[outWindow1 contentView] addSubview:_vieCocoaRenderView1]; + [outWindow1 setTitle:[NSString stringWithFormat:@"window1"]]; + [outWindow1 makeKeyAndOrderFront:NSApp]; + + + NSRect outWindow2Frame = NSMakeRect(400, 200, 200, 200); + NSWindow* outWindow2 = [[NSWindow alloc] initWithContentRect:outWindow2Frame styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO]; + [outWindow2 orderOut:nil]; + NSRect vieAutotestCocoaRenderView2Frame = NSMakeRect(0, 0, 200, 200); + _vieCocoaRenderView2 = [[ViECocoaRenderView alloc] initWithFrame:vieAutotestCocoaRenderView2Frame]; + [[outWindow2 contentView] addSubview:_vieCocoaRenderView2]; + [outWindow2 setTitle:[NSString stringWithFormat:@"window2"]]; + [outWindow2 makeKeyAndOrderFront:NSApp]; + + + + + + + +} + +-(void)initViECocoaTest{ + + int _error = 0; + _ptrViE = VideoEngine::Create(); + _ptrViEBase = ViEBase::GetInterface(_ptrViE); + _error = _ptrViEBase->Init(); + + _ptrViECapture = ViECapture::GetInterface(_ptrViE); + _ptrViERender = ViERender::GetInterface(_ptrViE); + _ptrViECodec = ViECodec::GetInterface(_ptrViE); + _ptrViENetwork = ViENetwork::GetInterface(_ptrViE); + + + _error = _ptrViE->SetTraceFile("ViEBaseStandardTest.txt"); + _error = _ptrViE->SetEncryptedTraceFile("ViEBaseStandardTestEncrypted.txt"); + + +} + + +-(void)initializeVariables{ + _fullScreen = YES; +} + +-(void)NSLogVideoCodecs{ + NSLog(@"Searching for video codecs....."); + + VideoCodec videoCodec; + memset(&videoCodec, 0, sizeof(VideoCodec)); + for(int index = 0; index < _ptrViECodec->NumberOfCodecs(); index++) + { + ViE_TEST(_ptrViECodec->GetCodec(index, videoCodec)); + NSLog(@"Video codec found: %s", videoCodec.plName); + } + +} +-(void)startViECocoaTest{ + + + + + int error=0; + + char deviceName[128]; + char deviceUniqueName[512]; + int captureId = 0; + int dummy = 0; + + //ViE_TEST(_ptrViEBase->CreateChannel(_videoChannel)); + //ViE_TEST(_ptrViECapture->GetCaptureDevice(0,deviceName,sizeof(deviceName),deviceUniqueName,sizeof(deviceUniqueName))); + //ViE_TEST(_ptrViECapture->AllocateCaptureDevice(deviceUniqueName,sizeof(deviceUniqueName),captureId)); + //ViE_TEST(_ptrViECapture->AllocateCaptureDevice("dummydevicethatdoesnotexist",sizeof(deviceUniqueName),dummy)); + + char captureDeviceName[V_DEVICE_NAME_LENGTH] = ""; + char captureDeviceUniqueId[V_DEVICE_NAME_LENGTH] = ""; + int captureDeviceId = 0; + + + + ViE_TEST(_ptrViE->SetTraceFilter(webrtc::TR_ALL)); + ViE_TEST(_ptrViE->SetTraceFile("ViECocoaTrace.txt")); + ViE_TEST(_ptrViE->SetEncryptedTraceFile("ViECocoaEncryptedTrace.txt")); + + + + + // base + ViE_TEST(_ptrViEBase->CreateChannel(_videoChannel)); + + // capture device + ViE_TEST(_ptrViECapture->GetCaptureDevice(V_CAPTURE_DEVICE_INDEX, captureDeviceName, V_DEVICE_NAME_LENGTH, captureDeviceUniqueId, V_DEVICE_NAME_LENGTH)); + ViE_TEST(_ptrViECapture->AllocateCaptureDevice(captureDeviceUniqueId, V_DEVICE_NAME_LENGTH, captureDeviceId)); + ViE_TEST(_ptrViECapture->ConnectCaptureDevice(captureDeviceId, _videoChannel)); + ViE_TEST(_ptrViECapture->StartCapture(captureDeviceId)); + + // renderer + ViE_TEST(_ptrViERender->AddRenderer(captureDeviceId, (void*)_vieCocoaRenderView1, 0, 0.0, 0.0, 1.0, 1.0)); + ViE_TEST(_ptrViERender->StartRender(captureDeviceId)); +// usleep(3 * 1000); +// ViE_TEST(_ptrViERender->RemoveRenderer(captureDeviceId)); + //exit(0); + + +// // codec +// [self NSLogVideoCodecs]; +// VideoCodec videoCodec; +// memset(&videoCodec, 0, sizeof(VideoCodec)); +// ViE_TEST(_ptrViECodec->GetCodec(V_CODEC_INDEX, videoCodec)); +// ViE_TEST(_ptrViECodec->SetReceiveCodec(_videoChannel, videoCodec)); +// ViE_TEST(_ptrViECodec->SetSendCodec(_videoChannel, videoCodec)); +// +// // network + base +// ViE_TEST(_ptrViENetwork->SetLocalReceiver(_videoChannel, V_RTP_PORT)); +// ViE_TEST(_ptrViEBase->StartReceive(_videoChannel)); +// ViE_TEST(_ptrViENetwork->SetSendDestination(_videoChannel, V_IP_ADDRESS, V_RTP_PORT)); +// ViE_TEST(_ptrViEBase->StartSend(_videoChannel)); +// ViE_TEST(_ptrViERender->MirrorRenderStream(captureDeviceId, true, false, true)); + + +} + +-(int)initLoopback +{ + +} +-(int)startLoopback +{ + //******************************************************** + // Begin create/initialize Video Engine for testing + //******************************************************** + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + std::string str; + + // + // Create a VideoEngine instance + // +// VideoEngine* ptrViE = NULL; + ptrViE = VideoEngine::Create(); + if (ptrViE == NULL) + { + printf("ERROR in VideoEngine::Create\n"); + return -1; + } + + error = ptrViE->SetTraceFilter(webrtc::TR_ALL); + if (error == -1) + { + printf("ERROR in VideoEngine::SetTraceLevel\n"); + return -1; + } + + + error = ptrViE->SetTraceFile("ViETrace.txt"); + if (error == -1) + { + printf("ERROR in VideoEngine::SetTraceFile\n"); + return -1; + } + + error = ptrViE->SetEncryptedTraceFile("ViEEncryptedTrace.txt"); + if (error == -1) + { + printf("ERROR in VideoEngine::SetEncryptedTraceFile\n"); + return -1; + } + + // + // Init VideoEngine and create a channel + // + ptrViEBase = ViEBase::GetInterface(ptrViE); + if (ptrViEBase == NULL) + { + printf("ERROR in ViEBase::GetInterface\n"); + return -1; + } + + error = ptrViEBase->Init(); + if (error == -1) + { + printf("ERROR in ViEBase::Init\n"); + return -1; + } + + int videoChannel = -1; + error = ptrViEBase->CreateChannel(_videoChannel); + if (error == -1) + { + printf("ERROR in ViEBase::CreateChannel\n"); + return -1; + } + + // + // List available capture devices, allocate and connect. + // + ptrViECapture = ViECapture::GetInterface(ptrViE); + if (ptrViEBase == NULL) + { + printf("ERROR in ViECapture::GetInterface\n"); + return -1; + } + + const unsigned int KMaxDeviceNameLength = 128; + const unsigned int KMaxUniqueIdLength = 256; + char deviceName[KMaxDeviceNameLength]; + memset(deviceName, 0, KMaxDeviceNameLength); + char uniqueId[KMaxUniqueIdLength]; + memset(uniqueId, 0, KMaxUniqueIdLength); + + std::cout << std::endl; + std::cout << "Available capture devices:" << std::endl; + unsigned int captureIdx = 0; + for (captureIdx = 0; + captureIdx < ptrViECapture->NumberOfCaptureDevices(); + captureIdx++) + { + memset(deviceName, 0, KMaxDeviceNameLength); + memset(uniqueId, 0, KMaxUniqueIdLength); + + error = ptrViECapture->GetCaptureDevice(captureIdx, + deviceName, KMaxDeviceNameLength, uniqueId, KMaxUniqueIdLength); + if (error == -1) + { + printf("ERROR in ViECapture::GetCaptureDevice\n"); + return -1; + } + std::cout << " " << captureIdx+1 << ". " << deviceName + << std::endl; + } + std::cout << std::endl; + std::cout << "Choose capture devices: "; +// std::getline(std::cin, str); +// captureIdx = atoi(str.c_str()) - 1; + captureIdx = 0; + error = ptrViECapture->GetCaptureDevice(captureIdx, deviceName, + KMaxDeviceNameLength, uniqueId, KMaxUniqueIdLength); + if (error == -1) + { + printf("ERROR in ViECapture::GetCaptureDevice\n"); + return -1; + } + + _captureId = 0; + error = ptrViECapture->AllocateCaptureDevice(uniqueId, + KMaxUniqueIdLength, _captureId); + if (error == -1) + { + printf("ERROR in ViECapture::AllocateCaptureDevice\n"); + return -1; + } + + error = ptrViECapture->ConnectCaptureDevice(_captureId, + _videoChannel); + if (error == -1) + { + printf("ERROR in ViECapture::ConnectCaptureDevice\n"); + return -1; + } + + error = ptrViECapture->StartCapture(_captureId); + if (error == -1) + { + printf("ERROR in ViECapture::StartCapture\n"); + return -1; + } + + // + // RTP/RTCP settings + // + ptrViERtpRtcp = ViERTP_RTCP::GetInterface(ptrViE); + if (ptrViERtpRtcp == NULL) + { + printf("ERROR in ViERTP_RTCP::GetInterface\n"); + return -1; + } + + error = ptrViERtpRtcp->SetRTCPStatus(_videoChannel, + kRtcpCompound_RFC4585); + if (error == -1) + { + printf("ERROR in ViERTP_RTCP::SetRTCPStatus\n"); + return -1; + } + + error = ptrViERtpRtcp->SetKeyFrameRequestMethod(_videoChannel, + kViEKeyFrameRequestPliRtcp); + if (error == -1) + { + printf("ERROR in ViERTP_RTCP::SetKeyFrameRequestMethod\n"); + return -1; + } + + error = ptrViERtpRtcp->SetTMMBRStatus(_videoChannel, true); + if (error == -1) + { + printf("ERROR in ViERTP_RTCP::SetTMMBRStatus\n"); + return -1; + } + + // + // Set up rendering + // + ptrViERender = ViERender::GetInterface(ptrViE); + if (ptrViERender == NULL) + { + printf("ERROR in ViERender::GetInterface\n"); + return -1; + } + + error = ptrViERender->AddRenderer(_captureId, _vieCocoaRenderView1, + 0, 0.0, 0.0, 1.0, 1.0); + if (error == -1) + { + printf("ERROR in ViERender::AddRenderer\n"); + return -1; + } + + error = ptrViERender->StartRender(_captureId); + if (error == -1) + { + printf("ERROR in ViERender::StartRender\n"); + return -1; + } + + error = ptrViERender->AddRenderer(_videoChannel, _vieCocoaRenderView2, + 1, 0.0, 0.0, 1.0, 1.0); + if (error == -1) + { + printf("ERROR in ViERender::AddRenderer\n"); + return -1; + } + + error = ptrViERender->StartRender(_videoChannel); + if (error == -1) + { + printf("ERROR in ViERender::StartRender\n"); + return -1; + } + + // + // Setup codecs + // + ptrViECodec = ViECodec::GetInterface(ptrViE); + if (ptrViECodec == NULL) + { + printf("ERROR in ViECodec::GetInterface\n"); + return -1; + } + + std::cout << std::endl; + std::cout << "Available codecs:" << std::endl; + + // Check available codecs and prepare receive codecs + VideoCodec videoCodec; + memset(&videoCodec, 0, sizeof(VideoCodec)); + unsigned int codecIdx = 0; + for (codecIdx = 0; + codecIdx < ptrViECodec->NumberOfCodecs(); + codecIdx++) + { + error = ptrViECodec->GetCodec(codecIdx, videoCodec); + if (error == -1) + { + printf("ERROR in ViECodec::GetCodec\n"); + return -1; + } + + error = ptrViECodec->SetReceiveCodec(_videoChannel, + videoCodec); + if (error == -1) + { + printf("ERROR in ViECodec::SetReceiveCodec\n"); + return -1; + } + if (videoCodec.codecType != kVideoCodecRED && + videoCodec.codecType != kVideoCodecULPFEC) + { + std::cout << " " << codecIdx+1 << ". " << videoCodec.plName + << std::endl; + } + } +// std::cout << std::endl; +// std::cout << "Choose codec: "; +// std::getline(std::cin, str); +// codecIdx = atoi(str.c_str()) - 1; + codecIdx = 0; + + error = ptrViECodec->GetCodec(codecIdx, videoCodec); + if (error == -1) + { + printf("ERROR in ViECodec::GetCodec\n"); + return -1; + } + + error = ptrViECodec->SetSendCodec(_videoChannel, videoCodec); + if (error == -1) + { + printf("ERROR in ViECodec::SetSendCodec\n"); + return -1; + } + + // + // Address settings + // + ptrViENetwork = ViENetwork::GetInterface(ptrViE); + if (ptrViENetwork == NULL) + { + printf("ERROR in ViENetwork::GetInterface\n"); + return -1; + } + + const char* ipAddress = "127.0.0.1"; + const unsigned short rtpPort = 6000; + error = ptrViENetwork->SetLocalReceiver(_videoChannel, rtpPort); + if (error == -1) + { + printf("ERROR in ViENetwork::SetLocalReceiver\n"); + return -1; + } + + error = ptrViEBase->StartReceive(_videoChannel); + if (error == -1) + { + printf("ERROR in ViENetwork::StartReceive\n"); + return -1; + } + + error = ptrViENetwork->SetSendDestination(_videoChannel, + ipAddress, rtpPort); + if (error == -1) + { + printf("ERROR in ViENetwork::SetSendDestination\n"); + return -1; + } + + error = ptrViEBase->StartSend(_videoChannel); + if (error == -1) + { + printf("ERROR in ViENetwork::StartSend\n"); + return -1; + } + + + //******************************************************** + // Engine started + //******************************************************** + + + // Call started + std::cout << std::endl; + std::cout << "Loopback call started" << std::endl; +// std::cout << std::endl << std::endl; +// std::cout << "Press enter to stop..."; +// std::getline(std::cin, str); +} + +-(int)stopLooback +{ + int error = 0; + + + + //******************************************************** + // Testing finished. Tear down Video Engine + //******************************************************** + + error = ptrViEBase->StopReceive(_videoChannel); + if (error == -1) + { + printf("ERROR in ViEBase::StopReceive\n"); + return -1; + } + + error = ptrViEBase->StopSend(_videoChannel); + if (error == -1) + { + printf("ERROR in ViEBase::StopSend\n"); + return -1; + } + + error = ptrViERender->StopRender(_captureId); + if (error == -1) + { + printf("ERROR in ViERender::StopRender\n"); + return -1; + } + + error = ptrViERender->RemoveRenderer(_captureId); + if (error == -1) + { + printf("ERROR in ViERender::RemoveRenderer\n"); + return -1; + } + + error = ptrViERender->StopRender(_videoChannel); + if (error == -1) + { + printf("ERROR in ViERender::StopRender\n"); + return -1; + } + + error = ptrViERender->RemoveRenderer(_videoChannel); + if (error == -1) + { + printf("ERROR in ViERender::RemoveRenderer\n"); + return -1; + } + + error = ptrViECapture->StopCapture(_captureId); + if (error == -1) + { + printf("ERROR in ViECapture::StopCapture\n"); + return -1; + } + + error = ptrViECapture->DisconnectCaptureDevice(_videoChannel); + if (error == -1) + { + printf("ERROR in ViECapture::DisconnectCaptureDevice\n"); + return -1; + } + + error = ptrViECapture->ReleaseCaptureDevice(_captureId); + if (error == -1) + { + printf("ERROR in ViECapture::ReleaseCaptureDevice\n"); + return -1; + } + + error = ptrViEBase->DeleteChannel(_videoChannel); + if (error == -1) + { + printf("ERROR in ViEBase::DeleteChannel\n"); + return -1; + } + + int remainingInterfaces = 0; + remainingInterfaces = ptrViECodec->Release(); + remainingInterfaces += ptrViECapture->Release(); + remainingInterfaces += ptrViERtpRtcp->Release(); + remainingInterfaces += ptrViERender->Release(); + remainingInterfaces += ptrViENetwork->Release(); + remainingInterfaces += ptrViEBase->Release(); + if (remainingInterfaces > 0) + { + printf("ERROR: Could not release all interfaces\n"); + return -1; + } + + bool deleted = VideoEngine::Delete(ptrViE); + if (deleted == false) + { + printf("ERROR in VideoEngine::Delete\n"); + return -1; + } + + return 0; + + // =================================================================== + // + // END: VideoEngine 3.0 Sample Code + // + // =================================================================== + + +} + +-(int)ioLooback +{ + //******************************************************** + // Begin create/initialize Video Engine for testing + //******************************************************** + + int error = 0; + bool succeeded = true; + int numberOfErrors = 0; + std::string str; + + // + // Create a VideoEngine instance + // + VideoEngine* ptrViE = NULL; + ptrViE = VideoEngine::Create(); + if (ptrViE == NULL) + { + printf("ERROR in VideoEngine::Create\n"); + return -1; + } + + error = ptrViE->SetTraceFilter(webrtc::TR_ALL); + if (error == -1) + { + printf("ERROR in VideoEngine::SetTraceLevel\n"); + return -1; + } + + + error = ptrViE->SetTraceFile("ViETrace.txt"); + if (error == -1) + { + printf("ERROR in VideoEngine::SetTraceFile\n"); + return -1; + } + + error = ptrViE->SetEncryptedTraceFile("ViEEncryptedTrace.txt"); + if (error == -1) + { + printf("ERROR in VideoEngine::SetEncryptedTraceFile\n"); + return -1; + } + + // + // Init VideoEngine and create a channel + // + ViEBase* ptrViEBase = ViEBase::GetInterface(ptrViE); + if (ptrViEBase == NULL) + { + printf("ERROR in ViEBase::GetInterface\n"); + return -1; + } + + error = ptrViEBase->Init(); + if (error == -1) + { + printf("ERROR in ViEBase::Init\n"); + return -1; + } + + int videoChannel = -1; + error = ptrViEBase->CreateChannel(videoChannel); + if (error == -1) + { + printf("ERROR in ViEBase::CreateChannel\n"); + return -1; + } + + // + // List available capture devices, allocate and connect. + // + ViECapture* ptrViECapture = + ViECapture::GetInterface(ptrViE); + if (ptrViEBase == NULL) + { + printf("ERROR in ViECapture::GetInterface\n"); + return -1; + } + + const unsigned int KMaxDeviceNameLength = 128; + const unsigned int KMaxUniqueIdLength = 256; + char deviceName[KMaxDeviceNameLength]; + memset(deviceName, 0, KMaxDeviceNameLength); + char uniqueId[KMaxUniqueIdLength]; + memset(uniqueId, 0, KMaxUniqueIdLength); + + std::cout << std::endl; + std::cout << "Available capture devices:" << std::endl; + unsigned int captureIdx = 0; + for (captureIdx = 0; + captureIdx < ptrViECapture->NumberOfCaptureDevices(); + captureIdx++) + { + memset(deviceName, 0, KMaxDeviceNameLength); + memset(uniqueId, 0, KMaxUniqueIdLength); + + error = ptrViECapture->GetCaptureDevice(captureIdx, + deviceName, KMaxDeviceNameLength, uniqueId, KMaxUniqueIdLength); + if (error == -1) + { + printf("ERROR in ViECapture::GetCaptureDevice\n"); + return -1; + } + std::cout << " " << captureIdx+1 << ". " << deviceName + << std::endl; + } + std::cout << std::endl; + std::cout << "Choose capture devices: "; +// std::getline(std::cin, str); +// captureIdx = atoi(str.c_str()) - 1; + captureIdx = 0; + error = ptrViECapture->GetCaptureDevice(captureIdx, deviceName, + KMaxDeviceNameLength, uniqueId, KMaxUniqueIdLength); + if (error == -1) + { + printf("ERROR in ViECapture::GetCaptureDevice\n"); + return -1; + } + + int captureId = 0; + error = ptrViECapture->AllocateCaptureDevice(uniqueId, + KMaxUniqueIdLength, captureId); + if (error == -1) + { + printf("ERROR in ViECapture::AllocateCaptureDevice\n"); + return -1; + } + + error = ptrViECapture->ConnectCaptureDevice(captureId, + videoChannel); + if (error == -1) + { + printf("ERROR in ViECapture::ConnectCaptureDevice\n"); + return -1; + } + + error = ptrViECapture->StartCapture(captureId); + if (error == -1) + { + printf("ERROR in ViECapture::StartCapture\n"); + return -1; + } + + // + // RTP/RTCP settings + // + ViERTP_RTCP* ptrViERtpRtcp = + ViERTP_RTCP::GetInterface(ptrViE); + if (ptrViERtpRtcp == NULL) + { + printf("ERROR in ViERTP_RTCP::GetInterface\n"); + return -1; + } + + error = ptrViERtpRtcp->SetRTCPStatus(videoChannel, + kRtcpCompound_RFC4585); + if (error == -1) + { + printf("ERROR in ViERTP_RTCP::SetRTCPStatus\n"); + return -1; + } + + error = ptrViERtpRtcp->SetKeyFrameRequestMethod(videoChannel, + kViEKeyFrameRequestPliRtcp); + if (error == -1) + { + printf("ERROR in ViERTP_RTCP::SetKeyFrameRequestMethod\n"); + return -1; + } + + error = ptrViERtpRtcp->SetTMMBRStatus(videoChannel, true); + if (error == -1) + { + printf("ERROR in ViERTP_RTCP::SetTMMBRStatus\n"); + return -1; + } + + // + // Set up rendering + // + ViERender* ptrViERender = + ViERender::GetInterface(ptrViE); + if (ptrViERender == NULL) + { + printf("ERROR in ViERender::GetInterface\n"); + return -1; + } + +// error = ptrViERender->EnableFullScreenRender(_vieCocoaRenderView1); +// if (error == -1) +// { +// printf("ERROR in ViERender::AddRenderer\n"); +// return -1; +// } + + + error = ptrViERender->AddRenderer(captureId, _vieCocoaRenderView1, + 0, 0.5, 0.5, 1.0, 1.0); + if (error == -1) + { + printf("ERROR in ViERender::AddRenderer\n"); + return -1; + } + + error = ptrViERender->StartRender(captureId); + if (error == -1) + { + printf("ERROR in ViERender::StartRender\n"); + return -1; + } + + error = ptrViERender->AddRenderer(videoChannel, _vieCocoaRenderView2, + 1, 0.0, 0.0, 1.0, 1.0); + if (error == -1) + { + printf("ERROR in ViERender::AddRenderer\n"); + return -1; + } + + error = ptrViERender->StartRender(videoChannel); + if (error == -1) + { + printf("ERROR in ViERender::StartRender\n"); + return -1; + } + + // + // Setup codecs + // + ViECodec* ptrViECodec = ViECodec::GetInterface(ptrViE); + if (ptrViECodec == NULL) + { + printf("ERROR in ViECodec::GetInterface\n"); + return -1; + } + + std::cout << std::endl; + std::cout << "Available codecs:" << std::endl; + + // Check available codecs and prepare receive codecs + VideoCodec videoCodec; + memset(&videoCodec, 0, sizeof(VideoCodec)); + unsigned int codecIdx = 0; + for (codecIdx = 0; + codecIdx < ptrViECodec->NumberOfCodecs(); + codecIdx++) + { + error = ptrViECodec->GetCodec(codecIdx, videoCodec); + if (error == -1) + { + printf("ERROR in ViECodec::GetCodec\n"); + return -1; + } + + error = ptrViECodec->SetReceiveCodec(videoChannel, + videoCodec); + if (error == -1) + { + printf("ERROR in ViECodec::SetReceiveCodec\n"); + return -1; + } + if (videoCodec.codecType != kVideoCodecRED && + videoCodec.codecType != kVideoCodecULPFEC) + { + std::cout << " " << codecIdx+1 << ". " << videoCodec.plName + << std::endl; + } + } + std::cout << std::endl; + std::cout << "Choose codec: "; +// std::getline(std::cin, str); +// codecIdx = atoi(str.c_str()) - 1; + + + error = ptrViECapture->ShowCaptureSettingsDialogBox("unique",10, "mytitle"); + codecIdx = 0; + error = ptrViECodec->GetCodec(codecIdx, videoCodec); + if (error == -1) + { + printf("ERROR in ViECodec::GetCodec\n"); + return -1; + } + + error = ptrViECodec->SetSendCodec(videoChannel, videoCodec); + if (error == -1) + { + printf("ERROR in ViECodec::SetSendCodec\n"); + return -1; + } + + // + // Address settings + // + ViENetwork* ptrViENetwork = + ViENetwork::GetInterface(ptrViE); + if (ptrViENetwork == NULL) + { + printf("ERROR in ViENetwork::GetInterface\n"); + return -1; + } + + const char* ipAddress = "127.0.0.1"; + const unsigned short rtpPort = 6000; + error = ptrViENetwork->SetLocalReceiver(videoChannel, rtpPort); + if (error == -1) + { + printf("ERROR in ViENetwork::SetLocalReceiver\n"); + return -1; + } + + error = ptrViEBase->StartReceive(videoChannel); + if (error == -1) + { + printf("ERROR in ViENetwork::StartReceive\n"); + return -1; + } + + error = ptrViENetwork->SetSendDestination(videoChannel, + ipAddress, rtpPort); + if (error == -1) + { + printf("ERROR in ViENetwork::SetSendDestination\n"); + return -1; + } + + error = ptrViEBase->StartSend(videoChannel); + if (error == -1) + { + printf("ERROR in ViENetwork::StartSend\n"); + return -1; + } + + + //******************************************************** + // Engine started + //******************************************************** + + + // Call started + std::cout << std::endl; + std::cout << "Loopback call started" << std::endl; + std::cout << std::endl << std::endl; + std::cout << "Press enter to stop..."; +// [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; +// std::getline(std::cin, str); + usleep(5 * 1000 * 1000); + + //int i = 0; +// while(1) +// { +// NSLog(@"app iteration %d", i); +// i++; +// [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; +// std::getline(std::cin, str); +// if(i > 3) +// { +// break; +// } +// } + + //******************************************************** + // Testing finished. Tear down Video Engine + //******************************************************** + + error = ptrViEBase->StopReceive(videoChannel); + if (error == -1) + { + printf("ERROR in ViEBase::StopReceive\n"); + return -1; + } + + error = ptrViEBase->StopSend(videoChannel); + if (error == -1) + { + printf("ERROR in ViEBase::StopSend\n"); + return -1; + } + + error = ptrViERender->StopRender(captureId); + if (error == -1) + { + printf("ERROR in ViERender::StopRender\n"); + return -1; + } + + error = ptrViERender->RemoveRenderer(captureId); + if (error == -1) + { + printf("ERROR in ViERender::RemoveRenderer\n"); + return -1; + } + + error = ptrViERender->StopRender(videoChannel); + if (error == -1) + { + printf("ERROR in ViERender::StopRender\n"); + return -1; + } + + error = ptrViERender->RemoveRenderer(videoChannel); + if (error == -1) + { + printf("ERROR in ViERender::RemoveRenderer\n"); + return -1; + } + + error = ptrViECapture->StopCapture(captureId); + if (error == -1) + { + printf("ERROR in ViECapture::StopCapture\n"); + return -1; + } + + error = ptrViECapture->DisconnectCaptureDevice(videoChannel); + if (error == -1) + { + printf("ERROR in ViECapture::DisconnectCaptureDevice\n"); + return -1; + } + + error = ptrViECapture->ReleaseCaptureDevice(captureId); + if (error == -1) + { + printf("ERROR in ViECapture::ReleaseCaptureDevice\n"); + return -1; + } + + error = ptrViEBase->DeleteChannel(videoChannel); + if (error == -1) + { + printf("ERROR in ViEBase::DeleteChannel\n"); + return -1; + } + + int remainingInterfaces = 0; + remainingInterfaces = ptrViECodec->Release(); + remainingInterfaces += ptrViECapture->Release(); + remainingInterfaces += ptrViERtpRtcp->Release(); + remainingInterfaces += ptrViERender->Release(); + remainingInterfaces += ptrViENetwork->Release(); + remainingInterfaces += ptrViEBase->Release(); + if (remainingInterfaces > 0) + { + printf("ERROR: Could not release all interfaces\n"); + return -1; + } + + bool deleted = VideoEngine::Delete(ptrViE); + if (deleted == false) + { + printf("ERROR in VideoEngine::Delete\n"); + return -1; + } + + NSLog(@"Finished function"); + return 0; + + // + // END: VideoEngine 3.0 Sample Code + // + // =================================================================== +} + + + + +-(IBAction)handleRestart:(id)sender +{ +// [self stopLooback]; +// [self startLoopback]; + [self ioLooback]; +} +@end diff --git a/video_engine/main/test/SimpleCocoaGUI/SimpleCocoaGUI_Prefix.pch b/video_engine/main/test/SimpleCocoaGUI/SimpleCocoaGUI_Prefix.pch new file mode 100644 index 0000000000..72b5870875 --- /dev/null +++ b/video_engine/main/test/SimpleCocoaGUI/SimpleCocoaGUI_Prefix.pch @@ -0,0 +1,7 @@ +// +// Prefix header for all source files of the 'SimpleCocoaGUI' target in the 'SimpleCocoaGUI' project +// + +#ifdef __OBJC__ + #import +#endif diff --git a/video_engine/main/test/SimpleCocoaGUI/main.m b/video_engine/main/test/SimpleCocoaGUI/main.m new file mode 100644 index 0000000000..9d52a1c3ce --- /dev/null +++ b/video_engine/main/test/SimpleCocoaGUI/main.m @@ -0,0 +1,12 @@ +// +// main.m +// SimpleCocoaGUI +// +// + +#import + +int main(int argc, char *argv[]) +{ + return NSApplicationMain(argc, (const char **) argv); +} diff --git a/video_engine/main/test/WindowsTest/Capture.rc b/video_engine/main/test/WindowsTest/Capture.rc new file mode 100644 index 0000000000..7ad089bd09 --- /dev/null +++ b/video_engine/main/test/WindowsTest/Capture.rc @@ -0,0 +1,254 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Korean resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_KOR) +#ifdef _WIN32 +LANGUAGE LANG_KOREAN, SUBLANG_DEFAULT +#pragma code_page(949) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_KOR)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 18, 1\r\n" + "#pragma code_page(949)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\Capture.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""l.kor\\afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN +END + +#endif // Korean resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// Swedish resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_SVE) +#ifdef _WIN32 +LANGUAGE LANG_SWEDISH, SUBLANG_DEFAULT +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_SLAVE_CHANNEL DIALOGEX 0, 0, 677, 358 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Slave channel" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_IPADDRESS1,"SysIPAddress32",WS_TABSTOP,485,18,105,15 + EDITTEXT IDC_LOCAL_PORT1,631,18,36,16,ES_AUTOHSCROLL + LTEXT "IP-address",IDC_STATIC,495,7,42,9 + LTEXT "Local Port",IDC_STATIC,633,7,37,9 + EDITTEXT IDC_REMOTE_PORT1,593,18,36,16,ES_AUTOHSCROLL + LTEXT "Port",IDC_STATIC,595,7,17,9 + CONTROL "Ext",IDC_EXTTRANSPORT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,489,41,29,12 + LTEXT "delay",IDC_STATIC,589,41,21,9 + COMBOBOX IDC_PACKETLOSS,535,40,45,82,CBS_DROPDOWN | WS_DISABLED | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_DELAY,611,40,45,82,CBS_DROPDOWN | WS_DISABLED | WS_VSCROLL | WS_TABSTOP +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_SLAVE_CHANNEL, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 670 + TOPMARGIN, 7 + BOTTOMMARGIN, 351 + END +END +#endif // APSTUDIO_INVOKED + +#endif // Swedish resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// Neutral (Sys. Default) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEUSD) +#ifdef _WIN32 +LANGUAGE LANG_NEUTRAL, SUBLANG_SYS_DEFAULT +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DXQUALITY_DIALOG DIALOGEX 0, 0, 699, 385 +STYLE DS_ABSALIGN | DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | WS_MINIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_WINDOWEDGE | WS_EX_STATICEDGE | WS_EX_APPWINDOW | WS_EX_NOINHERITLAYOUT +CAPTION "webrtc ViE test program" +FONT 9, "Arial", 400, 0, 0x0 +BEGIN + PUSHBUTTON "Start Send",IDC_STARTSEND,589,270,50,19 + PUSHBUTTON "Stop Send",IDC_STOPSend,639,270,50,19 + PUSHBUTTON "Start Listen",IDC_STARTLISTEN,589,291,50,19 + PUSHBUTTON "StopListen",IDC_STOPLISTEN,639,291,50,19 + CONTROL "",IDC_LIVEVIDEO,"Static",SS_BITMAP | SS_CENTERIMAGE | SS_SUNKEN,450,179,139,101 + COMBOBOX IDC_DEVICE,487,14,185,30,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP + CTEXT "Select Capture Device",IDC_STATIC,485,7,78,8 + COMBOBOX IDC_CODEC_LIST,490,90,58,30,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP + LTEXT "Codec",IDC_STATIC,490,82,21,8 + COMBOBOX IDC_CODEC_SIZE,627,90,62,30,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP + LTEXT "Codec Size",IDC_STATIC,611,82,36,8 + CONTROL "",IDC_IPADDRESS1,"SysIPAddress32",WS_TABSTOP,490,46,90,13 + EDITTEXT IDC_LOCAL_PORT1,615,46,31,14,ES_AUTOHSCROLL + LTEXT "IP-address",IDC_STATIC,498,37,36,8 + LTEXT "Local Port",IDC_STATIC,616,36,32,8 + LTEXT "Start Bitrate",IDC_STATIC,553,80,37,8 + COMBOBOX IDC_BITRATE,558,90,49,30,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP + EDITTEXT IDC_REMOTE_PORT1,582,46,31,14,ES_AUTOHSCROLL + LTEXT "Port",IDC_STATIC,584,37,14,8 + GROUPBOX "Remote client 1",IDC_STATIC,487,27,203,50 + LTEXT "Min FrameRate",IDC_STATIC,488,106,49,8 + COMBOBOX IDC_MIN_FRAME_RATE,488,115,48,82,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP + CONTROL "",IDC_CAPTURE,"Static",SS_BITMAP | SS_CENTERIMAGE | SS_REALSIZEIMAGE,7,7,418,276 + CONTROL "TMMBR",IDC_TMMBR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,639,159,40,10 + GROUPBOX "Standard Protection",IDC_STATIC,566,133,72,41 + CONTROL "None",IDC_PROT_NONE,"Button",BS_AUTORADIOBUTTON | WS_GROUP,579,141,33,10 + CONTROL "NACK",IDC_PROT_NACK,"Button",BS_AUTORADIOBUTTON,579,163,35,10 + CONTROL "FEC",IDC_PROT_FEC,"Button",BS_AUTORADIOBUTTON,579,152,30,10 + COMBOBOX IDC_RTCPMODE,571,119,80,57,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP + LTEXT "RTCP Mode",IDC_STATIC,571,110,39,8 + LISTBOX IDC_INFORMATION,476,309,214,63,LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_PACKETBURST,653,118,36,57,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP + LTEXT "Packet Burst",IDC_STATIC,649,109,40,8 + CONTROL "Stop Log",IDC_FREEZELOG,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,465,292,44,10 + PUSHBUTTON "Camera Cap",IDC_CAMERACAP,530,291,55,16 + CONTROL "Ext",IDC_EXTTRANSPORT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,493,66,25,10 + LTEXT "loss",IDC_STATIC,519,66,15,8 + LTEXT "delay",IDC_STATIC,578,66,18,8 + COMBOBOX IDC_PACKETLOSS,533,65,38,82,CBS_DROPDOWN | WS_DISABLED | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_DELAY,598,65,38,82,CBS_DROPDOWN | WS_DISABLED | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Record Incoming",IDC_BTN_RECORD_INCOMING,587,198,69,14 + PUSHBUTTON "Record outgoing",IDC_BTN_RECORD_OUTGOING,587,212,69,14 + PUSHBUTTON "Create Slave",IDC_BTN_CREATE_SLAVE,586,231,50,14 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_DXQUALITY_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 690 + VERTGUIDE, 321 + VERTGUIDE, 372 + VERTGUIDE, 425 + VERTGUIDE, 465 + TOPMARGIN, 7 + BOTTOMMARGIN, 372 + END +END +#endif // APSTUDIO_INVOKED + +#endif // Neutral (Sys. Default) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_KOR) +#ifdef _WIN32 +LANGUAGE 18, 1 +#pragma code_page(949) +#endif //_WIN32 +#include "res\Capture.rc2" // non-Microsoft Visual C++ edited resources +#include "l.kor\afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/video_engine/main/test/WindowsTest/CaptureDevicePool.cpp b/video_engine/main/test/WindowsTest/CaptureDevicePool.cpp new file mode 100644 index 0000000000..8f7c9a2d7f --- /dev/null +++ b/video_engine/main/test/WindowsTest/CaptureDevicePool.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "CaptureDevicePool.h" +#include "map_wrapper.h" +#include +#include +#include "critical_section_wrapper.h" +#include "vie_file.h" + +CaptureDevicePool::CaptureDevicePool(VideoEngine* videoEngine): +_critSect(*CriticalSectionWrapper::CreateCriticalSection()), +_vieCapture(ViECapture::GetInterface(videoEngine)), +_vieFile(ViEFile::GetInterface(videoEngine)) +{ +} + +CaptureDevicePool::~CaptureDevicePool(void) +{ + assert(_deviceMap.Size()==0); + _vieCapture->Release(); + _vieFile->Release(); + delete &_critSect; +} + +WebRtc_Word32 CaptureDevicePool::GetCaptureDevice(int& captureId, const char* uniqeDeviceName) +{ + CriticalSectionScoped cs(_critSect); + DeviceItem* device=NULL; + + for(MapItem* item=_deviceMap.First(); + item!=NULL; + item=_deviceMap.Next(item)) + { + //Found the device? + if(strcmp(uniqeDeviceName,(static_cast( item->GetItem()))->uniqeDeviceName)==0) + { + device=static_cast( item->GetItem()); + device->refCount++; + captureId=device->captureId; + return 0; + } + } + device = new DeviceItem; + strncpy(device->uniqeDeviceName,uniqeDeviceName,255); + + + // Device does not exist. Create it. + WebRtc_Word32 result=_vieCapture->AllocateCaptureDevice(device->uniqeDeviceName,strlen(device->uniqeDeviceName),device->captureId); + if(result==0) + { + //CaptureCapability cap; + /*cap.height=1080; + cap.width=1920; + cap.maxFPS=25; + cap.interlaced=true;*/ + // result=_vieCapture->StartCapture(device->captureId,cap); + result=_vieFile->SetCaptureDeviceImage(device->captureId,"captureDeviceImage.jpg"); + } + captureId=device->captureId; + _deviceMap.Insert(captureId,device); + device->refCount++; + + return result; + + +} +WebRtc_Word32 CaptureDevicePool::ReturnCaptureDevice(int captureId) +{ + CriticalSectionScoped cs(_critSect); + + MapItem* mapItem=_deviceMap.Find(captureId); + if(!mapItem) + return -1; + + DeviceItem* item=static_cast (mapItem->GetItem()); + if(!item) + return 0; + item->refCount--; + WebRtc_Word32 result=0; + + if(item->refCount==0) + { + result=_vieCapture->ReleaseCaptureDevice(captureId); + + _deviceMap.Erase(mapItem); + delete item; + + } + return result; +} diff --git a/video_engine/main/test/WindowsTest/CaptureDevicePool.h b/video_engine/main/test/WindowsTest/CaptureDevicePool.h new file mode 100644 index 0000000000..104b84f72b --- /dev/null +++ b/video_engine/main/test/WindowsTest/CaptureDevicePool.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#pragma once + +#include "common_types.h" + +#include "vie_base.h" +#include "vie_capture.h" +#include "vie_file.h" +#include "map_wrapper.h" + +namespace webrtc { +class CriticalSectionWrapper; +} +using namespace webrtc; +class CaptureDevicePool +{ +public: + CaptureDevicePool(VideoEngine* videoEngine); + ~CaptureDevicePool(void); + WebRtc_Word32 GetCaptureDevice(int& captureId, const char uniqeDeviceName[256]); + WebRtc_Word32 ReturnCaptureDevice(int captureId); + + private: + struct DeviceItem + { + int captureId; + WebRtc_Word32 refCount; + char uniqeDeviceName[256]; + DeviceItem() + { + captureId=-1; + refCount=0; + } + }; + CriticalSectionWrapper& _critSect; + ViECapture* _vieCapture; + ViEFile* _vieFile; + MapWrapper _deviceMap; + +}; diff --git a/video_engine/main/test/WindowsTest/ChannelDlg.cpp b/video_engine/main/test/WindowsTest/ChannelDlg.cpp new file mode 100644 index 0000000000..cea17c16fa --- /dev/null +++ b/video_engine/main/test/WindowsTest/ChannelDlg.cpp @@ -0,0 +1,1279 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "ChannelDlg.h" +#include "VideoSize.h" +#include "CaptureDevicePool.h" +#include "ChannelPool.h" + +#include +#include + + +#include "assert.h" + + +#include // threads. + +#if defined _WIN32 + #define SLEEP_10_SEC ::Sleep(10000) + #define GET_TIME_IN_MS timeGetTime +#endif + + +// Hack to convert char to TCHAR, using two buffers to be able to +// call twice in the same statement +TCHAR convertTemp1[256] = {0}; +TCHAR convertTemp2[256] = {0}; +bool convertBufferSwitch(false); +TCHAR* CharToTchar(const char* str, int len) +{ +#ifdef _UNICODE + TCHAR* temp = convertBufferSwitch ? convertTemp1 : convertTemp2; + convertBufferSwitch = !convertBufferSwitch; + memset(temp, 0, sizeof(convertTemp1)); + MultiByteToWideChar(CP_UTF8, 0, str, len, temp, 256); + return temp; +#else + return str; +#endif +} + +// Hack to convert TCHAR to char +char convertTemp3[256] = {0}; +char* TcharToChar(TCHAR* str, int len) +{ +#ifdef _UNICODE + memset(convertTemp3, 0, sizeof(convertTemp3)); + WideCharToMultiByte(CP_UTF8, 0, str, len, convertTemp3, 256, 0, 0); + return convertTemp3; +#else + return str; +#endif +} + +///////////////////////////////////////////////////////////////////////////// +// CDXChannelDlg dialog + +CDXChannelDlg::CDXChannelDlg(VideoEngine* videoEngine, + CaptureDevicePool& captureDevicePool, + ChannelPool& channelPool, + void* voiceEngine + ,CWnd* pParent,CDXChannelDlgObserver* observer, + int parentChannel/*=-1*/) + : CDialog(CDXChannelDlg::IDD, pParent), + _canAddLog(true), + _dialogObserver(observer), + _videoEngine(videoEngine), + _captureDevicePool(captureDevicePool), + _channelPool(channelPool), + _parentChannel(parentChannel), +#ifndef NO_VOICE_ENGINE + _voiceEngine((VoiceEngine*) voiceEngine), +#endif + _callbackEvent(::CreateEvent( NULL, FALSE, FALSE, NULL)), + _externalTransport(NULL) +{ + strcpy(_logMsg,""); + _channelId = -1; + _audioChannel=-1; + _captureId=-1; + + //_transport = NULL; + + + //{{AFX_DATA_INIT(CDXChannelDlg) + //}}AFX_DATA_INIT + // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 + + InitializeCriticalSection(&_critCallback); + unsigned int threadID; + _callbackThread=(HANDLE)_beginthreadex(NULL,1024*1024,CallbackThread,(void*)this,0, &threadID); + + +} + +void CDXChannelDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CDXChannelDlg) + DDX_Control(pDX, IDC_DEVICE, m_ctrlDevice); + DDX_Control(pDX, IDC_CODEC_LIST, m_ctrlCodec); + DDX_Control(pDX, IDC_CAPTURE, m_ctrlLiveRemoteVideo); + DDX_Control(pDX, IDC_LIVEVIDEO, m_ctrlLiveVideo); + DDX_Control(pDX, IDC_LOCAL_PORT1, m_localPort1); + DDX_Control(pDX, IDC_REMOTE_PORT1, m_remotePort1); + DDX_Control(pDX, IDC_IPADDRESS1, m_remoteIp1); + DDX_Control(pDX, IDC_CODEC_SIZE, m_ctrlCodecSize); + DDX_Control(pDX, IDC_RTCPMODE, m_ctrlRtcpMode); + DDX_Control(pDX, IDC_PACKETBURST, m_ctrlPacketBurst); + DDX_Control(pDX, IDC_BITRATE, m_ctrlBitrate); + DDX_Control(pDX, IDC_MIN_FRAME_RATE, m_ctrlMinFrameRate); + DDX_Control(pDX, IDC_TMMBR,m_cbTmmbr); + DDX_Control(pDX, IDC_EXTTRANSPORT,m_cbExternalTransport); + DDX_Control(pDX, IDC_PACKETLOSS,m_ctrlPacketLoss); + DDX_Control(pDX, IDC_DELAY,m_ctrlDelay); + DDX_Control(pDX, IDC_FREEZELOG,m_cbFreezeLog); + DDX_Control(pDX,IDC_INFORMATION,m_ctrlInfo); + //}}AFX_DATA_MAP +} + +// ON_WM_SYSKEYDOWN ALT+key + +BEGIN_MESSAGE_MAP(CDXChannelDlg, CDialog) + //{{AFX_MSG_MAP(CDXChannelDlg) + ON_WM_SYSCOMMAND() + ON_WM_RBUTTONUP() + //ON_WM_DEVICECHANGE() + ON_WM_PAINT() + ON_WM_QUERYDRAGICON() + ON_BN_CLICKED(IDC_STARTSEND, OnStartSend) + ON_BN_CLICKED(IDC_STOPSend, OnStopSend) + //ON_WM_TIMER() + ON_WM_DESTROY() + //}}AFX_MSG_MAP + ON_CBN_SELCHANGE(IDC_CODEC_LIST, OnCbnSelchangeCodecList) + ON_CBN_SELCHANGE(IDC_DEVICE, OnCbnSelchangeDevice) + ON_CBN_SELCHANGE(IDC_CODEC_SIZE, OnCbnSelchangeSize) + ON_CBN_SELCHANGE(IDC_BITRATE, OnCbnSelchangeBitrate) + //ON_MESSAGE(WM_DISPLAYCHANGE, OnDisplayChange) + ON_CBN_SELCHANGE(IDC_MIN_FRAME_RATE, OnCbnSelchangeMinFrameRate) + ON_BN_CLICKED(IDC_STARTLISTEN, OnBnClickedStartlisten) + ON_BN_CLICKED(IDC_STOPLISTEN, OnBnClickedStoplisten) + ON_BN_CLICKED(IDC_TMMBR, &CDXChannelDlg::OnBnClickedTmmbr) + ON_CBN_SELCHANGE(IDC_RTCPMODE, &CDXChannelDlg::OnCbnSelchangeRtcpmode) + ON_BN_CLICKED(IDC_PROT_NACK, &CDXChannelDlg::OnBnClickedProtNack) + ON_BN_CLICKED(IDC_PROT_NONE, &CDXChannelDlg::OnBnClickedProtNone) + ON_BN_CLICKED(IDC_PROT_FEC, &CDXChannelDlg::OnBnClickedProtFec) + ON_BN_CLICKED(IDC_FREEZELOG, &CDXChannelDlg::OnBnClickedFreezelog) + ON_BN_CLICKED(IDC_CAMERACAP, &CDXChannelDlg::OnBnClickedCameracap) + ON_BN_CLICKED(IDC_EXTTRANSPORT, &CDXChannelDlg::OnBnClickedExttransport) + ON_CBN_SELCHANGE(IDC_PACKETLOSS, &CDXChannelDlg::OnCbnSelchangePacketloss) + ON_CBN_SELCHANGE(IDC_DELAY, &CDXChannelDlg::OnCbnSelchangeDelay) + ON_BN_CLICKED(IDC_BTN_RECORD_INCOMING, &CDXChannelDlg::OnBnClickedBtnRecordIncoming) + ON_BN_CLICKED(IDC_BTN_RECORD_OUTGOING, &CDXChannelDlg::OnBnClickedBtnRecordOutgoing) + ON_BN_CLICKED(IDC_BTN_CREATE_SLAVE, &CDXChannelDlg::OnBnClickedBtnCreateSlave) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CDXChannelDlg message handlers + + +BOOL CDXChannelDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // Set the icon for this dialog. The framework does this automatically + // when the application's main window is not a dialog + SetIcon(m_hIcon, TRUE); // Set big icon + SetIcon(m_hIcon, FALSE); // Set small icon + + + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("5")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("6")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("7")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("8")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("9")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("10")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("11")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("12")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("13")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("14")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("15")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("16")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("17")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("18")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("19")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("20")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("21")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("22")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("23")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("24")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("25")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("26")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("27")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("28")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("29")); + ::SendMessage(m_ctrlMinFrameRate.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("30")); + m_ctrlMinFrameRate.SetCurSel(25); + + // Codec sizes + for(VideoSize i=VideoSize::UNDEFINED;iSetVoiceEngine(_voiceEngine),-5); +#endif + + int err = 0; + + char str[64]; + bool found = false; + + + int captureIdx = 0; + while (-1 !=_vieCapture->GetCaptureDevice(captureIdx,str,sizeof(str),NULL,0)) + { + char* tmp = strstr(str,"(VFW)"); + if (!tmp) + { + ::SendMessage(m_ctrlDevice.m_hWnd, CB_ADDSTRING, 0,(LPARAM)CharToTchar(str,-1)); + found = true; + } + captureIdx++; + memset(str, 0, 64); + } + WIN32_FIND_DATA FindFileData; + HANDLE hFind; + //char fileSearch[256]; + //strcpy(fileSearch,_T("*.avi")); + hFind = FindFirstFile(_T("*.avi"), &FindFileData); + if (hFind != INVALID_HANDLE_VALUE) + { + + ::SendMessage(m_ctrlDevice.m_hWnd, CB_ADDSTRING, 0,(LPARAM)(FindFileData.cFileName)); + while(FindNextFile(hFind,&FindFileData)) + { + ::SendMessage(m_ctrlDevice.m_hWnd, CB_ADDSTRING, 0,(LPARAM)(FindFileData.cFileName)); + } + FindClose(hFind); + } + + + + ::SendMessage(m_ctrlDevice.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("Conference")); + ::SendMessage(m_ctrlDevice.m_hWnd, CB_ADDSTRING, 0,(LPARAM)_T("None")); + + if (!found) + { + strncpy(str,"N/A",64); + ::SendMessage(m_ctrlDevice.m_hWnd, CB_ADDSTRING, 0,(LPARAM)CharToTchar(str,-1)); + } + m_ctrlDevice.SetCurSel(0); + + //Codecs + int numOfCodecs = _vieCodec->NumberOfCodecs(); + for(int i=0; iGetCodec(i,codec)) + { + ::SendMessage(m_ctrlCodec.m_hWnd, CB_ADDSTRING, 0,(LPARAM)CharToTchar(codec.plName,-1)); + } + } + m_ctrlCodec.SetCurSel(0); + +#ifndef NO_VOICE_ENGINE + CodecInst voiceCodec; + int numOfVeCodecs = _veCodec->NumOfCodecs(); + for(int i=0; iGetCodec(i,voiceCodec)!=-1) + { + if(strncmp(voiceCodec.plname,"ISAC",4)==0) + break; + + + } + } + + _audioChannel = _veBase->CreateChannel(); + + + TEST_MUSTPASS(_veRTCP->SetRTCPStatus(_audioChannel, true),-5); + TEST_MUSTPASS(_veCodec->SetSendCodec(_audioChannel, voiceCodec),-5); + TEST_MUSTPASS(_veBase->StartPlayout(_audioChannel),-5); + +#endif //NO_VOICE_ENGINE + + if(_parentChannel==-1) + { + TEST_MUSTPASS(_vieBase->CreateChannel(_channelId),-5); + } + else // This is a slave channel + { + TEST_MUSTPASS(_vieBase->CreateChannel(_channelId,_parentChannel),-5); + } +#ifndef NO_VOICE_ENGINE + TEST_MUSTPASS(_vieBase->ConnectAudioChannel(_channelId,_audioChannel),-5); +#endif + + _channelPool.AddChannel(_channelId); + + //Set Receive codec + { + VideoCodec codec; + int numOfCodecs = _vieCodec->NumberOfCodecs();; + for(int i=0; iGetCodec(i,codec)) + { + if(codec.codecType == webrtc::kVideoCodecVP8) + { + codec.codecSpecific.VP8.feedbackModeOn = true; + codec.codecSpecific.VP8.pictureLossIndicationOn = true; + } + TEST_MUSTPASS(_vieCodec->SetReceiveCodec(_channelId,codec),-5); + } + } + } + + //TMMBR + m_cbTmmbr.SetCheck(BST_CHECKED); + OnBnClickedTmmbr(); + + //Packet Burst + m_ctrlPacketBurst.SetCurSel(0); + + + //Protection method none + CButton *opProtection = (CButton *) GetDlgItem(IDC_PROT_NONE); + opProtection->SetCheck(BST_CHECKED); + OnBnClickedProtNone(); + + + // Configure the renderer + ConfigureRender(); + + TEST_MUSTPASS(_vieCodec->RegisterEncoderObserver(_channelId,*this),kViECodecObserverAlreadyRegistered); + TEST_MUSTPASS(_vieCodec->RegisterDecoderObserver(_channelId,*this),-5); + + TEST_MUSTPASS(_vieBase->RegisterObserver(*this),kViEBaseObserverAlreadyRegistered); + + + + + //Set captions based on channel id + m_remoteIp1.SetAddress(127,0,0,1); + CString port; + port.AppendFormat(_T("%d"),11111+_channelId*4); + m_remotePort1.SetWindowText(port); + m_localPort1.SetWindowText(port); + + CString title; + this->GetWindowText(title); + if(_parentChannel==-1) + { + title.AppendFormat(_T("%s - channel %d"),title,_channelId); + } + else + { + title.AppendFormat(_T("%s - slave channel %d - parent %d"),title,_channelId,_parentChannel); + } + this->SetWindowText(title); + + if(_parentChannel!=-1) + m_ctrlDevice.EnableWindow(FALSE); //Prevent from changing capture device + + return TRUE; // return TRUE unless you set the focus to a control +} + + + +void CDXChannelDlg::OnTimer(UINT nIDEvent) +{ + CDialog::OnTimer(nIDEvent); +} + +void CDXChannelDlg::SetSendCodec() +{ + // Get the codec stucture + int codecSel= m_ctrlCodec.GetCurSel(); + VideoCodec codec; + TEST_MUSTPASS(_vieCodec->GetCodec(codecSel,codec),-5); + + + // Set Codec Size + VideoSize sizeSel=VideoSize(m_ctrlCodecSize.GetCurSel()); + int width, height; + GetWidthHeight(sizeSel, width, height); + codec.width=width; + codec.height=height; + + //Set the codec bitrate + CString bitrateStr; + m_ctrlBitrate.GetLBText(m_ctrlBitrate.GetCurSel(), bitrateStr); + int bitrate = _ttoi(bitrateStr.GetBuffer(0)); + if(codec.codecType!=kVideoCodecI420) + { + codec.startBitrate=bitrate; + codec.maxBitrate=bitrate*4; + } + + + //Set the codec frame rate + codec.maxFramerate = m_ctrlMinFrameRate.GetCurSel() +5; + + if(strncmp(codec.plName, "VP8", 5) == 0) + { + codec.codecSpecific.VP8.feedbackModeOn = true; + codec.codecSpecific.VP8.pictureLossIndicationOn = true; + TEST_MUSTPASS(_vieRTPRTCP->SetKeyFrameRequestMethod(_channelId, kViEKeyFrameRequestPliRtcp),-5); + }else + { + TEST_MUSTPASS(_vieRTPRTCP->SetKeyFrameRequestMethod(_channelId, kViEKeyFrameRequestPliRtcp),-5); + } + TEST_MUSTPASS(_vieCodec->SetSendCodec(_channelId, codec),-5); + + if (codec.codecType == webrtc::kVideoCodecMPEG4) + { + unsigned char configParameterSize = 0; + _vieCodec->GetCodecConfigParameters(_channelId, codec.codecSpecific.MPEG4.configParameters, configParameterSize); + codec.codecSpecific.MPEG4.configParametersSize = configParameterSize; + _vieCodec->SetReceiveCodec(_channelId, codec); + } + if (codec.codecType == webrtc::kVideoCodecI420) + { // Need to set the receive codec size + _vieCodec->SetReceiveCodec(_channelId, codec); + } +} + +void CDXChannelDlg::SetSendDestination() +{ + if(_externalTransport) + return; + + BYTE part1, part2, part3, part4; + char sendIP1[16]; + m_remoteIp1.GetAddress(part1, part2, part3, part4); + sprintf(sendIP1,"%d.%d.%d.%d",part1,part2,part3,part4); + + CString strPort; + m_remotePort1.GetWindowText(strPort); + int remotePort1 = _ttoi(strPort.GetString()); + +#ifdef IPV6 + char* recIP = "::0"; +#else + char* recIP = "0.0.0.0"; +#endif //IPV6 + + TEST_MUSTPASS(_vieNetwork->SetSendDestination(_channelId,sendIP1,remotePort1),kViENetworkAlreadySending); + + #ifndef NO_VOICE_ENGINE + m_localPort1.GetWindowText(strPort); + int localPort1 = _ttoi(strPort.GetString()); + int res=_veBase->SetLocalReceiver(_audioChannel,localPort1+2); + + TEST_MUSTPASS(_veBase->SetSendDestination(_audioChannel, remotePort1+2, sendIP1),-5) + #endif + +} + +void CDXChannelDlg::SetLocalReceiver() +{ + if(_externalTransport) + return; + + CString strPort; + m_localPort1.GetWindowText(strPort); + int localPort1 = _ttoi(strPort.GetString()); + + + + // May fail because we are sending + TEST_MUSTPASS(_vieNetwork->SetLocalReceiver(_channelId, localPort1),-5); + + #ifndef NO_VOICE_ENGINE + int res=_veBase->SetLocalReceiver(_audioChannel,localPort1+2); + #endif + + + +} + +void CDXChannelDlg::SetCaptureDevice() +{ + if(_parentChannel!=-1) // don't accept changing input on slave channels. + return; + + int camSel=-1; + camSel=m_ctrlDevice.GetCurSel(); + + CString captureStr; + //captureStr.Compare + m_ctrlDevice.GetLBText(camSel, captureStr); + if(captureStr!=_T("N/A") != 0) + { + + TEST_MUSTPASS(_vieFile->StopPlayFile(_captureId),kViEFileNotPlaying); + TEST_MUSTPASS(_vieCapture->DisconnectCaptureDevice(_channelId),kViECaptureDeviceNotConnected); + TEST_MUSTPASS(_vieRender->RemoveRenderer(_captureId),kViERenderInvalidRenderId); + + if(_captureId>=0x1001 && _captureId<0x10FF)// ID is a capture device + { + TEST_MUSTPASS(_captureDevicePool.ReturnCaptureDevice(_captureId),-5); + } + + if(captureStr!=_T("None")==0) + { + _captureId=-1; + } + else if(_tcsstr(captureStr,_T(".avi"))!=NULL ) // Selected an AVI file + { + TEST_MUSTPASS(_vieFile->StartPlayFile(TcharToChar(captureStr.GetBuffer(),-1),_captureId,false,webrtc::kFileFormatAviFile),-5); + TEST_MUSTPASS(_vieRender->AddRenderer(_captureId,m_ctrlLiveVideo.m_hWnd, 0, 0.0f, 0.0f,1.0f,1.0f),-5); + TEST_MUSTPASS(_vieRender->StartRender(_captureId),-5); + TEST_MUSTPASS(_vieFile->SendFileOnChannel(_captureId,_channelId),-5); + TEST_MUSTPASS(_vieFile->StartPlayFileAsMicrophone(_captureId,_channelId,true),-5); + //TEST_MUSTPASS(_vieFile->StartPlayAudioLocally(_captureId,_channelId),-5); + } + else + { + + char captureName[256]; + char uniqueCaptureName[256]; + + TEST_MUSTPASS(_vieCapture->GetCaptureDevice(camSel,captureName,256,uniqueCaptureName,256),-5); + + TEST_MUSTPASS(_captureDevicePool.GetCaptureDevice(_captureId,uniqueCaptureName),-5); + TEST_MUSTPASS(_vieCapture->StartCapture(_captureId),kViECaptureDeviceAlreadyStarted); + TEST_MUSTPASS(_vieCapture->RegisterObserver(_captureId,*this),kViECaptureObserverAlreadyRegistered); + + TEST_MUSTPASS(_vieRender->AddRenderer(_captureId,m_ctrlLiveVideo.m_hWnd, 0, 0.0f, 0.0f,1.0f,1.0f),-5); + TEST_MUSTPASS(_vieCapture->ConnectCaptureDevice(_captureId,_channelId),-5); + TEST_MUSTPASS(_vieRender->StartRender(_captureId),-5); + } + } + +} + + + +void CDXChannelDlg::OnBnClickedStartlisten() +{ + + + // Configure the local ports + SetLocalReceiver(); + + //Configure the remote destination- needed in order to be able to respond to RTCP messages + SetSendDestination(); + + + #ifndef NO_VOICE_ENGINE + TEST_MUSTPASS(_veBase->StartReceive(_audioChannel),-5); + #endif + TEST_MUSTPASS(_vieBase->StartReceive(_channelId),-5); + + +} + +void CDXChannelDlg::OnStartSend() +{ + + // Set the send destination + SetSendDestination(); + + // Configure the local ports (Needed to be able to receive RTCP + //SetLocalReceiver(); + + + // Set the send codec + SetSendCodec(); + + if(_captureId==-1) // If no capture device has been set. + SetCaptureDevice(); //Set the capture device + + + + //TEST_MUSTPASS(_vieRTPRTCP->SetStartSequenceNumber(_channelId,1),-5); + + // Start sending + TEST_MUSTPASS(_vieBase->StartSend(_channelId),-5); + + + #ifndef NO_VOICE_ENGINE + TEST_MUSTPASS(_veBase->StartSend(_audioChannel),-5); + #endif + + +} + +void CDXChannelDlg::ConfigureRender() +{ + TEST_MUSTPASS(_vieRender->AddRenderer(_channelId,m_ctrlLiveRemoteVideo.m_hWnd, 0, 0.0f, 0.0f,1.0f,1.0f),-5); + + TEST_MUSTPASS(_vieFile->SetRenderStartImage(_channelId,"renderStartImage.jpg"),-5); + TEST_MUSTPASS(_vieRender->StartRender(_channelId),-5); + TEST_MUSTPASS(_vieFile->SetRenderTimeoutImage(_channelId,"renderTimeoutImage.jpg"),-5); + + +} + + +void CDXChannelDlg::OnStopSend() +{ + + #ifndef NO_VOICE_ENGINE + TEST_MUSTPASS(_veBase->StopSend(_audioChannel),-5); + #endif + + + TEST_MUSTPASS(_vieBase->StopSend(_channelId),kViEBaseNotSending); // Accept error Not sending + + +} +void CDXChannelDlg::OnBnClickedStoplisten() +{ + + + #ifndef NO_VOICE_ENGINE + TEST_MUSTPASS(_veBase->StopReceive(_audioChannel),-5); + #endif + TEST_MUSTPASS(_vieBase->StopReceive(_channelId),-5); +} + + +void CDXChannelDlg::OnDestroy() +{ + + OnStopSend(); + OnBnClickedStoplisten(); + + if(_vieCapture && _parentChannel==-1) + { + _vieCapture->DisconnectCaptureDevice(_channelId); + _captureDevicePool.ReturnCaptureDevice(_captureId); + } + if(_vieFile && _parentChannel!=-1) + { + TEST_MUSTPASS(_vieFile->StopPlayFile(_captureId),kViEFileNotPlaying); + } + + + + + if(_videoEngine) + { + if(_parentChannel==-1) + { + _vieCodec->DeregisterEncoderObserver(_channelId); + } + _vieBase->DeleteChannel(_channelId); + _channelPool.RemoveChannel(_channelId); + } + + _videoEngine = NULL; +#ifndef NO_VOICE_ENGINE + + if (_voiceEngine) + { + _veBase->DeleteChannel(_audioChannel); + _veBase->Release(); + _veNetwork->Release(); + _veCodec->Release(); + _veRTCP->Release(); + } +#endif + + + strcpy(_logMsg,""); + SetEvent(_callbackEvent); + MSG msg; // Wait until the callback thread exits. Need to handle messages since the callback thread can call SendMessage when updating UI + while(WaitForSingleObject(_callbackThread,10)==WAIT_TIMEOUT) + { + DWORD ret = PeekMessage( &msg, NULL, 0, 0,PM_REMOVE ); + if (ret >0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + CloseHandle(_callbackThread); + CloseHandle(_callbackEvent); + DeleteCriticalSection(&_critCallback); + + TEST_MUSTPASS(_vieCapture->Release()<0,-5); + TEST_MUSTPASS(_vieRTPRTCP->Release()<0,-5); + TEST_MUSTPASS(_vieRender->Release()<0,-5); + TEST_MUSTPASS(_vieCodec->Release()<0,-5); + TEST_MUSTPASS(_vieNetwork->Release()<0,-5); + TEST_MUSTPASS(_vieFile->Release()<0,-5); + TEST_MUSTPASS(_vieBase->Release()<0,-5); + + + +#ifdef TEST_EXTERNAL_TRANSPORT + if(_transport) + delete _transport; + _transport = NULL; +#endif + + delete _externalTransport; + + CDialog::OnDestroy(); + if(_dialogObserver) + { + _dialogObserver->ChannelDialogEnded(this); + } +} + +void CDXChannelDlg::OnCancel() +{ + DestroyWindow(); +} +// If you add a minimize button to your dialog, you will need the code below +// to draw the icon. For MFC applications using the document/view model, +// this is automatically done for you by the framework. + +void CDXChannelDlg::OnPaint() +{ + if (IsIconic()) + { + CPaintDC dc(this); // device context for painting + + SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); + + // Center icon in client rectangle + int cxIcon = GetSystemMetrics(SM_CXICON); + int cyIcon = GetSystemMetrics(SM_CYICON); + CRect rect; + GetClientRect(&rect); + int x = (rect.Width() - cxIcon + 1) / 2; + int y = (rect.Height() - cyIcon + 1) / 2; + + // Draw the icon + dc.DrawIcon(x, y, m_hIcon); + } + else + { + CDialog::OnPaint(); + } +} + +BOOL CDXChannelDlg::OnDeviceChange( UINT nID, DWORD lParam) +{ + if(nID == DBT_DEVNODES_CHANGED) + { + // SetCaptureDevice(); + } + return CDialog::OnDeviceChange(nID, lParam); +} + + +void CDXChannelDlg::OnSysCommand(UINT nID, LPARAM lParam) +{ + if(SC_MAXIMIZE == nID) + {} + CDialog::OnSysCommand(nID, lParam); +} + + +static bool fullScreen = false; +void CDXChannelDlg::OnRButtonUp( UINT nFlags, CPoint point) +{ + CDialog::OnRButtonUp( nFlags, point); +} + +// The system calls this to obtain the cursor to display while the user drags +// the minimized window. +HCURSOR CDXChannelDlg::OnQueryDragIcon() +{ + return (HCURSOR) m_hIcon; +} + +void CDXChannelDlg::OnCbnSelchangeCodecList() +{ + SetSendCodec(); +} + + +void CDXChannelDlg::OnCbnSelchangeSize() +{ + SetSendCodec(); +} + +void CDXChannelDlg::OnCbnSelchangeDevice() +{ + + + SetCaptureDevice(); + +} + + +void CDXChannelDlg::OnCbnSelchangeBitrate() +{ + + SetSendCodec(); + +} + +void CDXChannelDlg::OnCbnSelchangeMinFrameRate() +{ + + SetSendCodec(); + +} + + +void CDXChannelDlg::OnBnClickedTmmbr() +{ + + TEST_MUSTPASS(_vieRTPRTCP->SetTMMBRStatus(_channelId,m_cbTmmbr.GetCheck()==BST_CHECKED),-5); + +} + +void CDXChannelDlg::OnCbnSelchangeRtcpmode() +{ + + /* + kRtcpNone = 0, + kRtcpCompound_RFC4585 = 1, + kRtcpNonCompound_RFC5506 = 2 */ + ViERTCPMode mode=ViERTCPMode(m_ctrlRtcpMode.GetCurSel()); + TEST_MUSTPASS(_vieRTPRTCP->SetRTCPStatus(_channelId,mode),-5); + +} + +void CDXChannelDlg::OnBnClickedFreezelog() +{ + _canAddLog=m_cbFreezeLog.GetCheck()!=BST_CHECKED; +} + +void CDXChannelDlg::OnBnClickedProtNack() +{ + + TEST_MUSTPASS(_vieRTPRTCP->SetNACKStatus(_channelId,true),-5); + +} + +void CDXChannelDlg::OnBnClickedProtNone() +{ + + TEST_MUSTPASS(_vieRTPRTCP->SetNACKStatus(_channelId,false),-5); + TEST_MUSTPASS(_vieRTPRTCP->SetFECStatus(_channelId,false,0,0),-5); +} + +void CDXChannelDlg::OnBnClickedProtFec() +{ + int noCodec=_vieCodec->NumberOfCodecs(); + int redPayloadType=0; + int fecPayloadType=0; + for(unsigned char i=0;iGetCodec(i,codec); + if(codec.codecType==webrtc::kVideoCodecRED) + { + redPayloadType=codec.plType; + } + if(codec.codecType==webrtc::kVideoCodecULPFEC) + { + fecPayloadType=codec.plType; + } + } + TEST_MUSTPASS(_vieRTPRTCP->SetFECStatus(_channelId,true,redPayloadType,fecPayloadType),-5); +} + +void CDXChannelDlg::OnBnClickedCameracap() +{ + char version[1024]; + _vieBase->GetVersion(version); + MessageBox(CharToTchar(version,-1)); + int p=strlen(version); +#ifndef NO_VOICE_ENGINE + _veBase->GetVersion(version); + MessageBox(CharToTchar(version,-1)); +#endif +} + +unsigned int WINAPI CDXChannelDlg::CallbackThread(LPVOID lpParameter) +{ + static_cast(lpParameter)->CallbackThreadProcess(); + return 0; +} + +void CDXChannelDlg::CallbackThreadProcess() +{ + while(1) + { + if(WAIT_OBJECT_0==WaitForSingleObject(_callbackEvent,INFINITE)) + { + char smsg[512]; + EnterCriticalSection(&_critCallback); + strncpy(smsg,_logMsg,strlen(_logMsg)+1); + strcpy(_logMsg,""); + + + LeaveCriticalSection(&_critCallback); + if(strstr(smsg,"Send")!=NULL) + { + unsigned short fractionLost=0; + unsigned int cumulativeLost=0; + unsigned int extendedMax=0; + unsigned int jitter=0; + int rttMs=0; + + + + _vieRTPRTCP->GetReceivedRTCPStatistics(_channelId, + fractionLost, + cumulativeLost, + extendedMax, + jitter, + rttMs); + + //int bw=0; + //if(_vieCodec->GetAvailableBandwidth(_channelId,bw)==0) + //{ + // sprintf(smsg,"%s, rtt %d, loss %d,bw %d", smsg,rttMs,fractionLost,bw); + //} + //else + //{ + // _vieBase->LastError(); // Reset last error. + //} + + + + } + if(strlen(smsg)) + { + m_ctrlInfo.InsertString(0,(LPCTSTR) CharToTchar(smsg,-1)); + while(m_ctrlInfo.GetCount()==151) + m_ctrlInfo.DeleteString(150); + } + else + { + break; // End the callback thread + } + } + } + +} +void CDXChannelDlg::AddToInfo(const char* msg) +{ + if(!_canAddLog) + return; + EnterCriticalSection(&_critCallback); + + SYSTEMTIME systemTime; + GetSystemTime(&systemTime); + + if(strlen(_logMsg)==0) + { + SetEvent(_callbackEvent); // Notify of new + } + + sprintf (_logMsg, "(%2u:%2u:%2u:%3u) %s", systemTime.wHour, + systemTime.wMinute, + systemTime.wSecond, + systemTime.wMilliseconds, + msg + ); + + + + LeaveCriticalSection(&_critCallback); + + +} + +void CDXChannelDlg::IncomingRate(const int videoChannel, + unsigned int framerate, + unsigned int bitrate) +{ + char str[64]; + sprintf(str,"Incoming Fr:%d br %d\n", framerate, bitrate); + AddToInfo(str); +} + +void CDXChannelDlg::RequestNewKeyFrame(int channel) +{ + assert(!"(RequestNewKeyFrame why is it called"); +} +void CDXChannelDlg::PerformanceAlarm(unsigned int cpuLoad) +{ + char str[64]; + sprintf(str,"Performance alarm %d",cpuLoad); + AddToInfo(str); +} +void CDXChannelDlg::OutgoingRate(const int videoChannel, + unsigned int framerate, + unsigned int bitrate) + { + char str[64]; + sprintf(str,"Send Fr:%d br %d", framerate, bitrate); + AddToInfo(str); + } +void CDXChannelDlg::IncomingCodecChanged(const int videoChannel, + const VideoCodec& videoCodec) + { + char str[128]; + sprintf(str,"Incoming codec channel:%d pltype:%d width:%d height:%d\n", videoChannel, videoCodec.plType, videoCodec.width,videoCodec.height); + AddToInfo(str); + } +void CDXChannelDlg::BrightnessAlarm(const int captureId, + const Brightness brightness) +{ + + switch(brightness) + { + case Normal: + AddToInfo("BrightnessAlarm - image ok.\n"); + break; + case Bright: + AddToInfo("BrightnessAlarm - light image.\n"); + break; + case Dark: + AddToInfo("BrightnessAlarm - dark image.\n"); + break; + } +} + +void CDXChannelDlg::CapturedFrameRate(const int captureId, + const unsigned char frameRate) +{ + char str[64]; + sprintf(str,"Local Camera Frame rate:%d \n", frameRate); + AddToInfo(str); +} + +void CDXChannelDlg::NoPictureAlarm(const int captureId, + const CaptureAlarm alarm) +{ + char str[64]; + sprintf(str,"No Picture alarm\n"); + AddToInfo(str); + +} + + +void CDXChannelDlg::OnBnClickedExttransport() +{ + if(m_cbExternalTransport.GetCheck()==BST_CHECKED) + { + m_localPort1.EnableWindow(FALSE); + m_remotePort1.EnableWindow(FALSE); + m_remoteIp1.EnableWindow(FALSE); + m_ctrlPacketLoss.EnableWindow(TRUE); + m_ctrlDelay.EnableWindow(TRUE); + _externalTransport= new tbExternalTransport(*_vieNetwork); + _vieNetwork->RegisterSendTransport(_channelId,*_externalTransport); + } + else + { + _vieNetwork->DeregisterSendTransport(_channelId); + + delete _externalTransport; + _externalTransport=NULL; + m_localPort1.EnableWindow(TRUE); + m_remotePort1.EnableWindow(TRUE); + m_remoteIp1.EnableWindow(TRUE); + m_ctrlPacketLoss.EnableWindow(FALSE); + m_ctrlDelay.EnableWindow(FALSE); + } +} + + +void CDXChannelDlg::OnCbnSelchangePacketloss() +{ + if(_externalTransport) + { + _externalTransport->SetPacketLoss(m_ctrlPacketLoss.GetCurSel()*2); + } +} + + +void CDXChannelDlg::OnCbnSelchangeDelay() +{ + if(_externalTransport) + { + _externalTransport->SetNetworkDelay(m_ctrlDelay.GetCurSel()*30); + } + +} + +void CDXChannelDlg::OnBnClickedBtnRecordIncoming() +{ + + CButton *recordBtn = (CButton *) GetDlgItem(IDC_BTN_RECORD_INCOMING); + + CString text; + recordBtn->GetWindowText(text); + if(text!=_T("Stop Rec Inc")!=0) + { + recordBtn->SetWindowText(_T("Stop Rec Inc")); + SYSTEMTIME time; + GetSystemTime(&time); + sprintf(_fileName,"IncomingChannel%d_%4d%2d%2d%2d%2d.avi",_channelId,time.wYear,time.wMonth,time.wDay,time.wHour,time.wMinute); + + AudioSource audioSource=PLAYOUT; + webrtc::CodecInst audioCodec; + strcpy(audioCodec.plname,"L16"); + audioCodec.rate = 256000; + audioCodec.plfreq = 16000; + audioCodec.pacsize = 160; + + webrtc::VideoCodec videoCodec; + memset(&videoCodec,0,sizeof(videoCodec)); + + strcpy(videoCodec.plName,"VP8"); + videoCodec.maxBitrate=1000; + videoCodec.startBitrate=1000; + videoCodec.width=352; + videoCodec.height=288; + videoCodec.codecType=webrtc::kVideoCodecVP8; + videoCodec.maxFramerate=30; + TEST_MUSTPASS(_vieFile->StartRecordIncomingVideo(_channelId,_fileName,audioSource,audioCodec, videoCodec),-5); + } + else + { + recordBtn->SetWindowText(_T("Record Incoming")); + TEST_MUSTPASS(_vieFile->StopRecordIncomingVideo(_channelId),-5); + CString msg; + msg.AppendFormat(_T("Recorded file %s"),_fileName); + MessageBox(msg); + } +} + +void CDXChannelDlg::OnBnClickedBtnRecordOutgoing() +{ + + CButton *recordBtn = (CButton *) GetDlgItem(IDC_BTN_RECORD_OUTGOING); + CString text; + recordBtn->GetWindowText(text); + if(text!=_T("Stop Rec Out")) + { + recordBtn->SetWindowText(_T("Stop Rec Out")); + SYSTEMTIME time; + GetSystemTime(&time); + sprintf(_fileName,"OutgoingChannel%d_%4d%2d%2d%2d%2d.avi",_channelId,time.wYear,time.wMonth,time.wDay,time.wHour,time.wMinute); + + AudioSource audioSource=MICROPHONE; + webrtc::CodecInst audioCodec; + strcpy(audioCodec.plname,"L16"); + audioCodec.rate = 256000; + audioCodec.plfreq = 16000; + audioCodec.pacsize = 160; + + webrtc::VideoCodec videoCodec; + memset(&videoCodec,0,sizeof(videoCodec)); + + strcpy(videoCodec.plName,"VP8"); + videoCodec.maxBitrate=1000; + videoCodec.startBitrate=1000; + videoCodec.width=352; + videoCodec.height=288; + videoCodec.codecType=webrtc::kVideoCodecVP8; + videoCodec.maxFramerate=30; + TEST_MUSTPASS(_vieFile->StartRecordOutgoingVideo(_channelId,_fileName,audioSource,audioCodec,videoCodec),-5); + } + else + { + recordBtn->SetWindowText(_T("Record Outgoing")); + TEST_MUSTPASS(_vieFile->StopRecordOutgoingVideo(_channelId),-5); + CString msg; + msg.AppendFormat(_T("Recorded file %s"),_fileName); + MessageBox(msg); + } +} + +void CDXChannelDlg::OnBnClickedBtnCreateSlave() +{ + CDXChannelDlg* newSlave =new CDXChannelDlg(_videoEngine,_captureDevicePool,_channelPool,_voiceEngine,NULL,_dialogObserver,_channelId); + newSlave->Create(CDXChannelDlg::IDD,NULL); +} diff --git a/video_engine/main/test/WindowsTest/ChannelDlg.h b/video_engine/main/test/WindowsTest/ChannelDlg.h new file mode 100644 index 0000000000..59b8d073e4 --- /dev/null +++ b/video_engine/main/test/WindowsTest/ChannelDlg.h @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_TEST_WINDOWSTEST_CHANNELDLG_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_TEST_WINDOWSTEST_CHANNELDLG_H_ + +#include "StdAfx.h" +//#define NO_VOICE_ENGINE + +///////////////////////////////////////////////////////////////////////////// +// CDXChannelDlg dialog +// Include ViE headers + +#include "common_types.h" + +#include "vie_base.h" +#include "vie_capture.h" +#include "vie_codec.h" +#include "vie_network.h" +#include "vie_render.h" +#include "vie_rtp_rtcp.h" +#include "vie_errors.h" +#include "vie_file.h" +#include "tbExternalTransport.h" + +#include "resource.h" // main symbols + + +#ifndef NO_VOICE_ENGINE + +#include "voe_base.h" +#include "voe_errors.h" +#include "voe_base.h" +#include "voe_network.h" +#include "voe_codec.h" +#include "voe_rtp_rtcp.h" +#endif + +using namespace webrtc; +class CDXChannelDlg; +class CaptureDevicePool; +class ChannelPool; + +#define TEST_MUSTPASS(expr,oklasterror) \ + { \ + if ((expr)) \ + { \ + CString r_msg; \ + int r_lastError=_vieBase->LastError(); \ + CString exp; \ + exp=#expr;\ + r_msg.Format(_T("\nError at line:%i, %s \nError code: %i\n"),__LINE__, exp,r_lastError); \ + if(r_lastError!=oklasterror) \ + ::MessageBox (NULL, (LPCTSTR)r_msg, TEXT("Error Message"), MB_OK | MB_ICONINFORMATION); \ + } \ + } + +class CDXChannelDlgObserver +{ +public: + virtual void ChannelDialogEnded(CDXChannelDlg* context)=0; + +protected: + virtual ~CDXChannelDlgObserver(){}; + +}; + +class CDXChannelDlg : public CDialog , public ViEEncoderObserver, public ViEDecoderObserver, public ViEBaseObserver, public ViECaptureObserver +{ +// Construction +public: + CDXChannelDlg(VideoEngine* videoEngine, + CaptureDevicePool& captureDevicePool, + ChannelPool& channelPool, + void* voiceEngine=NULL + ,CWnd* pParent = NULL,CDXChannelDlgObserver* observer=NULL,int parentChannel=-1); // standard constructor + +// Dialog Data + //{{AFX_DATA(CDXChannelDlg) + enum { IDD = IDD_DXQUALITY_DIALOG }; + CComboBox m_ctrlDevice; + CComboBox m_ctrlCodec; + CComboBox m_ctrlBitrate; + CComboBox m_ctrlCodecSize; + CComboBox m_ctrlRtcpMode; + CComboBox m_ctrlPacketBurst; + CComboBox m_ctrlMinFrameRate; + + CListBox m_ctrlInfo; + + CStatic m_ctrlLiveRemoteVideo; + CStatic m_ctrlLiveVideo; + CEdit m_localPort1; + CEdit m_remotePort1; + CIPAddressCtrl m_remoteIp1; + CButton m_cbTmmbr; + CButton m_cbExternalTransport; + CButton m_cbFreezeLog; + CButton m_cbDefaultSendChannel; + CComboBox m_ctrlPacketLoss; + CComboBox m_ctrlDelay; + + + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CDXChannelDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + + + +public : + // Callback + + //Capture observer + virtual void BrightnessAlarm(const int captureId, + const Brightness brightness); + + virtual void CapturedFrameRate(const int captureId, + const unsigned char frameRate); + + virtual void NoPictureAlarm(const int captureId, + const CaptureAlarm alarm); + + + // same callback method is being used to raise also to clear. + // true - raise, false - clear + // virtual void NoPictureAlarm(bool active = true); + + // Encoder observer + virtual void OutgoingRate(const int videoChannel, + const unsigned int framerate, + const unsigned int bitrate) ; + + //Decoder observer + virtual void IncomingCodecChanged(const int videoChannel, + const VideoCodec& videoCodec); + + virtual void IncomingRate(const int videoChannel, + const unsigned int framerate, + const unsigned int bitrate); + + virtual void RequestNewKeyFrame(const int videoChannel); + + // Base observer + virtual void PerformanceAlarm(const unsigned int cpuLoad); + + + //virtual void IncomingCSRCChanged(int channel, unsigned int csrc, bool added); + + + +// Implementation +protected: + HICON m_hIcon; + int _channelId; + int _parentChannel; + int _audioChannel; + bool _canAddLog; + + // Thread and function for callbacks + CRITICAL_SECTION _critCallback; + HANDLE _callbackThread; + HANDLE _callbackEvent; + char _logMsg[512]; + static unsigned int WINAPI CallbackThread(LPVOID lpParameter); + void CallbackThreadProcess(); + + + + //void GetSize(int sizeSel, int &width, int &height); + virtual void ConfigureRender(); + + virtual void SetCaptureDevice(); + virtual void SetLocalReceiver(); + virtual void SetSendDestination(); + virtual void SetSendCodec(); + + + void AddToInfo(const char* msg); + + // afx_msg void Command(UINT nID, LPARAM lParam); + + // Generated message map functions + //{{AFX_MSG(CDXChannelDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnSysCommand(UINT nID, LPARAM lParam); + afx_msg void OnRButtonUp( UINT nFlags, CPoint point); + afx_msg BOOL OnDeviceChange( UINT, DWORD ); + afx_msg void OnPaint(); + //afx_msg LRESULT OnDisplayChange(WPARAM, LPARAM); + afx_msg HCURSOR OnQueryDragIcon(); + virtual afx_msg void OnStartSend(); + virtual afx_msg void OnDestroy(); + virtual afx_msg void OnStopSend(); + virtual afx_msg void OnCancel(); + afx_msg void OnTimer(UINT nIDEvent); + + //}}AFX_MSG + DECLARE_MESSAGE_MAP() + +private: + CDXChannelDlgObserver* _dialogObserver; + + VideoEngine* _videoEngine; + ViEBase* _vieBase; + ViECapture* _vieCapture; + ViERTP_RTCP* _vieRTPRTCP; + ViERender* _vieRender; + ViECodec* _vieCodec; + ViENetwork* _vieNetwork; + ViEFile* _vieFile; + tbExternalTransport* _externalTransport; + char _fileName[256]; + + +#ifndef NO_VOICE_ENGINE + VoiceEngine* _voiceEngine; + VoEBase* _veBase; + VoENetwork* _veNetwork; + VoECodec* _veCodec; + VoERTP_RTCP* _veRTCP; +#else + void* _voiceEngine; + +#endif + + VideoCodec _sendCodec; + int _captureId; + CaptureDevicePool& _captureDevicePool; + ChannelPool& _channelPool; + + + afx_msg void OnCbnSelchangeCodecList(); + afx_msg void OnCbnSelchangeDevice(); + afx_msg void OnCbnSelchangeSize(); + afx_msg void OnCbnSelchangeBitrate(); + afx_msg void OnCbnSelchangeWindowSize(); + afx_msg void OnBnClickedversion(); + afx_msg void OnCbnSelchangeMinFrameRate(); + afx_msg void OnBnClickedStartlisten(); + afx_msg void OnBnClickedStoplisten(); + afx_msg void OnBnClickedStopsend(); + afx_msg void OnBnClickedTmmbr(); + afx_msg void OnCbnSelchangeRtcpmode(); + afx_msg void OnBnClickedProtNack(); + afx_msg void OnBnClickedProtNone(); + afx_msg void OnBnClickedProtFec(); + afx_msg void OnBnClickedFreezelog(); + afx_msg void OnBnClickedCameracap(); +public: + afx_msg void OnBnClickedExttransport(); + afx_msg void OnCbnSelchangePacketloss(); + afx_msg void OnCbnSelchangeDelay(); + afx_msg void OnBnClickedBtnRecordIncoming(); + afx_msg void OnBnClickedBtnRecordOutgoing(); + afx_msg void OnBnClickedBtnCreateSlave(); +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_TEST_WINDOWSTEST_CHANNELDLG_H_ diff --git a/video_engine/main/test/WindowsTest/ChannelPool.cpp b/video_engine/main/test/WindowsTest/ChannelPool.cpp new file mode 100644 index 0000000000..dbd1644fae --- /dev/null +++ b/video_engine/main/test/WindowsTest/ChannelPool.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "ChannelPool.h" +#include "map_wrapper.h" +#include +#include +#include "critical_section_wrapper.h" + +ChannelPool::ChannelPool(): +_critSect(*webrtc::CriticalSectionWrapper::CreateCriticalSection()) +{ +} + +ChannelPool::~ChannelPool(void) +{ + assert(_channelMap.Size()==0); + delete &_critSect; +} + +WebRtc_Word32 ChannelPool::AddChannel(int channel) +{ + return _channelMap.Insert(channel,(void*) channel); +} +WebRtc_Word32 ChannelPool::RemoveChannel(int channel) +{ + return _channelMap.Erase(channel); +} + +webrtc::MapWrapper& ChannelPool::ChannelMap() +{ + return _channelMap; +} diff --git a/video_engine/main/test/WindowsTest/ChannelPool.h b/video_engine/main/test/WindowsTest/ChannelPool.h new file mode 100644 index 0000000000..374c676a31 --- /dev/null +++ b/video_engine/main/test/WindowsTest/ChannelPool.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#pragma once +#include "StdAfx.h" +#include "common_types.h" + +#include "vie_base.h" +#include "map_wrapper.h" + +namespace webrtc { +class CriticalSectionWrapper; +} + +class ChannelPool +{ +public: + ChannelPool(); + ~ChannelPool(void); + WebRtc_Word32 AddChannel(int channel); + WebRtc_Word32 RemoveChannel(int channel); + + webrtc::MapWrapper& ChannelMap(); + + private: + webrtc::CriticalSectionWrapper& _critSect; + webrtc::MapWrapper _channelMap; + +}; diff --git a/video_engine/main/test/WindowsTest/StdAfx.h b/video_engine/main/test/WindowsTest/StdAfx.h new file mode 100644 index 0000000000..78b1fbddfd --- /dev/null +++ b/video_engine/main/test/WindowsTest/StdAfx.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_TEST_WINDOWSTEST_STDAFX_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_TEST_WINDOWSTEST_STDAFX_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC Automation classes +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_TEST_WINDOWSTEST_STDAFX_H_ diff --git a/video_engine/main/test/WindowsTest/VideoSize.h b/video_engine/main/test/WindowsTest/VideoSize.h new file mode 100644 index 0000000000..60e2bdd070 --- /dev/null +++ b/video_engine/main/test/WindowsTest/VideoSize.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_TEST_WINDOWSTEST_VIDEOSIZE_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_TEST_WINDOWSTEST_VIDEOSIZE_H_ +#include "StdAfx.h" +enum VideoSize + { + UNDEFINED, + SQCIF, // 128*96 = 12 288 + QQVGA, // 160*120 = 19 200 + QCIF, // 176*144 = 25 344 + CGA, // 320*200 = 64 000 + QVGA, // 320*240 = 76 800 + SIF, // 352*240 = 84 480 + WQVGA, // 400*240 = 96 000 + CIF, // 352*288 = 101 376 + W288P, // 512*288 = 147 456 (WCIF) + W368P, // 640*368 = 235 520 + S_448P, // 576*448 = 281 088 + VGA, // 640*480 = 307 200 + S_432P, // 720*432 = 311 040 + W432P, // 768*432 = 331 776 (a.k.a WVGA 16:9) + S_4SIF, // 704*480 = 337 920 + W448P, // 768*448 = 344 064 + NTSC, // 720*480 = 345 600 + FW448P, // 800*448 = 358 400 + S_768x480P, // 768*480 = 368 640 (a.k.a WVGA 16:10) + WVGA, // 800*480 = 384 000 + S_4CIF, // 704576 = 405 504 + SVGA, // 800*600 = 480 000 + W544P, // 960*544 = 522 240 + W576P, // 1024*576 = 589 824 (W4CIF) + HD, // 960*720 = 691 200 + XGA, // 1024*768 = 786 432 + WHD, // 1280*720 = 921 600 + FULL_HD, // 1440*1080 = 1 555 200 + UXGA, // 1600*1200 = 1 920 000 + WFULL_HD, // 1920*1080 = 2 073 600 + NUMBER_OF_VIDEO_SIZE + }; + +int GetWidthHeight(VideoSize size, int& width, int& height); + + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_TEST_WINDOWSTEST_VIDEOSIZE_H_ diff --git a/video_engine/main/test/WindowsTest/WindowsTest.cpp b/video_engine/main/test/WindowsTest/WindowsTest.cpp new file mode 100644 index 0000000000..ceda8a99da --- /dev/null +++ b/video_engine/main/test/WindowsTest/WindowsTest.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "WindowsTest.h" +#include "ChannelDlg.h" +#include "WindowsTestMainDlg.h" +#include "engine_configurations.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +// Check memory leaks id running debug +#if (defined(_DEBUG) && defined(_WIN32)) +// #include "vld.h" +#endif +///////////////////////////////////////////////////////////////////////////// +// CDXWindowsTestApp + +BEGIN_MESSAGE_MAP(CDXWindowsTestApp, CWinApp) + //{{AFX_MSG_MAP(CDXWindowsTestApp) + // NOTE - the ClassWizard will add and remove mapping macros here. + // DO NOT EDIT what you see in these blocks of generated code! + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CDXWindowsTestApp construction + +CDXWindowsTestApp::CDXWindowsTestApp() +{ + +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only object + +CDXWindowsTestApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CDXWindowsTestApp initialization + +BOOL CDXWindowsTestApp::InitInstance() +{ + int result=0; + #ifndef NO_VOICE_ENGINE + _voiceEngine = VoiceEngine::Create(); + _veBase = VoEBase::GetInterface(_voiceEngine); + result+=_veBase->Init(); + #else + _voiceEngine=NULL; + #endif + + _videoEngine = VideoEngine::Create(); + + _videoEngine->SetTraceFilter(webrtc::kTraceDefault);//webrtc::kTraceDebug | webrtc::kTraceError | webrtc::kTraceApiCall | webrtc::kTraceWarning | webrtc::kTraceCritical | webrtc::kTraceStateInfo | webrtc::kTraceInfo | webrtc::kTraceStream); + _videoEngine->SetTraceFile("trace.txt"); + + ViEBase* vieBase=ViEBase::GetInterface(_videoEngine); + result+=vieBase->Init(); + if(result!=0) + { + ::MessageBox (NULL, (LPCTSTR)("failed to init VideoEngine"), TEXT("Error Message"), MB_OK | MB_ICONINFORMATION); + } + + { + WindowsTestMainDlg dlg(_videoEngine,_voiceEngine); + + m_pMainWnd = &dlg; + int nResponse = dlg.DoModal(); + } + + vieBase->Release(); + + if(!VideoEngine::Delete(_videoEngine)) + { + char errorMsg[255]; + sprintf(errorMsg,"All VideoEngine interfaces are not released properly!"); + ::MessageBox (NULL, (LPCTSTR)errorMsg, TEXT("Error Message"), MB_OK | MB_ICONINFORMATION); + } + + #ifndef NO_VOICE_ENGINE + + _veBase->Terminate(); + if(_veBase->Release()!=0) + { + // ensure that no interface is still referenced + char errorMsg[256]; + sprintf(errorMsg,"All VoiceEngine interfaces are not released properly!"); + ::MessageBox (NULL, (LPCTSTR)errorMsg, TEXT("Error Message"), MB_OK | MB_ICONINFORMATION); + } + + if (false == VoiceEngine::Delete(_voiceEngine)) + { + char errorMsg[256]; + sprintf(errorMsg,"VoiceEngine::Delete() failed!"); + ::MessageBox (NULL, (LPCTSTR)errorMsg, TEXT("Error Message"), MB_OK | MB_ICONINFORMATION); + } + #endif + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/video_engine/main/test/WindowsTest/WindowsTest.h b/video_engine/main/test/WindowsTest/WindowsTest.h new file mode 100644 index 0000000000..dc3ee9d26b --- /dev/null +++ b/video_engine/main/test/WindowsTest/WindowsTest.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_TEST_WINDOWSTEST_WINDOWSTEST_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_TEST_WINDOWSTEST_WINDOWSTEST_H_ + + +#include "StdAfx.h" +#include "resource.h" // main symbols + + + +///////////////////////////////////////////////////////////////////////////// + +//Forward declarations +namespace webrtc { + class VoiceEngine; + class VoEBase; + class VideoEngine; +} +using namespace webrtc; + +class CDXWindowsTestApp : public CWinApp +{ +public: + CDXWindowsTestApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CDXWindowsTestApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CDXWindowsTestApp) + // NOTE - the ClassWizard will add and remove member functions here. + // DO NOT EDIT what you see in these blocks of generated code ! + //}}AFX_MSG + DECLARE_MESSAGE_MAP() + + VideoEngine* _videoEngine; + VoiceEngine* _voiceEngine; + VoEBase* _veBase; +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_TEST_WINDOWSTEST_WINDOWSTEST_H_ diff --git a/video_engine/main/test/WindowsTest/WindowsTestMainDlg.cpp b/video_engine/main/test/WindowsTest/WindowsTestMainDlg.cpp new file mode 100644 index 0000000000..fcc490d93b --- /dev/null +++ b/video_engine/main/test/WindowsTest/WindowsTestMainDlg.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// WindowsTestMainDlg.cpp : implementation file +// +#include "WindowsTestMainDlg.h" +#include "WindowsTest.h" +#include "ChannelDlg.h" + +#include "voe_base.h" + +// WindowsTestMainDlg dialog + +IMPLEMENT_DYNAMIC(WindowsTestMainDlg, CDialog) + +WindowsTestMainDlg::WindowsTestMainDlg(VideoEngine* videoEngine,void* voiceEngine,CWnd* pParent /*=NULL*/) + : CDialog(WindowsTestMainDlg::IDD, pParent), + _videoEngine(videoEngine), + _voiceEngine((VoiceEngine*) voiceEngine), + _testDlg1(NULL), + _testDlg2(NULL), + _testDlg3(NULL), + _testDlg4(NULL), + _externalInWidth(0), + _externalInHeight(0), + _externalInVideoType(0), + _captureDevicePool(videoEngine) +{ + +} + +WindowsTestMainDlg::~WindowsTestMainDlg() +{ +} + +void WindowsTestMainDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); +} + + +BEGIN_MESSAGE_MAP(WindowsTestMainDlg, CDialog) + ON_BN_CLICKED(IDC_CHANNEL1, &WindowsTestMainDlg::OnBnClickedChannel1) + ON_BN_CLICKED(IDC_CHANNEL2, &WindowsTestMainDlg::OnBnClickedChannel2) + ON_BN_CLICKED(IDC_CHANNEL3, &WindowsTestMainDlg::OnBnClickedChannel3) + ON_BN_CLICKED(IDC_CHANNEL4, &WindowsTestMainDlg::OnBnClickedChannel4) +END_MESSAGE_MAP() + + + +void WindowsTestMainDlg::OnBnClickedChannel1() +{ + if(!_testDlg1) + { + _testDlg1=new CDXChannelDlg(_videoEngine,_captureDevicePool,_channelPool,_voiceEngine,NULL,this); + _testDlg1->Create(CDXChannelDlg::IDD,this); + } + else + { + _testDlg1->SetActiveWindow(); + } +} + +void WindowsTestMainDlg::OnBnClickedChannel2() +{ + if(!_testDlg2) + { + _testDlg2=new CDXChannelDlg(_videoEngine,_captureDevicePool,_channelPool,_voiceEngine,NULL,this); + _testDlg2->Create(CDXChannelDlg::IDD,this); + + } + else + { + _testDlg2->SetActiveWindow(); + } +} + +void WindowsTestMainDlg::ChannelDialogEnded(CDXChannelDlg* context) +{ + if(context==_testDlg4) + { + delete _testDlg4; + _testDlg4=NULL; + } + else if(context==_testDlg3) + { + delete _testDlg3; + _testDlg3=NULL; + } + else if(context==_testDlg2) + { + delete _testDlg2; + _testDlg2=NULL; + } + else if(context==_testDlg1) + { + delete _testDlg1; + _testDlg1=NULL; + } + else // Slave channel + { + delete context; + } + +} + + + +void WindowsTestMainDlg::OnBnClickedChannel3() +{ + if(!_testDlg3) + { + _testDlg3=new CDXChannelDlg(_videoEngine,_captureDevicePool,_channelPool,_voiceEngine,NULL,this); + _testDlg3->Create(CDXChannelDlg::IDD,this); + + } + else + { + _testDlg3->SetActiveWindow(); + } +} + +void WindowsTestMainDlg::OnBnClickedChannel4() +{ + if(!_testDlg4) + { + _testDlg4=new CDXChannelDlg(_videoEngine,_captureDevicePool,_channelPool,_voiceEngine,NULL,this); + _testDlg4->Create(CDXChannelDlg::IDD,this); + + } + else + { + _testDlg4->SetActiveWindow(); + } +} diff --git a/video_engine/main/test/WindowsTest/WindowsTestMainDlg.h b/video_engine/main/test/WindowsTest/WindowsTestMainDlg.h new file mode 100644 index 0000000000..8aae99a9e1 --- /dev/null +++ b/video_engine/main/test/WindowsTest/WindowsTestMainDlg.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#pragma once +#include "StdAfx.h" +#include "WindowsTestResource.h" + +#include "ChannelDlg.h" +#include "CaptureDevicePool.h" +#include "ChannelPool.h" + +//Forward declarations +namespace webrtc { + class VideoEngine; + class VoiceEngine; +} +using namespace webrtc; +class CDXCaptureDlg; + + +class WindowsTestMainDlg : public CDialog, private CDXChannelDlgObserver +{ + DECLARE_DYNAMIC(WindowsTestMainDlg) + +public: + WindowsTestMainDlg(VideoEngine* videoEngine,void* voiceEngine=NULL,CWnd* pParent = NULL); // standard constructor + virtual ~WindowsTestMainDlg(); + +// Dialog Data + enum { IDD = IDD_WINDOWSTEST_MAIN }; + +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + + DECLARE_MESSAGE_MAP() +public: + afx_msg void OnBnClickedChannel1(); + afx_msg void OnBnClickedChannel2(); + afx_msg void OnBnClickedChannel3(); + afx_msg void OnBnClickedChannel4(); + + + VideoEngine* _videoEngine; + VoiceEngine* _voiceEngine; + VoEBase* _veBase; + + CDXChannelDlg* _testDlg1; + CDXChannelDlg* _testDlg2; + CDXChannelDlg* _testDlg3; + CDXChannelDlg* _testDlg4; + + int _externalInWidth; + int _externalInHeight; + int _externalInVideoType; + + CaptureDevicePool _captureDevicePool; + ChannelPool _channelPool; + + +private: + virtual void ChannelDialogEnded(CDXChannelDlg* context); + +public: + +}; diff --git a/video_engine/main/test/WindowsTest/WindowsTestResouce.rc b/video_engine/main/test/WindowsTest/WindowsTestResouce.rc new file mode 100644 index 0000000000..5e866ad614 --- /dev/null +++ b/video_engine/main/test/WindowsTest/WindowsTestResouce.rc @@ -0,0 +1,101 @@ +// Microsoft Visual C++ generated resource script. +// +#include "WindowsTestResource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Swedish resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_SVE) +#ifdef _WIN32 +LANGUAGE LANG_SWEDISH, SUBLANG_DEFAULT +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "WindowsTestResource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_WINDOWSTEST_MAIN DIALOGEX 0, 0, 186, 156 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Windows ViE Test" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK",IDOK,129,7,50,14 + PUSHBUTTON "Cancel",IDCANCEL,129,24,50,14 + PUSHBUTTON "Channel 1",IDC_CHANNEL1,129,45,50,14 + PUSHBUTTON "Channel 2",IDC_CHANNEL2,129,62,50,14 + PUSHBUTTON "Channel 3",IDC_CHANNEL3,129,79,50,14 + PUSHBUTTON "Channel 4",IDC_CHANNEL4,129,96,50,14 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_WINDOWSTEST_MAIN, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 149 + END +END +#endif // APSTUDIO_INVOKED + +#endif // Swedish resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/video_engine/main/test/WindowsTest/WindowsTestResource.h b/video_engine/main/test/WindowsTest/WindowsTestResource.h new file mode 100644 index 0000000000..2d49c28a6f --- /dev/null +++ b/video_engine/main/test/WindowsTest/WindowsTestResource.h @@ -0,0 +1,28 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by WindowsTestResouce.rc +// +#define IDD_WINDOWSTEST_MAIN 101 +#define IDC_CHANNEL1 1001 +#define IDC_CHANNEL2 1002 +#define IDC_CHANNEL3 1004 +#define IDC_CHANNEL4 1005 +#define IDC_POSITION 1009 +#define IDC_INFORMATION 1050 +#define IDC_CHECK_CHANNEL1 1070 +#define IDC_CHECK_CHANNEL2 1071 +#define IDC_CHECK_CHANNEL3 1072 +#define IDC_CHECK_CHANNEL4 1073 +#define IDC_COMBO1 1074 +#define IDC_BTN_CREATE2 1076 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 106 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1076 +#define _APS_NEXT_SYMED_VALUE 107 +#endif +#endif diff --git a/video_engine/main/test/WindowsTest/captureDeviceImage.jpg b/video_engine/main/test/WindowsTest/captureDeviceImage.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3bb3ba48bbdaab19f788179d627b6d83304e2c45 GIT binary patch literal 10742 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

#2im%gf=K z07vIFb3yAso$*`wJx*7oi*IKLCKPK#e@i)O>nRWVtNNA4Cs??VRSweFmQL$%?XDui zCdj)+)Yc}Sh3Wa)FRWgMy3o zB%AKY91>wY6@{sDGJKoaoIFbSxKiWzt+sK3Dd!Vmg5Hwnr@U2!p+^9cj>jAc)Kr)& z*#0}($x7e-{6yLE8sSYG7s9!$^$T4W9 z+>N_s?#VuO{p8Qcc(o^iIYD44{{Bt_|9%-I$3D*JL$xTm9#58O8J|0?`@|Lc;Ev5D zt_xR%7&NP;UHvlJBW-IM0~6o8KN1Zh#eCu4C4!&(EZ;(x6zS!4+-s?*KW+;uzQM*0 znO~n}u;Ey6?V~utH;P=ECM)JpicU!jgUce-rF@rBX^$v2+LD^9$7FS#=T8PRdZy4% zt(QK~+WB8YSkzPx*>F%EKUqJ<81g$`D|tn?0-zYio3m@Nf~jJGiK(f}pgCDp`w*w# zEAn?K59=|5$IeHA{hc}M*3G$`1>GI#O-vC_HXPs+A=``}9a{M5aIlSSiXLb5DW#vH zDSTpobfv{SQCC1ZB>yP5%u(@+d}mzc?-%SxE~-o`>Q%*c{y^niwG30Hl8Bi|Xag|B za~#co2a@XzlGzbmw`QFGSj@?|W-%||R#eTV?HfW=5AxrqV?n3zShc-N$xTCQDAlKc=ER%zyQqZ zv4PLv#OPd0oqv)6t$710GQ(Me0b+f}Vlhpg=pJ!B6O!X*-QT!kg_^V)+iTPM_ngi0 z`n*!?u8)X@*Y|<;$kP4?k8j;&O@-=;rk_UsE*;ZinJ_)W!pI*RT#AQ zetvh&-Z}zQ_l`B_dKPMQT>7D=NqX^M49G_1FK@X!MS!DsM*qDI7oB-O8+r<7qGq`} z%&e_Wl<;$Fvdb%V3q1JdVm&4)gs6K$1Dw-z#FO3tOTVRe0EBsxu?)ncQf`Es?qw4S8yjCaimOGAFZ+U}@9tgT}Np|G-cwH6X(f&~2g|eRZgjsrt3Twu? zQa9V%o7UXp4h)C8zGJ(Ml5z;kGnQo}!azE1(!%k@Uuk_EmTx2iotl!y+Rl%CH_?gx-xgZWoJ(n1802h{Q8#34doB)Z;w)F(u`gjuQa8+1%uZB^gOi7wi}AZ6H7B znj;+ov4ZGsF`&85>jgjY#g4+BOZtB?nD@JL`Msqn`>?G75md@e!6>9VO^zfdn`Afm z!d4Y<~*CBW(~Ya-B*O z-3|%-jpONH*W2sS74_>8>Zu6Z{T7i4Mx%!aif`e#6nC zUr`qC>i}$Q{Hv+U=1N_(Tl38)Yw>5j9)g`ZiyipN;+3jpj*ly;baK@?)ueh7!qEV! zjoffxcw4DCfcTS}VnSzy`qx6YSCZAncBW!&#wWp|p(EBe80b^CT@Ug=ZH2EVP4S4P zEd)>~P>=ESw%o4`Y4UoM$^L{zbf0thTZdr0nSHLpZED4Tv^F|7V*FIS$Zef{UmL6SSUl*d<-Rt1)7DuGH(_B5KN6E{uRux-wIE?eKog| zAOcZ(W)YSoxf&tawDYnb)%D1Hn4qK)9$-58Rq*S*js6kt8@4cwHE5@3A)ce}c4BeY zGk=jcWd9&op}Zj(={9pJNf`l2d}Vy`@HnQy6(6yw<#-NgPO-5c`S2rzgL9el$mY6; zdc0qd8lN5^ZBe28k14FJYH))fd2nwMRf^KF+vuo(>GTIk2N`xM$DZaiRiJNrp_P2+ zw|0vy2@?LYKx@~xUYjy?J7VqpGr15wG36Iub-3Vs#GLFOH7`y-4>x_UO%plvfM;gjSj-6@jIu()XmNc z3aq}vXKXH4N1*Qz=Kvp95Xt?K`^h!rR>b~|JBWd%d6&AX;vKVR`B4nKbD2M-oa8Cy z8?<}H)$=UUkp{?Q&pU2C>d>l~B!gbyc6;_U2Nsta^u0v-^ibq`q?eSgwfQf4+#^W5 zOAa2nUd2vBkbGq4)@N|c#POzE1sE%c-ppndQ)84zpn*harM*3Uod zCYz?3`S?axsx(dhP@Hb|2K{~ks?w6tK?5Zp-atSNiU4{r3RywchHrxXWw7!2hJwxuCA!_ z-wjbhsZrUEzul4BWIuwZaT{$7|PR{=|3ukf6J2~ zH7~UDgIxU=4qSB(abNctQOsq-kvP{ExcalMg5cDlyvEh5xc^BQsu+h@0VLIQmvDn3>WTSHo(I+ zt89x0zm%jH_&b`+eOZCfTb|CYImI%U{S}S-(%9!riQucPy(HH2=c!;DohMDTR{TY8 zY;`-)cUIUvv=l48t^yffopz|XQ^7M#C317+;!z)L$P2Ni*2v;YRn$b0ncN_APt zzIb=1A;C$XLR_C7Q4}e)lyzy3Vx9;|LfAOI3JeF&{6)qim=LJJH)?o0lW@t;Sm0Nh zENFP~=$Zj3J-(fzZ-L@h;oH7g;nDf_Zr$83$mCxayRRJna(z#O!%g>*iJM@&CwmZ` z+lGoC zk^n^zT4_#R@Yc>4sVgNmpI9M7s#~6oG*(oAOl!;BZh{b&I=l}4MB6y(%Hh<)!+860 z6ZvpP`RZDV@z5XR!mJ%Uw4->58Kcc*%8)FT@Cj|Z{ALd0r+)1EJnd&lMPg@ag15(h z>?4fROQx9lFSYN8+dQ|Ly!;|o%KtKbWTXeI(5Z;5D6({;i;AGD(BbQM_@1w=G?gVX z^d>ubLvzt1mi>Sxfs&N^Om~k!uMINn&NNM4arNieo^Iub11Qf4;xi?NG5Yi;G#-8f3`0yw!@oi^!FWBdC~ut(QB{2@DFfKBzvj;fw*H)iv<; z^5fC9r<;T8i4iI}mn#Uf#k~%)L+cZZtTMTfHip>Bg~j>pv%==X=lK@ub!K$N;-C`8 zcR7f`5ce)z{z^5avu~+_~5=1csG%{Eo3-N>Bm%%k+I3IZ^~?~dAk)-I8Q)1UcU$qYO}P2mst4!lCnZpf^p;EM&_Sx>L*B5rki@aKv#a6-H%&X z%sW7=2Dp{OO@aZVZT5{LXRwouV;Ihypia`qw_N~RkrL^lAjcE+;j4Np<_QLJ>alh7{5( zel~h3UYd|7Z*7ZxL3Ep{1#OSq)I3hRcBum-%%$8rm&G&B52aNG(9?Xr29nO_Web@AB}{Mg$4i#R7J@mo zSY$6VuiQ{j!2( z!fC*4B%Z89nskokN;37`!@mxP19bFbryK6sLQPqT_pY8+Na?8X_R)ybXR*WMIU%3W zW%tG1LxeL%aB`&dXcb)H0Y30!5+l|vKTV*hk(rbuKMIX_9liUJVR;%YWar9*D^Tk3 z+Ewx@%!0nS_w@x)+#T(uME5l)bS^oh$Iex=j^ErlPCJyeNqnGLw>Zx`_ns?eA1AZ` zyFMr_h_ zN*q6%cndvqHc*bgv={kmV&NSs2#1eNnj6g!A&)LI`9md_ezoZn-f zXzupW$q{eXGaz&&1mL&*F5w25nl4_X#YI8T1{e-*eitO13#kwy<%kkBXdgnyvj~rT zlxB9hox{knogg+BNmLY0(I?Wbt06BH zeP>btv*%skE!-{Kq)raPUFKQ!p&0cRup*rwWZ>qolb_ID9R-Z(^1HYx%$uq`oOK70 zMq&yje@~fWPA?$Y`|;8X2>&Z7eiVA96WEc?&oa;=0GB6?5Uv^T1qL zYO!esxK__FwwD^iS2!p3>*CNtP*}TNd{sf8G^%qa2OS-Y)3t>%$NTCat0h2!)))s% l9ZIUcDM%1Oet5KIeuNe-Q?C|S6@ShTH6(|9v*tM;`yYA7U@iav literal 0 HcmV?d00001 diff --git a/video_engine/main/test/WindowsTest/renderStartImage.jpg b/video_engine/main/test/WindowsTest/renderStartImage.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b10a84259c3d37ed35e2d449f1f5ab64265b5c6a GIT binary patch literal 10274 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 literal 0 HcmV?d00001 diff --git a/video_engine/main/test/WindowsTest/renderTimeoutImage.jpg b/video_engine/main/test/WindowsTest/renderTimeoutImage.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cb34d67664328692c26b1811bf1ef9979f14b941 GIT binary patch literal 10554 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 z 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, data, len); + newPacket->length = len; + newPacket->channel = channel; + + _crit.Enter(); + newPacket->receiveTime = NowMs() + _networkDelayMs; + _rtpPackets.push(newPacket); + _event.Set(); + _crit.Leave(); + return len; +} + +int tbExternalTransport::SendRTCPPacket(int channel, const void *data, int len) +{ + _statCrit.Enter(); + _rtcpCount++; + _statCrit.Leave(); + + VideoPacket* newPacket = new VideoPacket(); + memcpy(newPacket->packetBuffer, data, len); + newPacket->length = len; + newPacket->channel = channel; + + _crit.Enter(); + newPacket->receiveTime = NowMs() + _networkDelayMs; + _rtcpPackets.push(newPacket); + _event.Set(); + _crit.Leave(); + return len; +} + +WebRtc_Word32 tbExternalTransport::SetPacketLoss(WebRtc_Word32 lossRate) +{ + CriticalSectionScoped cs(_statCrit); + _lossRate = lossRate; + return 0; +} + +void tbExternalTransport::SetNetworkDelay(WebRtc_Word64 delayMs) +{ + CriticalSectionScoped cs(_crit); + _networkDelayMs = delayMs; + return; +} + +void tbExternalTransport::ClearStats() +{ + CriticalSectionScoped cs(_statCrit); + _rtpCount = 0; + _dropCount = 0; + _rtcpCount = 0; + return; +} + +void tbExternalTransport::GetStats(WebRtc_Word32& numRtpPackets, WebRtc_Word32& numDroppedPackets, WebRtc_Word32& numRtcpPackets) +{ + CriticalSectionScoped cs(_statCrit); + numRtpPackets = _rtpCount; + numDroppedPackets = _dropCount; + numRtcpPackets = _rtcpCount; + return; +} + +void tbExternalTransport::EnableSSRCCheck() +{ + CriticalSectionScoped cs(_statCrit); + _checkSSRC = true; +} +unsigned int tbExternalTransport::ReceivedSSRC() +{ + CriticalSectionScoped cs(_statCrit); + return _lastSSRC; +} + +void tbExternalTransport::EnableSequenceNumberCheck() +{ + CriticalSectionScoped cs(_statCrit); + _checkSequenceNumber = true; +} + +unsigned short tbExternalTransport::GetFirstSequenceNumber() +{ + CriticalSectionScoped cs(_statCrit); + return _firstSequenceNumber; +} + + +bool tbExternalTransport::ViEExternalTransportRun(void* object) +{ + return static_cast(object)->ViEExternalTransportProcess(); +} +bool tbExternalTransport::ViEExternalTransportProcess() +{ + unsigned int waitTime = KMaxWaitTimeMs; + + VideoPacket* packet = NULL; + + while (!_rtpPackets.empty()) + { + // Take first packet in queue + _crit.Enter(); + packet = _rtpPackets.front(); + WebRtc_Word64 timeToReceive = packet->receiveTime - NowMs(); + if (timeToReceive > 0) + { + // No packets to receive yet + if (timeToReceive < waitTime && + timeToReceive > 0) + { + waitTime = (unsigned int) timeToReceive; + } + _crit.Leave(); + break; + } + _rtpPackets.pop(); + _crit.Leave(); + + // Send to ViE + if (packet) + { + { + CriticalSectionScoped cs(_statCrit); + if (_checkSSRC) + { + _lastSSRC = ((packet->packetBuffer[8]) << 24); + _lastSSRC += (packet->packetBuffer[9] << 16); + _lastSSRC += (packet->packetBuffer[10] << 8); + _lastSSRC += packet->packetBuffer[11]; + _checkSSRC = false; + } + if (_checkSequenceNumber) + { + _firstSequenceNumber = (unsigned char)packet->packetBuffer[2] << 8; + _firstSequenceNumber += (unsigned char)packet->packetBuffer[3]; + _checkSequenceNumber = false; + } + } + /* + unsigned short sequenceNumber = (unsigned char)packet->packetBuffer[2] << 8; + sequenceNumber += (unsigned char)packet->packetBuffer[3]; + + int marker=packet->packetBuffer[1] & 0x80; + unsigned int timestamp=((((unsigned char*)packet->packetBuffer)[4]) << 24) + ((((unsigned char*)packet->packetBuffer)[5])<<16) +((((unsigned char*)packet->packetBuffer)[6])<<8)+(((unsigned char*)packet->packetBuffer)[7]); + char str[256]; + sprintf(str,"Receiving seq %u length %d m %d, ts %u\n", sequenceNumber,packet->length,marker,timestamp) ; + OutputDebugString(str);*/ + + _vieNetwork.ReceivedRTPPacket(packet->channel, packet->packetBuffer, packet->length); + delete packet; + packet = NULL; + } + } + while (!_rtcpPackets.empty()) + { + // Take first packet in queue + _crit.Enter(); + packet = _rtcpPackets.front(); + WebRtc_Word64 timeToReceive = packet->receiveTime - NowMs(); + if (timeToReceive > 0) + { + // No packets to receive yet + if (timeToReceive < waitTime && + timeToReceive > 0) + { + waitTime = (unsigned int) timeToReceive; + } + _crit.Leave(); + break; + } + packet = _rtcpPackets.front(); + _rtcpPackets.pop(); + _crit.Leave(); + + // Send to ViE + if (packet) + { + _vieNetwork.ReceivedRTCPPacket(packet->channel, packet->packetBuffer, packet->length); + delete packet; + packet = NULL; + } + } + _event.Wait(waitTime + 1); // Add 1 ms to not call to early... + return true; +} + +WebRtc_Word64 tbExternalTransport::NowMs() +{ + return TickTime::MillisecondTimestamp(); +} diff --git a/video_engine/main/test/WindowsTest/tbExternalTransport.h b/video_engine/main/test/WindowsTest/tbExternalTransport.h new file mode 100644 index 0000000000..85c2f6b19b --- /dev/null +++ b/video_engine/main/test/WindowsTest/tbExternalTransport.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// +// tbExternalTransport.h +// + +#ifndef WEBRTC_VIDEO_ENGINE_MAIN_TEST_WINDOWSTEST_TBEXTERNALTRANSPORT_H_ +#define WEBRTC_VIDEO_ENGINE_MAIN_TEST_WINDOWSTEST_TBEXTERNALTRANSPORT_H_ + +#include "common_types.h" +#include + +namespace webrtc +{ +class CriticalSectionWrapper; +class EventWrapper; +class ThreadWrapper; +class ViENetwork; +} + +class tbExternalTransport : public webrtc::Transport +{ +public: + tbExternalTransport(webrtc::ViENetwork& vieNetwork); + ~tbExternalTransport(void); + + virtual int SendPacket(int channel, const void *data, int len); + virtual int SendRTCPPacket(int channel, const void *data, int len); + + WebRtc_Word32 SetPacketLoss(WebRtc_Word32 lossRate); // Rate in % + void SetNetworkDelay(WebRtc_Word64 delayMs); + + void ClearStats(); + void GetStats(WebRtc_Word32& numRtpPackets, WebRtc_Word32& numDroppedPackets, WebRtc_Word32& numRtcpPackets); + + void EnableSSRCCheck(); + unsigned int ReceivedSSRC(); + + void EnableSequenceNumberCheck(); + unsigned short GetFirstSequenceNumber(); + + +protected: + static bool ViEExternalTransportRun(void* object); + bool ViEExternalTransportProcess(); +private: + WebRtc_Word64 NowMs(); + + enum { KMaxPacketSize = 1650}; + enum { KMaxWaitTimeMs = 100}; + typedef struct + { + WebRtc_Word8 packetBuffer[KMaxPacketSize]; + WebRtc_Word32 length; + WebRtc_Word32 channel; + WebRtc_Word64 receiveTime; + } VideoPacket; + + typedef std::queue VideoPacketQueue; + + + webrtc::ViENetwork& _vieNetwork; + webrtc::ThreadWrapper& _thread; + webrtc::EventWrapper& _event; + webrtc::CriticalSectionWrapper& _crit; + webrtc::CriticalSectionWrapper& _statCrit; + + WebRtc_Word32 _lossRate; + WebRtc_Word64 _networkDelayMs; + WebRtc_Word32 _rtpCount; + WebRtc_Word32 _rtcpCount; + WebRtc_Word32 _dropCount; + + VideoPacketQueue _rtpPackets; + VideoPacketQueue _rtcpPackets; + + bool _checkSSRC; + WebRtc_UWord32 _lastSSRC; + bool _checkSequenceNumber; + WebRtc_UWord16 _firstSequenceNumber; + WebRtc_Word32 _lastSeq; + + //int& numberOfErrors; + + //int _bits; + //int _lastTicks; + //int _dropCnt; + //int _sentCount; + //int _frameCount; + //int _packetLoss; + + //VideoEngine* _video; + + //ReceiveBufferQueue _videoBufferQueue; + //ReceiveBufferQueue _rtcpBufferQueue; +}; + +#endif // WEBRTC_VIDEO_ENGINE_MAIN_TEST_WINDOWSTEST_TBEXTERNALTRANSPORT_H_ diff --git a/video_engine/main/test/WindowsTest/videosize.cpp b/video_engine/main/test/WindowsTest/videosize.cpp new file mode 100644 index 0000000000..a675ec5ff7 --- /dev/null +++ b/video_engine/main/test/WindowsTest/videosize.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "VideoSize.h" +int GetWidthHeight( VideoSize size, int& width, int& height) +{ + switch(size) + { + case SQCIF: + width = 128; + height = 96; + return 0; + case QQVGA: + width = 160; + height = 120; + return 0; + case QCIF: + width = 176; + height = 144; + return 0; + case CGA: + width = 320; + height = 200; + return 0; + case QVGA: + width = 320; + height = 240; + return 0; + case SIF: + width = 352; + height = 240; + return 0; + case WQVGA: + width = 400; + height = 240; + return 0; + case CIF: + width = 352; + height = 288; + return 0; + case W288P: + width = 512; + height = 288; + return 0; + case W368P: + width = 640; + height = 368; + return 0; + case S_448P: + width = 576; + height = 448; + return 0; + case VGA: + width = 640; + height = 480; + return 0; + case S_432P: + width = 720; + height = 432; + return 0; + case W432P: + width = 768; + height = 432; + return 0; + case S_4SIF: + width = 704; + height = 480; + return 0; + case W448P: + width = 768; + height = 448; + return 0; + case NTSC: + width = 720; + height = 480; + return 0; + case FW448P: + width = 800; + height = 448; + return 0; + case S_768x480P: + width = 768; + height = 480; + return 0; + case WVGA: + width = 800; + height = 480; + return 0; + case S_4CIF: + width = 704; + height = 576; + return 0; + case SVGA: + width = 800; + height = 600; + return 0; + case W544P: + width = 960; + height = 544; + return 0; + case W576P: + width = 1024; + height = 576; + return 0; + case HD: + width = 960; + height = 720; + return 0; + case XGA: + width = 1024; + height = 768; + return 0; + case FULL_HD: + width = 1440; + height = 1080; + return 0; + case WHD: + width = 1280; + height = 720; + return 0; + case UXGA: + width = 1600; + height = 1200; + return 0; + case WFULL_HD: + width = 1920; + height = 1080; + return 0; + default: + return -1; + } + return -1; +} \ No newline at end of file diff --git a/video_engine/main/test/WindowsTest/windowstest.gyp b/video_engine/main/test/WindowsTest/windowstest.gyp new file mode 100644 index 0000000000..486083f4ca --- /dev/null +++ b/video_engine/main/test/WindowsTest/windowstest.gyp @@ -0,0 +1,73 @@ +{ + 'includes': [ + '../../../../common_settings.gypi', # Common settings + ], + 'conditions': [ + ['OS=="win"', { + 'targets': [ + # WinTest - GUI test for Windows + { + 'target_name': 'vie_win_test', + 'type': 'executable', + 'dependencies': [ + '../../../../system_wrappers/source/system_wrappers.gyp:system_wrappers', # need the headers + '../../../../modules/video_render/main/source/video_render.gyp:video_render_module', + '../../../../modules/video_capture/main/source/video_capture.gyp:video_capture_module', + ## VoiceEngine + '../../../../voice_engine/main/source/voice_engine_core.gyp:voice_engine_core', + ## VideoEngine + '../../source/video_engine_core.gyp:video_engine_core', + ], + 'include_dirs': [ + './interface', + '../../../../', # common_types.h and typedefs.h + '../commonTestClasses/' + ], + 'sources': [ + 'Capture.rc', + 'captureDeviceImage.jpg', + 'ChannelDlg.cpp', + 'ChannelDlg.h', + 'ChannelPool.cpp', + 'ChannelPool.h', + 'renderStartImage.jpg', + 'renderTimeoutImage.jpg', + 'res\Capture.rc2', + 'resource.h', + 'StdAfx.h', + 'videosize.cpp', + 'VideoSize.h', + 'WindowsTest.cpp', + 'WindowsTest.h', + 'WindowsTestMainDlg.cpp', + 'WindowsTestMainDlg.h', + 'WindowsTestResouce.rc', + 'WindowsTestResource.h', + 'tbExternalTransport.cpp', + 'CaptureDevicePool.cpp', + 'tbExternalTransport.h', + 'CaptureDevicePool.h', + + ], + 'configurations': { + 'Common_Base': { + 'msvs_configuration_attributes': { + 'UseOfMFC': '1', # Static + }, + }, + }, + 'msvs_settings': { + 'VCLinkerTool': { + 'SubSystem': '2', # Windows + }, + }, + }, + ], + }], + ], +} +# Local Variables: +# tab-width:2 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=2 shiftwidth=2: